phacility/phabricator · error · Exception

URI "%s" is not a valid linkable resource. A valid linkable

Error message

URI "%s" is not a valid linkable resource. A valid linkable resource URI must specify a protocol.

What it means

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.

Source

Thrown at src/infrastructure/env/PhabricatorEnv.php:730


  /**
   * Detect if a URI identifies a valid linkable remote resource, throwing a
   * detailed message if it does not.
   *
   * A valid linkable remote resource can be safely linked or redirected to.
   * This is primarily a protocol whitelist check.
   *
   * @param string URI to test.
   * @return void
   * @task uri
   */
  public static function requireValidRemoteURIForLink($raw_uri) {
    $uri = new PhutilURI($raw_uri);

    $proto = $uri->getProtocol();
    if (!strlen($proto)) {
      throw new Exception(
        pht(
          'URI "%s" is not a valid linkable resource. A valid linkable '.
          'resource URI must specify a protocol.',
          $raw_uri));
    }

    $protocols = self::getEnvConfig('uri.allowed-protocols');
    if (!isset($protocols[$proto])) {
      throw new Exception(
        pht(
          'URI "%s" is not a valid linkable resource. A valid linkable '.
          'resource URI must use one of these protocols: %s.',
          $raw_uri,
          implode(', ', array_keys($protocols))));
    }

    $domain = $uri->getDomain();
    if (!strlen($domain)) {

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Give the URI an explicit allowed scheme: 'https://example.com/page' instead of 'example.com/page' or '//example.com/page'.
  2. If the target is local, skip this validator - it is meant for remote resources; link locally with a relative path at render time instead.
  3. Sanitize user-supplied redirect targets before validation: prepend 'https://' when no scheme is present, or reject early with a form error.

Example fix

// before
PhabricatorEnv::requireValidRemoteURIForLink($next); // $next = '/x' -> throws

// after
$next = PhabricatorURI::normalize($next);
if (!PhabricatorEnv::isValidURIForLink($next)) {
  $next = '/'; // keep navigation local instead of failing
}
// or store the remote target fully qualified:
$uri = 'https://'.$host.$path;
Defensive patterns

Strategy: validation

Validate before calling

$uri = new PhutilURI($candidate);
if (!strlen($uri->getProtocol())) {
  // no scheme: absolutize or reject before calling the validator
  $candidate = 'https://'.ltrim($candidate, '/');
  $uri = new PhutilURI($candidate);
}
PhabricatorEnv::requireValidRemoteURIForLink($candidate);

Type guard

function uriHasProtocol($raw) {
  return strlen((new PhutilURI($raw))->getProtocol()) > 0;
}

Try / catch

try {
  PhabricatorEnv::requireValidRemoteURIForLink($url);
} catch (Exception $ex) {
  // treat as non-linkable: render as plain text, never as <a href>
  return phutil_escape_html($url);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/78cc0e64ecaf06c3. Report an issue: GitHub.