Back to Blog
high SEVERITY8 min read

fast-uri 3.1.2 IDN Host Bypass: CVE-2026-13676 Upgrade

A dependency tree in this repository resolved `fast-uri` 3.1.2, a version affected by CVE-2026-13676, in which improper Unicode hostname canonicalization lets a crafted URI parse to one host while the HTTP client connects to another. Because `fast-uri` is what `ajv` uses to parse and resolve URIs for `format: "uri"` validation and `$ref` resolution, any host allowlist built on its parsed output could be bypassed. The fix upgrades the resolved copy to `fast-uri` 4.1.4 and pins it with an npm `ove

O
By Orbis AppSec
•Published September 29, 2026•Reviewed September 29, 2026

Answer Summary

The npm package `fast-uri` at version 3.1.2 — pulled in transitively through `ajv` in an MCP server's dependency tree — is affected by CVE-2026-13676, a security policy bypass caused by improper Unicode hostname canonicalization. An attacker who controls a URI string can craft an internationalized (IDN) host that `fast-uri.parse()` normalizes to a benign-looking `host`, while `fetch()`/`undici` and the WHATWG URL parser resolve the same string to a different, attacker-chosen host — defeating allowlist and denylist checks built on the parsed value and enabling SSRF-style access to internal endpoints. The fix upgrades the resolved dependency from 3.1.2 to 4.1.4 and adds an npm `overrides` entry so `ajv` can only install the patched parser. No CWE identifier was assigned in the advisory data available for this finding.

Vulnerability at a Glance

cweN/A
fixUpgrade the resolved `fast-uri` to 4.1.4 and pin it via an npm `overrides` entry under `ajv`
riskHost allowlists and denylists built on `fast-uri.parse()` output can be bypassed with a crafted IDN hostname, redirecting requests to an attacker-chosen origin
languageJavaScript / TypeScript (Node.js)
root cause`fast-uri` 3.1.2 normalizes Unicode host components differently from the WHATWG URL/IDNA rules used by Node's HTTP clients
vulnerabilitySecurity policy bypass via improper Unicode hostname canonicalization in a URI parser

Summary

A dependency tree in this repository resolved fast-uri 3.1.2, a version affected by CVE-2026-13676, in which improper Unicode hostname canonicalization lets a crafted URI parse to one host while the HTTP client connects to another. Because fast-uri is what ajv uses to parse and resolve URIs for format: "uri" validation and $ref resolution, any host allowlist built on its parsed output could be bypassed. The fix upgrades the resolved copy to fast-uri 4.1.4 and pins it with an npm overrides entry under ajv so the lockfile cannot drift back to a vulnerable release.

Introduction

fast-uri exists because Node's built-in URL is slow for hot-path validation. It is a hand-written RFC 3986 parser, and ajv — the JSON Schema validator sitting underneath a large fraction of the Node ecosystem, including MCP tool-input validation — delegates to it for parse(), serialize(), resolve(), and equal(). That makes fast-uri a policy component, not just a string utility: whatever it says the host of a URI is, is usually what downstream code trusts.

CVE-2026-13676 is about that trust breaking. In fast-uri 3.1.2 and earlier affected releases, the host component of a URI is canonicalized in a way that does not match the IDNA/ToASCII rules that the WHATWG URL parser — and therefore fetch(), undici, and http.request() with a URL object — applies to the same input. Feed both parsers one string and you can get two different hostnames. Any check of the form "parse the URI, confirm the host is on my allowlist, then request it" becomes a check on a host that is never actually contacted.

This repository's dependency tree resolved 3.1.2 through ajv. The fix bumps the resolved version to 4.1.4 and, importantly, pins it with an overrides block so the vulnerable range cannot come back.

Affected Versions

Affected unknown (this dependency tree resolved 3.1.2, which is affected)
Fixed in unknown as a single canonical value; the PR names 4.0.1, 3.1.3, and 2.4.2 as patched lines and installs 4.1.4
Ecosystem npm
CVE / GHSA CVE-2026-13676 / not assigned
CWE unknown

If your lockfile resolves fast-uri below the patched line for your major version — 2.4.2, 3.1.3, or 4.0.1 — treat yourself as exposed and upgrade.

The Vulnerability Explained

The entire vulnerable "code" in this repository is a resolved version. Here is the change, in prose-safe form — the lockfile entry for fast-uri moved from:

"version": "3.1.2"

to:

"version": "4.1.4"

The bug lives inside that package, in how the authority/host portion of a URI is normalized. fast-uri splits a URI with its own regular-expression-driven scanner and then applies its own normalization to the host: case folding, percent-decoding, and Unicode handling. The WHATWG URL parser does something materially different — it runs the host through IDNA ToASCII, which includes Unicode NFC/IDNA mapping, punycode encoding, and label-separator handling for characters like U+FF0E (fullwidth full stop) and U+3002 (ideographic full stop), both of which are treated as label separators equivalent to ..

That divergence is the vulnerability. When one parser treats a codepoint as an ordinary host character and the other maps it to a dot (or to an ASCII lookalike), the two parsers disagree about where the registrable domain ends.

Attack scenario against this code path

Consider the shape of code that ajv + fast-uri enables in an MCP server that accepts a robot or instrument endpoint as a tool argument:

import { parse } from 'fast-uri'

const ALLOWED = new Set(['robot.internal.lab'])

function guard(uri) {
  const { host } = parse(uri)          // fast-uri 3.1.2 canonicalization
  if (!ALLOWED.has(host)) throw new Error('host not allowed')
  return fetch(uri)                    // WHATWG URL canonicalization
}

The host that parse() returns and the host that fetch() dials are produced by two different canonicalizers. An attacker submits a uri whose authority contains a Unicode label separator or a codepoint that IDNA maps to an ASCII character — for example a fullwidth full stop placed so that fast-uri sees a single opaque label ending in robot.internal.lab, while ToASCII splits it and the effective registrable domain becomes attacker-controlled. parse() reports an allowlisted host; fetch() connects to the attacker's.

The same divergence works in the other direction against a denylist: a URI whose fast-uri host looks like a harmless external domain can resolve to metadata.internal, 169.254.169.254's IDN alias, or an internal control-plane hostname.

For a service that drives laboratory hardware over HTTP, the concrete impact is SSRF with an attacker-selected origin: requests intended for an in-lab instrument are sent to an external host, or requests the policy layer believed were external reach internal endpoints. Secondary impact includes bypassing format: "uri" schema constraints that were written specifically to constrain which hosts a tool may talk to, and $ref resolution in ajv resolving a schema reference against an unexpected authority.

The tell for this whole vulnerability class is two parsers, one string. Nothing in the vulnerable snippet above looks wrong; the flaw is entirely in the assumption that parse(uri).host and the host fetch(uri) uses are the same value.

The Fix

Two changes, both in dependency metadata, and both necessary.

1. The resolved version moved forward. The lockfile entry for fast-uri went from 3.1.2 to 4.1.4, which carries the corrected host canonicalization. The registry URL for that entry also changed from a mirror host back to the public npm registry, which is worth noting only because it means the new artifact is verified against the canonical registry's own metadata rather than a mirror's copy.

2. A scoped overrides entry pins it. Because fast-uri is transitive — it is pulled in by ajv, not declared by this project — bumping the lockfile alone is fragile. Any npm install that re-resolves ajv's dependency range could quietly reintroduce 3.1.2. The manifest gained:

"overrides": {
  "ajv": {
    "fast-uri": "4.1.4"
  }
}

This tells npm: whatever version range ajv asks for, install exactly 4.1.4. It keeps the fix durable across lockfile regeneration without adding fast-uri as a direct dependency that no first-party code imports.

Scoping the override under ajv rather than declaring it at the top level is a deliberate trade-off: it constrains only the parent that is actually known to pull fast-uri in, avoiding surprise resolutions for any unrelated consumer added later. The flip side is that if a different parent package starts depending on fast-uri, that copy is not covered by this override — worth re-checking the resolved tree after adding new dependencies.

Neither change touches application code, and no API migration is required: ajv consumes parse, serialize, resolve, and equal, all of which are stable across the bump. The observable behavioural change is that hostnames containing Unicode that previously canonicalized permissively now canonicalize consistently with IDNA — so a schema or allowlist that used to accept a deceptive IDN host will now reject it. That is the fix working, not a regression.

Key Takeaways

  • fast-uri.parse(uri).host is not guaranteed to be the host your HTTP client dials. Before 4.0.1 / 3.1.3 / 2.4.2 the two canonicalizers diverge on Unicode hosts; if a security decision depends on the host, derive it from the same parser that performs the request (new URL(uri).hostname) and pass the parsed object — not the raw string — onward.
  • fast-uri arrives through ajv, so "I don't use a URI parser" is not a defence. Every format: "uri" constraint and every $ref resolution in your JSON Schema validation runs through it, which includes MCP tool-input schemas.
  • A lockfile bump without an overrides pin is temporary. For a transitive dependency like fast-uri under ajv, only the overrides entry survives a lockfile regeneration.
  • CVE-2026-13676 has three patched lines, not one — 2.4.2, 3.1.3, and 4.0.1. Check which major your tree resolved before assuming you need the 4.x jump.
  • Unicode label separators (U+FF0E, U+3002) and IDNA-mapped codepoints belong in your test fixtures for any allowlist that accepts hostnames from users.

How Orbis AppSec Detected This

  • Source: URI strings supplied by untrusted callers — tool arguments and request fields validated against JSON Schema format: "uri" constraints, and any URI handed to fast-uri's exported parse() / resolve().
  • Sink: fast-uri's host canonicalization inside parse(), whose host output feeds allowlist and denylist decisions that gate outbound HTTP requests through fetch() / undici.
  • Missing control: no guarantee that the host produced by fast-uri 3.1.2 matches the IDNA/ToASCII canonicalization applied by the WHATWG URL parser used at connection time — so a single URI string yields two different hostnames and the policy check is performed on the wrong one.
  • CWE: unknown — no CWE identifier was assigned in the advisory data available for this finding.
  • Fix: the resolved fast-uri was upgraded from 3.1.2 to 4.1.4 and pinned with an npm overrides entry under ajv so the vulnerable range cannot be re-resolved.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2026-13676 is a reminder that URI parsers are security controls. fast-uri was adopted across the ecosystem for speed, and ajv made it a de facto dependency of anything doing JSON Schema validation — so a disagreement between its host canonicalization and IDNA's became a disagreement between "the host I checked" and "the host I connected to." The remediation here is small: the resolved dependency moves from 3.1.2 to 4.1.4, and an overrides entry under ajv makes that pin stick. The larger lesson is architectural — if a hostname gates an outbound request, extract it with the same parser that will perform the request, and test that path with fullwidth and ideographic full stops before an attacker does.

Prevention and further reading

Frequently Asked Questions

Why does the fix add an `overrides` entry under `ajv` instead of adding `fast-uri` as a direct dependency?

`fast-uri` is not used directly by this project's code — it arrives underneath `ajv`, which uses it for URI parsing during JSON Schema validation. The `overrides` entry forces `ajv` to resolve `fast-uri` 4.1.4 without adding an unused direct dependency, and it keeps a future `npm install` from re-resolving the vulnerable 3.1.2 range.

Does moving from fast-uri 3.1.2 to 4.1.4 break `ajv`'s `format: "uri"` validation?

The exported surface `fast-uri` provides to `ajv` — `parse`, `serialize`, `resolve`, `equal` — is unchanged across the major bump, so `ajv` continues to validate `format: "uri"` and resolve `$ref` values normally. What changes is that hostnames containing non-ASCII or Unicode-normalizable characters now canonicalize consistently, so a schema that previously accepted a deceptive IDN host may now reject it.

If my code never calls `fast-uri` directly, am I still exposed to CVE-2026-13676?

Possibly — the risk comes from any decision made on a host value that originated in `fast-uri.parse()`, including `ajv` schema validation of URI fields whose result gates an outbound request. If nothing in your request path compares a `fast-uri`-derived host against an allowlist, the practical impact is low, but the upgrade is cheap and removes the ambiguity.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #36

Related Articles

critical

Cloudflare Worker Reverse Proxy SSRF via String Concatenation

A Cloudflare Worker reverse proxy built its upstream request URL by string-concatenating an environment variable with the incoming request's pathname and search string, letting a crafted path or query redirect requests to attacker-chosen hosts. The fix replaces concatenation with the `URL` object's `pathname` and `search` setters, which normalize input against the configured origin instead of blindly appending it.

high

CVE-2026-42043: axios NO_PROXY Bypass via Malformed URL

CVE-2026-42043 is a high-severity vulnerability in axios where malformed URLs circumvent NO_PROXY environment variable protections, causing internal requests to route through attacker-controlled proxies. The fix upgrades axios from 1.13.6 to 1.18.0, introducing stricter proxy agent handling with `https-proxy-agent` and updated `follow-redirects`.

high

boardcards.js parseArgs() SSRF: --board Flag Reaches Metadata IPs

The `parseArgs()` function in boardcards.js accepted a user-controlled `--board` command-line argument and passed it directly to fetch() calls without validating the URL scheme or hostname. An attacker could supply `http://169.254.169.254/latest/meta-data/` or other internal addresses to extract cloud credentials or scan internal networks.

high

fetch() with Automatic Redirects Leaks HTTPS Trust to api.tomys.top

The `withProxy()` fetch wrapper allowed automatic HTTP redirects from `api.tomys.top`, trusting the external API's domain integrity without validating final destinations. A compromised or malicious redirect response could send users to attacker-controlled phishing sites while maintaining HTTPS protocol and hostname checks that falsely appear secure.

high

Meta.get() Honored @uploadURL From Script Headers: SSRF Fix

A userscript manager's metadata parser accepted the `@uploadURL` directive from a script's own header block, so any installed or auto-updated script could silently redirect the options-page `uploadScript()` fetch to an attacker-chosen destination — including loopback, link-local metadata endpoints, and LAN addresses. The fix makes `Meta.get()` ignore `@uploadURL` when it appears in a script header, leaving the User Metadata field (validated in `Meta.getUserMeta()`) as the only way to set it, and

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.