UntrustedNetworkDestination
error native on by defaultGroup: Security
Detect request-derived destination reaching curl, URL stream reads or equivalent network sinks without a proven outbound destination policy.
Example
<?php
$ch = curl_init($_GET['url']); curl_exec($ch);
$h = curl_init();
curl_setopt_array($h, [\CURLOPT_URL => $_GET['feed']]);
curl_exec($h);
$page = file_get_contents('http://' . $_GET['mirror'] . '/index.json');
// Not reported: the origin is written in the source.
$fixed = curl_init('https://api.example.test/search?q=' . urlencode($_GET['q']));
curl_exec($fixed);
readfile('/srv/exports/' . $_GET['name']);Reported:
- line 2: Validate the outbound network destination and redirects.
- line 6: Validate the outbound network destination and redirects.
- line 8: Validate the outbound network destination and redirects.
Detection
- Report request-derived destination reaching curl, URL stream reads or equivalent network sinks without a proven outbound destination policy. The sinks are:
curl_execon a handle whose effective URL is request-derived. The URL is thecurl_initargument, replaced in execution order by every dominatingcurl_setopt($h, CURLOPT_URL, …)and by theCURLOPT_URLentry of every dominatingcurl_setopt_arraycall whose option array is a literal. The last such assignment decides, so a later fixed URL clears an earlier request-derived one. An option array that cannot be read as a literal changes nothing.file_get_contents,fopen,readfile,file(the filename) andcopy(the source), when the request can choose a remote stream: either the request-derived value supplies the first bytes of the name, and with them the scheme, or the name starts with a literalhttp://,https://,ftp://orftps://and the origin is not fixed (D4).- a call to a project function whose summary shows the argument reaching
curl_initor aCURLOPT_URLassignment.
- Resolve builtin calls and named arguments before checking the contract. Project-local callees are considered only when their bodies or immutable summaries establish the required facts. The default is enabled, with positive proof required. The option constant is resolved too:
\CURLOPT_URLand an unqualified name falling back to the global constant both count; a namespaced constant with the same short name does not. - For flow-dependent conditions, require a reachable source-to-use path with no intervening invalidation. Unknown calls, ambiguous aliases, recursion or analysis-budget exhaustion cannot establish definite facts.
- D4. The leading operand of a destination is found by looking through parentheses, the left side of concatenations, the first part of an interpolated string and variables with a single reaching value. The origin is fixed when that operand is literal text spelling a scheme,
://, a non-empty authority and the/,?or#that ends it. The closing delimiter is required: without it a later operand could still extend or replace the host. A variable is followed to its reaching assignment only when its scope cannot rebind it behind that assignment; a reference alias, a by-reference closure capture,foreachor argument, acatchbinding orstaticdeclaration of the same name,extract(), an assignment through a variable variable, aglobaldeclaration of it or a$GLOBALSaccess that is not a different literal entry keeps the report.
Project method wrappers are followed only when a single visible declaration is fixed by a final receiver class, a final method, or a private method accessible at the call. Open overridable methods, ambiguous declarations, conditional class bodies and trait bodies do not establish a wrapper sink. Static calls must name a static method. These contracts compose through nested project wrappers and refresh when class or method dispatch metadata changes.
Numeric array values retain their network-destination provenance through wrappers: numerical content alone does not validate an outbound destination.
Exceptions (no report)
- Fixed destinations, policy validating schemes/resolved addresses/redirects, non-network local streams and unknown flows are excluded. In particular:
- a destination with a fixed origin (D4), whatever request data follows in the path, query or fragment;
- a destination that is a
matchexpression (directly or through a variable with a single reaching value) whose every arm is a string literal, a concatenation starting with a fixed origin, athrow, or a nestedmatchof the same shape. Arms are judged syntactically; an arm that is a variable is not proven. Amatchfollowed by a concatenated suffix is not covered; - a stream name whose leading operand is other literal text (a local path, a non-network wrapper), is not request-derived, or cannot contain
://: an integer, float or boolean cast, or a call tointval,floatval,basename,urlencodeorrawurlencode.
- Unresolved targets, incomplete required facts, suppressed findings and PHP versions lacking the referenced API or language feature produce no report. User declarations shadowing builtin names are not builtin contracts.
- Request data that a
switchcompared with constant labels is the matched label inside an arm reached only through string labels that do not read as numbers (not empty and not starting, after whitespace, with a digit, a sign or a dot), and after the switch when every other way out replaced it or left the scope:switch ($sort) { case 'name': case 'date': break; default: $sort = 'name'; }validates$sort. A numeric, empty, boolean or null label, a label expression, a fall-through from another arm, adefaultarm and any other subject prove nothing. The subject is a plain variable or an element of a request superglobal read under one constant key (switch ($_GET['action']), not$_FILES); the element is the matched label only while nothing that may run code is evaluated: a call, an object creation, an assignment, anunset, or the use of a local variable, property, offset of a local or class constant ends it, as does a reference to the superglobal. Aswitch (true)reads each case expression like the condition of anif/elseifchain.
Fix
No automatic fix: outbound allowlists, DNS and redirect handling are deployment-specific.
PHP versions
Apply only on PHP versions supporting the referenced APIs and syntax (within PHP 5.3–8.5). Builtin behavior and argument contracts follow the configured PHP version. Extension-specific calls require resolved extension API symbols; do not treat same-named application functions as extension calls.
Options
None. Enable or disable this rule through normal rule configuration.
Configure
In custos.json:
{
"rules": {
"UntrustedNetworkDestination": {
"enabled": false
}
}
}Suppress
Before the statement or declaration (or the first statement of the file):
// @custos-ignore UntrustedNetworkDestination