{"record":{"id":"78cc0e64ecaf06c3","repo":"phacility/phabricator","slug":"uri-s-is-not-a-valid-linkable-resource-a-valid","errorCode":null,"errorMessage":"URI \"%s\" is not a valid linkable resource. A valid linkable resource URI must specify a protocol.","messagePattern":"URI \"(.+?)\" is not a valid linkable resource\\. A valid linkable resource URI must specify a protocol\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/infrastructure/env/PhabricatorEnv.php","lineNumber":730,"sourceCode":"\n\n  /**\n   * Detect if a URI identifies a valid linkable remote resource, throwing a\n   * detailed message if it does not.\n   *\n   * A valid linkable remote resource can be safely linked or redirected to.\n   * This is primarily a protocol whitelist check.\n   *\n   * @param string URI to test.\n   * @return void\n   * @task uri\n   */\n  public static function requireValidRemoteURIForLink($raw_uri) {\n    $uri = new PhutilURI($raw_uri);\n\n    $proto = $uri->getProtocol();\n    if (!strlen($proto)) {\n      throw new Exception(\n        pht(\n          'URI \"%s\" is not a valid linkable resource. A valid linkable '.\n          'resource URI must specify a protocol.',\n          $raw_uri));\n    }\n\n    $protocols = self::getEnvConfig('uri.allowed-protocols');\n    if (!isset($protocols[$proto])) {\n      throw new Exception(\n        pht(\n          'URI \"%s\" is not a valid linkable resource. A valid linkable '.\n          'resource URI must use one of these protocols: %s.',\n          $raw_uri,\n          implode(', ', array_keys($protocols))));\n    }\n\n    $domain = $uri->getDomain();\n    if (!strlen($domain)) {","sourceCodeStart":712,"sourceCodeEnd":748,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/env/PhabricatorEnv.php#L712-L748","documentation":"PhabricatorEnv::requireValidRemoteURIForLink() validates a URI that Phabricator will render as a clickable link or redirect to. The first gate is a protocol check: PhutilURI->getProtocol() must return a non-empty string. A relative URI ('/path'), protocol-relative ('//host/path'), or garbage string yields no protocol and the plain Exception is thrown. This is an anti-open-redirect / anti-javascript-URI measure.","triggerScenarios":"Passing a relative or malformed URI to requireValidRemoteURIForLink() - e.g. an external-link field or redirect target saved as '/jump/to/x' or 'example.com/page' with no scheme; the check runs at read/render time or on save depending on the calling field.","commonSituations":"Users pasting bare domains into link/remarkup external-link fields; imports that stored relative URLs; code feeding request-supplied 'next' parameters into the validator without normalization.","solutions":["Give the URI an explicit allowed scheme: 'https://example.com/page' instead of 'example.com/page' or '//example.com/page'.","If the target is local, skip this validator - it is meant for remote resources; link locally with a relative path at render time instead.","Sanitize user-supplied redirect targets before validation: prepend 'https://' when no scheme is present, or reject early with a form error."],"exampleFix":"// before\nPhabricatorEnv::requireValidRemoteURIForLink($next); // $next = '/x' -> throws\n\n// after\n$next = PhabricatorURI::normalize($next);\nif (!PhabricatorEnv::isValidURIForLink($next)) {\n  $next = '/'; // keep navigation local instead of failing\n}\n// or store the remote target fully qualified:\n$uri = 'https://'.$host.$path;","handlingStrategy":"validation","validationCode":"$uri = new PhutilURI($candidate);\nif (!strlen($uri->getProtocol())) {\n  // no scheme: absolutize or reject before calling the validator\n  $candidate = 'https://'.ltrim($candidate, '/');\n  $uri = new PhutilURI($candidate);\n}\nPhabricatorEnv::requireValidRemoteURIForLink($candidate);","typeGuard":"function uriHasProtocol($raw) {\n  return strlen((new PhutilURI($raw))->getProtocol()) > 0;\n}","tryCatchPattern":"try {\n  PhabricatorEnv::requireValidRemoteURIForLink($url);\n} catch (Exception $ex) {\n  // treat as non-linkable: render as plain text, never as <a href>\n  return phutil_escape_html($url);\n}","preventionTips":["Always store absolute URIs with scheme in link-typed fields; reject bare hosts at the form layer.","Use PhabricatorEnv::isValidURIForLink() for a boolean check when you want graceful degradation.","Feed redirect targets through this validator before issuing redirects - it is your open-redirect guard."],"tags":["phabricator","uri","validation","link","open-redirect","security"],"backgroundTag":"uri-missing-protocol","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}