denoland/deno · error · TypeError

ERR_INVALID_URL

ERR_INVALID_URL

Error message

Invalid URL: ${path}

What it means

When an HTTP proxy is configured, Deno rewrites the request path to absolute-form for the proxy hop (RFC 9112 section 3.2.2). buildProxyRequestTarget rejects an options.path that is itself an absolute URL aimed at a different authority than the host that passed the --allow-net permission check, or one embedding userinfo (user:pass@host). This fails closed because an absolute path to another host would retarget the proxied request past the permission check.

Solutions

  1. Keep the target authority in options.host and the path origin-form: '/api/x?y=1'
  2. Move credentials out of the path into options.auth or the Authorization header
  3. If no proxy was intended, unset HTTP_PROXY/HTTPS_PROXY (and no_proxy as needed) for that request

Example fix

// before (with HTTP_PROXY set)
http.request({ host: 'api.example.com', path: 'http://api.example.com/v1/x' }, cb);
// OK only if identical authority; throws when the embedded host differs or has userinfo

// after
http.request({ host: 'api.example.com', path: '/v1/x' }, cb);
Defensive patterns

Strategy: validation

Validate before calling

function assertProxySafePath(path, host) {
  let u;
  try { u = new URL(path); } catch { return; } // origin-form is fine
  if (u.host !== host || u.username || u.password) {
    throw new Error(`path '${path}' retargets the proxied request away from ${host}`);
  }
}

Try / catch

try { http.request({ host, path }, cb); }
catch (err) {
  if (err.code === 'ERR_INVALID_URL' && process.env.HTTP_PROXY) {
    // strip scheme/userinfo from path, keep only pathname+search
    const u = new URL(path);
    return http.request({ host, path: u.pathname + u.search }, cb);
  }
  throw err;
}

Prevention

When it happens

Trigger: HTTP(S)_PROXY environment variable or proxy agent set, plus http.request({ host: 'a.com', path: 'http://other.com/x' }) or path: 'http://user:pass@a.com/x'. The absolute path's host must equal the validated host and carry no credentials.

Common situations: Code that passes full URLs in options.path (which appears to work without a proxy); credential-bearing URLs moved into the path 'to keep them out of logs'; enabling a corporate proxy in CI and suddenly hitting this on existing request builders.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/9cca0b286898ab5d. Report an issue: GitHub.

Appendix: source

Thrown at ext/node/polyfills/_http_client.js:492

  // An absolute-form path is already a complete target. It is only honored
  // when its authority matches the one that `--allow-net` validated and it
  // carries no userinfo: the permission check upstream sees only `host` and
  // `port`, so an absolute `path` aimed at another authority (or embedding
  // credentials for one) would otherwise retarget the proxied request past
  // that check. Fail closed instead - like a denied direct connection.
  let absolute = null;
  try {
    absolute = new URL(path);
  } catch {
    // Not absolute-form; resolved as an origin-form path below.
  }
  if (absolute !== null) {
    if (
      absolute.host !== base.host ||
      absolute.username !== "" ||
      absolute.password !== ""
    ) {
      throw new ERR_INVALID_URL(path);
    }
    return absolute.href;
  }

  try {
    return new URL(base.origin + path).href;
  } catch {
    const portSuffix = +port === 80 ? "" : `:${port}`;
    return `http://${authorityHost}${portSuffix}${path}`;
  }
}

function ClientRequest(input, options, cb) {
  FunctionPrototypeCall(OutgoingMessage, this);

  if (typeof input === "string") {
    const urlStr = input;
    // Match Node: `new URL(...)` in ClientRequest surfaces as

View on GitHub (pinned to 9ad36f7a2c)