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 |
Prevention & Best Practices
1. Always canonicalize before comparing hostnames
Never compare a raw URI hostname against a policy list. Normalize first:
const { toASCII } = require('url'); // Node.js built-in
function canonicalHost(url) {
try {
return new URL(url).hostname; // URL constructor applies IDNA normalization
} catch {
throw new Error('Invalid URL');
}
}
The WHATWG URL constructor (built into Node.js ≥ 10) performs full IDNA 2008 canonicalization — use it as your source of truth rather than a third-party parser for security-sensitive hostname comparisons.
2. Pin transitive dependencies with overrides / resolutions
For npm projects use overrides (npm ≥ 8.3):
"overrides": {
"fast-uri": "4.1.2"
}
For Yarn projects use resolutions:
"resolutions": {
"fast-uri": "4.1.2"
}
3. Run a software composition analysis (SCA) tool in CI
Trivy, Snyk, and GitHub Dependabot all scan package-lock.json for known-vulnerable versions. Add one to your CI pipeline so vulnerable transitive dependencies are caught before they reach production:
# Example GitHub Actions step
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
4. Understand the OWASP SSRF and injection guidance
Unicode normalization attacks are specifically called out in the OWASP Server-Side Request Forgery Prevention Cheat Sheet under "Bypasses" — always validate the resolved form of a URL, not the user-supplied string.
Relevant standards
- CWE-183: Permissive List of Allowed Inputs — the policy check operates on a non-canonical form
- CWE-20: Improper Input Validation — the parser does not normalize Unicode hostnames
- RFC 5891: Internationalized Domain Names in Applications (IDNA) 2008
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.
References
- CWE-183: Permissive List of Allowed Inputs
- CWE-20: Improper Input Validation
- OWASP SSRF Prevention Cheat Sheet
- OWASP Input Validation Cheat Sheet
- Node.js WHATWG URL API documentation
- RFC 5891 – Internationalized Domain Names in Applications (IDNA) 2008
- Semgrep rules for URI parsing issues
- fix: upgrade fast-uri to 4.0.1, 3.1.3, 2.4.2 (CVE-2026-13676)