Back to Blog
high SEVERITY8 min read

How Unicode Hostname Canonicalization Bypass happens in Node.js and how to fix it

CVE-2026-13676 is a high-severity vulnerability in the `fast-uri` npm package where improper Unicode hostname canonicalization allowed attackers to bypass security policies by crafting hostnames that appeared safe but resolved differently after normalization. The fix upgrades `fast-uri` from version 3.1.2 to 4.1.2 and pins the version using an npm `overrides` directive in `package.json` to ensure no transitive dependency pulls in the vulnerable version.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2026-13676 is a high-severity security policy bypass vulnerability (CWE-183) in the `fast-uri` npm package (Node.js) caused by improper Unicode hostname canonicalization. An attacker can supply a Unicode hostname that passes URI validation but resolves to a different, restricted host after normalization — effectively bypassing allowlists or blocklists. The fix is to upgrade `fast-uri` to version 4.1.2 (or 3.1.3 / 2.4.2 for older branches) and add an npm `overrides` entry in `package.json` to pin the dependency across the entire tree.

Vulnerability at a Glance

cweCWE-183 (Permissive List of Allowed Inputs)
fixUpgrade fast-uri to 4.1.2 and pin with npm overrides to prevent transitive re-introduction of the vulnerable version
riskAttackers craft Unicode hostnames that pass URI validation but resolve to blocked/internal hosts after normalization
languageJavaScript / Node.js
root causefast-uri 3.1.2 did not fully canonicalize Unicode hostnames before comparing them against security policies
vulnerabilitySecurity policy bypass via Unicode hostname canonicalization

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:

  1. Unicode label — human-readable: münchen.de
  2. 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 parsed host field for allowlist/blocklist decisions is vulnerable to bypass via Unicode lookalike characters.
  • Upgrading package-lock.json alone is not enough — transitive dependencies can re-introduce the vulnerable version; the "overrides" entry in package.json is what makes the fix durable.
  • The WHATWG URL constructor is safer than third-party parsers for security-sensitive hostname extraction — it applies IDNA normalization by design.
  • Trivy caught this in package-lock.json before 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's parse() function, where the host component is extracted and used in security policy evaluation.
  • Sink: Any comparison of the raw host value returned by fast-uri 3.1.2 against 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-uri was upgraded to 4.1.2 in package-lock.json and pinned via an "overrides" entry in package.json to 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

Frequently Asked Questions

What is a Unicode hostname canonicalization bypass?

It is a class of vulnerability where a URI parser accepts a hostname containing Unicode characters (e.g., homoglyphs or mixed-script characters) that look different from, but ultimately resolve to, a restricted hostname — allowing an attacker to slip past allowlists or blocklists.

How do you prevent Unicode hostname canonicalization bypass in Node.js?

Use a URI parsing library that normalizes hostnames to their canonical ASCII/IDNA form before any policy check, and pin your dependency versions with npm `overrides` or `resolutions` to prevent transitive upgrades pulling in a vulnerable version.

What CWE is Unicode hostname canonicalization bypass?

CWE-183 (Permissive List of Allowed Inputs) — the validation logic accepts inputs that should be blocked because it does not reduce them to a canonical form first.

Is input validation alone enough to prevent this bypass?

No. Validation must be performed on the *canonicalized* form of the hostname. Validating the raw Unicode string before normalization is precisely what makes this class of bypass possible.

Can static analysis detect Unicode hostname canonicalization bypass?

Yes — tools like Trivy (which flagged this exact issue) can match known-vulnerable package versions in `package-lock.json`. SAST tools can also flag URI parsing calls that compare hostnames without prior normalization.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

high

How Security Policy Bypass via Improper Unicode Hostname Canonicalization Happens in Node.js and How to Fix It

A high-severity vulnerability (CVE-2026-13676) in the `fast-uri` npm package allowed attackers to bypass security policies through improper Unicode hostname canonicalization. The fix upgrades `fast-uri` from version 3.1.0 to 4.1.2 using npm overrides to ensure the patched version is used throughout the entire dependency tree of the `ide-agent-kit` project.

high

How ReDoS happens in Node.js path-to-regexp and how to fix it

CVE-2024-52798 is a Regular Expression Denial of Service (ReDoS) vulnerability in the `path-to-regexp` package's 0.1.x branch, which remains unpatched in that legacy line. Because `path-to-regexp` is a transitive dependency pulled in by `websocket-driver` and many other popular Node.js packages, any application that processes attacker-controlled URL paths through an affected version is at risk of catastrophic backtracking that can freeze the event loop. Upgrading `websocket-driver` to 0.7.5 — an

high

How Denial of Service via Crafted Long-Path Tar Archives Happens in Node.js and How to Fix It

CVE-2026-73566 is a Denial of Service vulnerability in node-tar that allows attackers to craft specially malformed tar archives with excessively long file paths to exhaust system resources and crash applications. The fix upgrades tar from version 7.5.19 to 7.5.21, which implements proper path length validation to prevent this attack vector.

high

How Denial of Service via Memory Exhaustion happens in Socket.IO Parser and how to fix it

CVE-2026-69185 is a high-severity Denial of Service vulnerability in the `socket.io-parser` package that allows attackers to exhaust server memory by sending specially crafted packets. The fix upgrades `socket.io-parser` from version 4.2.4 to 4.2.7 (and parallel branches to 3.4.5 and 3.3.6) in `client/package-lock.json`, closing the attack surface against malicious clients. This kind of memory-exhaustion flaw is particularly dangerous in real-time applications where the parser handles a continuo

high

How express-check-csurf-middleware-usage happens in JavaScript/Express and how to fix it

A high-severity CSRF vulnerability was identified in `tower_game/index.js` where the Express application lacked any Cross-Site Request Forgery protection middleware. Without CSRF validation, an attacker could craft malicious pages that trick authenticated users into submitting unwanted requests to the game server. The fix adds `csurf` middleware with cookie-based token storage in just four lines of code.

high

How Quadratic CPU Consumption Happens in JavaScript YAML Parsing and How to Fix It

A high-severity denial-of-service vulnerability in js-yaml (GHSA-5p4m-2wfm-xmqj) allowed attackers to trigger quadratic CPU consumption by supplying crafted YAML input containing `!!omap` (ordered map) types. The vulnerability affected both the 3.x and 4.x branches of js-yaml, and the fix for CVE-2026-59870 had not been backported to all affected versions. Upgrading from `js-yaml@4.3.0` to `4.3.1` (and `3.15.0` to `3.15.1`) resolves the issue by correcting the inefficient duplicate-key detection