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

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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.