The Vulnerability at a Glance
| Field | Detail |
|---|---|
| CVE | CVE-2026-13676 |
| Severity | High |
| Package | fast-uri |
| Affected versions | < 2.4.2, < 3.1.3, < 4.0.1 |
| Fixed versions | 2.4.2, 3.1.3, 4.0.1 (pinned to 4.1.2 in this PR) |
| CWE | CWE-183 – Permissive List of Allowed Inputs |
| Root cause | Hostnames containing Unicode characters were not canonicalized before policy evaluation |
Introduction
The package-lock.json file in this project locked fast-uri at version 3.1.2 — a version that contains a subtle but dangerous flaw: when a URI containing a Unicode hostname is parsed, the library returns the hostname in its raw Unicode form rather than its canonical ASCII-compatible encoding (ACE / Punycode). Any security policy — an allowlist, a blocklist, an SSRF guard — that relies on the parsed hostname string for its decision is therefore operating on an un-normalized value that may not match what the underlying network stack ultimately connects to.
This is the exact pattern that enables CVE-2026-13676: an attacker supplies a hostname like аpple.com (Cyrillic а, U+0430, instead of Latin a, U+0061). The raw string does not match apple.com in a byte-for-byte comparison, so an allowlist that only permits apple.com rejects it — or, more dangerously, a blocklist that forbids apple.com passes it — while the DNS resolver happily maps it to the same IP address.
The Vulnerability Explained
What is Unicode hostname canonicalization?
Every public domain name can be expressed in two equivalent forms:
- Unicode label — human-readable:
münchen.de - ACE / Punycode label — ASCII-safe:
xn--mnchen-3ya.de
The process of converting between these forms is called IDNA canonicalization (Internationalized Domain Names in Applications, RFC 5891). A URI parser that returns the Unicode form without first converting it to its canonical ACE form breaks the fundamental assumption that "equal strings mean equal hostnames."
The vulnerable code pattern
Before the fix, package-lock.json pinned fast-uri at 3.1.2:
"node_modules/fast-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="
}
In this version, the hostname component extracted from a parsed URI is returned verbatim. Consider this illustrative usage pattern common in Node.js services:
const { parse } = require('fast-uri');
const ALLOWED_HOSTS = new Set(['api.example.com']);
function isSafeRedirect(url) {
const { host } = parse(url);
// host may be "аpi.example.com" (Cyrillic а) — NOT in ALLOWED_HOSTS
// but the network will resolve it identically to "api.example.com"
return ALLOWED_HOSTS.has(host);
}
The check ALLOWED_HOSTS.has(host) fails for the Cyrillic variant, so isSafeRedirect returns false — meaning an attacker can bypass a blocklist by passing a Unicode lookalike, or bypass an allowlist by encoding a permitted host in a way the parser doesn't normalize.
Attack scenario
Imagine an SSRF protection layer that blocks requests to internal metadata endpoints:
const BLOCKED_HOSTS = new Set([
'169.254.169.254', // AWS metadata
'metadata.google.internal'
]);
function fetchExternal(url) {
const { host } = parse(url); // fast-uri 3.1.2
if (BLOCKED_HOSTS.has(host)) throw new Error('Blocked');
return fetch(url);
}
An attacker submits a URL where metadata.google.internal is encoded using visually identical Unicode characters. The raw host string returned by fast-uri 3.1.2 doesn't match the blocked string, so the check passes. The fetch() call resolves the hostname through the system DNS resolver, which does apply IDNA normalization, and the request reaches the metadata endpoint — leaking cloud credentials.
The Fix
The PR makes two coordinated changes to fully remediate CVE-2026-13676.
Change 1: Upgrade fast-uri in package-lock.json
"node_modules/fast-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
- "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.2.tgz",
+ "integrity": "sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==",
Version 4.1.2 (which supersedes the originally targeted 4.0.1) includes the IDNA normalization fix: hostnames are converted to their canonical ACE form before being returned, so the string аpi.example.com (Cyrillic) becomes xn--pi-9ed.example.com — which correctly does not match api.example.com.
Change 2: Pin the version with npm overrides in package.json
- "devDependencies": {}
+ "devDependencies": {},
+ "overrides": {
+ "fast-uri": "4.1.2"
+ }
This is the more important change for long-term security. Without an overrides entry, any transitive dependency that declares "fast-uri": "^3.0.0" or "fast-uri": ">=2.0.0" in its own package.json could cause npm to install a vulnerable version alongside the patched one. The overrides directive tells npm: regardless of what any nested dependency requests, always resolve fast-uri to exactly 4.1.2.
Why both changes are necessary
| Change | What it does | Without it |
|---|---|---|
package-lock.json upgrade |
Installs the patched version today | The vulnerable version remains installed |
package.json overrides |
Prevents re-introduction via transitive deps | A future npm install or dep update could pull in 3.1.2 again |
Key Takeaways
fast-uri< 3.1.3 / < 4.0.1 returns raw Unicode hostnames — any security policy that uses the parsedhostfield for allowlist/blocklist decisions is vulnerable to bypass via Unicode lookalike characters.- Upgrading
package-lock.jsonalone is not enough — transitive dependencies can re-introduce the vulnerable version; the"overrides"entry inpackage.jsonis what makes the fix durable. - The WHATWG
URLconstructor is safer than third-party parsers for security-sensitive hostname extraction — it applies IDNA normalization by design. - Trivy caught this in
package-lock.jsonbefore runtime — SCA scanning of lockfiles is the right place to detect CVEs in transitive dependencies, where manual review rarely reaches. - Unicode homoglyph attacks are not theoretical — the same normalization gap that enables this bypass is used in real-world phishing and SSRF campaigns; treat hostname comparison as a canonicalization problem, not a string-equality problem.
How Orbis AppSec Detected This
- Source: User-controlled URL strings passed to
fast-uri'sparse()function, where thehostcomponent is extracted and used in security policy evaluation. - Sink: Any comparison of the raw
hostvalue returned byfast-uri 3.1.2against an allowlist or blocklist — the non-canonicalized Unicode string is the dangerous value. - Missing control: IDNA/Punycode normalization of the hostname before policy comparison. The library returned the Unicode label directly instead of converting it to its canonical ACE form.
- CWE: CWE-183 – Permissive List of Allowed Inputs (the policy list is compared against a non-canonical representation of the input).
- Fix:
fast-uriwas upgraded to4.1.2inpackage-lock.jsonand pinned via an"overrides"entry inpackage.jsonto prevent transitive re-introduction of the vulnerable version.
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 parsing is not a solved problem — the gap between what a string looks like and what a hostname resolves to is a persistent source of security bugs. The fast-uri library's failure to canonicalize Unicode hostnames before returning them created a silent bypass for any downstream security policy that treated the parsed host as authoritative.
The two-part fix — upgrading the library and pinning it with overrides — demonstrates the right approach to dependency security: patch the immediate vulnerability, then close the door on its re-introduction. Pair that with IDNA-aware hostname comparison in your own code, SCA scanning in CI, and a clear understanding of how DNS resolvers normalize internationalized names, and this class of bypass becomes much harder to land.