{"record":{"id":"a90df988434f6f39","repo":"phacility/phabricator","slug":"uri-s-is-not-a-valid-fetchable-resource-the-do-a90df9","errorCode":null,"errorMessage":"URI \"%s\" is not a valid fetchable resource. The domain \"%s\" resolves to the address \"%s\", which is blacklisted for outbound requests.","messagePattern":"URI \"(.+?)\" is not a valid fetchable resource\\. The domain \"(.+?)\" resolves to the address \"(.+?)\", which is blacklisted for outbound requests\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/infrastructure/env/PhabricatorEnv.php","lineNumber":835,"sourceCode":"        pht(\n          'URI \"%s\" is not a valid fetchable resource. A valid fetchable '.\n          'resource URI must specify a domain.',\n          $raw_uri));\n    }\n\n    $addresses = gethostbynamel($domain);\n    if (!$addresses) {\n      throw new Exception(\n        pht(\n          'URI \"%s\" is not a valid fetchable resource. The domain \"%s\" could '.\n          'not be resolved.',\n          $raw_uri,\n          $domain));\n    }\n\n    foreach ($addresses as $address) {\n      if (self::isBlacklistedOutboundAddress($address)) {\n        throw new Exception(\n          pht(\n            'URI \"%s\" is not a valid fetchable resource. The domain \"%s\" '.\n            'resolves to the address \"%s\", which is blacklisted for '.\n            'outbound requests.',\n            $raw_uri,\n            $domain,\n            $address));\n      }\n    }\n\n    $resolved_uri = clone $uri;\n    $resolved_uri->setDomain(head($addresses));\n\n    return array($resolved_uri, $domain);\n  }\n\n\n  /**","sourceCodeStart":817,"sourceCodeEnd":853,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/env/PhabricatorEnv.php#L817-L853","documentation":"Final gate of requireValidRemoteURIForFetch(): after DNS resolution, every returned address is checked against the outbound blacklist (isBlacklistedOutboundAddress, driven by `phabricator.serious-business`-adjacent security config - concretely the outbound-address blacklist covering loopback, private ranges, link-local, etc.). If any A/AAAA record falls in a blacklisted range, the fetch is refused. This is Phabricator's core SSRF defense: it stops users from making the server fetch 127.0.0.1, 169.254.169.254 (cloud metadata), or RFC1918 internals via a DNS name.","triggerScenarios":"requireValidRemoteURIForFetch() on a hostname that resolves (or multi-homed resolves partly) to 127.0.0.1, 10.x, 192.168.x, 169.254.169.254, ::1, or any range in the configured outbound blacklist - e.g. a developer pointing an image/import URL at 'localhost.attacker.com' which DNS-maps to 127.0.0.1.","commonSituations":"Legitimate intranet fetches (Gravatar-on-prem mirrors, internal CI links) blocked because the target lives in RFC1918 space; post-exploitation or security scans testing SSRF; split-horizon DNS where the public name resolves internally to a private IP from the Phabricator host's view.","solutions":["If the fetch is genuinely internal and trusted, adjust the outbound address blacklist config (outbound address blacklist keys in cluster/security settings) to permit the specific internal range - narrow it to the exact CIDR needed, never disable the whole blacklist.","Otherwise point the URL at a genuinely public host.","Never work around by pre-resolving and passing an IP - the same per-address check runs on every resolved address."],"exampleFix":"# before: hostname resolves to a blacklisted private address\n$ getent hosts ci.internal\n10.0.0.15      ci.internal\n# fetch of https://ci.internal/... throws: blacklisted for outbound requests\n\n# after: whitelist the exact internal range (deliberate, narrow change)\n$ ./bin/config set cluster.addresses ...   # unrelated\n# adjust the outbound blacklist to exclude 10.0.0.15/32 only, keep the rest","handlingStrategy":"try-catch","validationCode":"$domain = (new PhutilURI($url))->getDomain();\nforeach ((array) gethostbynamel($domain) as $addr) {\n  if (PhabricatorEnv::isBlacklistedOutboundAddress($addr)) {\n    // resolves to a private/blacklisted range: refuse before fetching\n    return array('err' => 'Blocked outbound address');\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  PhabricatorEnv::requireValidRemoteURIForFetch($url, array('http', 'https'));\n} catch (Exception $ex) {\n  if (strpos($ex->getMessage(), 'blacklisted for outbound') !== false) {\n    // deliberate SSRF block: report, never bypass by proxying elsewhere\n    phlog(pht('Blocked SSRF attempt: %s', $url));\n    return new Aphront403Response();\n  }\n  throw $ex;\n}","preventionTips":["Never disable the outbound blacklist to make a fetch work; whitelist the narrowest CIDR instead.","When allowing internal fetches, also require authentication on the internal target.","Audit user-supplied fetch URLs periodically for DNS names mapping into private space."],"tags":["phabricator","ssrf","dns","security","fetch","blacklist","private-network"],"backgroundTag":"ssrf-private-address-blocked","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}