Back to Blog
high SEVERITY9 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 handling of Unicode hostnames during URI parsing could allow attackers to bypass security policies. By upgrading `fast-uri` from 3.1.2 to 3.1.3 (or 2.4.2 / 4.0.1 depending on the major version in use), the canonicalization logic is corrected to ensure that Unicode hostnames are normalized consistently before any policy checks are applied. This fix matters because URI parsing libraries are foundational co

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-184 / improper input validation) in the `fast-uri` npm package affecting versions prior to 2.4.2, 3.1.3, and 4.0.1. The root cause is improper Unicode hostname canonicalization: when a URI contains an internationalized or Unicode hostname, `fast-uri` failed to normalize it consistently, allowing crafted hostnames to slip past allow-list or block-list checks. The fix is to upgrade `fast-uri` to a patched version (3.1.3 for the 3.x line) and, in monorepos or projects that cannot directly control transitive dependencies, add an `overrides` entry in `package.json` to force the patched version throughout the dependency tree.

Vulnerability at a Glance

cweCWE-184 (Incomplete List of Disallowed Inputs) / CWE-20 (Improper Input Validation)
fixUpgrade fast-uri to 3.1.3 (or 2.4.2 / 4.0.1) and pin the version via package.json overrides
riskAttackers can craft Unicode hostnames that pass allow-list or block-list checks, enabling SSRF, open redirects, or unauthorized resource access
languageJavaScript / Node.js
root causefast-uri did not fully canonicalize Unicode (IDN/Punycode) hostnames before returning parsed URI components, allowing homograph-style bypasses
vulnerabilitySecurity policy bypass via improper Unicode hostname canonicalization

How Unicode Hostname Canonicalization Bypass Happens in Node.js and How to Fix It

The Hidden Risk Inside Your URI Parser

URI parsing sounds like one of the most boring, solved problems in software engineering. Parse a string, hand back a structured object, done. But when internationalized domain names enter the picture, "solved" turns out to be a dangerous assumption — and CVE-2026-13676 in the widely-used fast-uri npm package is a sharp reminder of exactly why.

This post walks through the vulnerability, how it can be exploited, and the precise changes made to close it.


Summary

CVE-2026-13676 is a high-severity security policy bypass in fast-uri, a popular Node.js URI parsing library. Versions prior to 2.4.2, 3.1.3, and 4.0.1 fail to fully canonicalize Unicode hostnames, meaning a carefully crafted internationalized hostname can pass through fast-uri's parser looking like a different string than what security policies expect. The fix is a targeted version upgrade, enforced project-wide via a package.json overrides entry.


Introduction

The package-lock.json file in this repository locked fast-uri at version 3.1.2. That version contains a flaw in how it handles Unicode hostnames — specifically, it does not guarantee that an internationalized hostname is normalized to its canonical ASCII-compatible encoding (ACE / Punycode) form before returning the parsed host component. Any downstream code that uses that host value to make a security decision — an SSRF allow-list check, a redirect validator, an origin policy — is therefore operating on an un-normalized string and can be fooled.

Trivy's static analysis flagged this dependency as matching rule CVE-2026-13676, surfacing the issue before it could be exploited in production.


The Vulnerability Explained

What Is Unicode Hostname Canonicalization?

The Domain Name System only understands ASCII labels. Internationalized domain names (IDNs) — hostnames containing non-ASCII characters like münchen.de or 例え.jp — are encoded into ASCII via the Punycode algorithm before DNS resolution. The ASCII form of münchen.de is xn--mnchen-3ya.de.

A correctly implemented URI parser should normalize both representations to the same canonical form so that security comparisons are deterministic. If a parser returns münchen.de from one call and xn--mnchen-3ya.de from another, any string-equality check against an allow-list will produce inconsistent results.

The Specific Flaw in fast-uri 3.1.2

In fast-uri versions before the patch, the hostname component extracted from a URI was returned in whatever Unicode form it arrived in — it was not consistently converted to Punycode before being handed back to the caller. This means:

// Vulnerable behavior in fast-uri 3.1.2
const { host } = fastUri.parse('http://аррle.com/admin');
// host might be returned as the Unicode string 'аррle.com'
// rather than its Punycode equivalent 'xn--rrle-5cdd.com'

(Note: the Cyrillic characters а and р above are visually identical to the Latin a and p — a classic homograph attack.)

If your application then checks:

const ALLOWED_HOSTS = ['apple.com'];
if (!ALLOWED_HOSTS.includes(parsedUrl.host)) {
  throw new Error('Host not allowed');
}

…the check passes because 'аррle.com' !== 'apple.com' at the byte level, even though both resolve to the same (or a visually indistinguishable) destination. The security policy is bypassed entirely.

Real-World Attack Scenario

Consider a Node.js service that:
1. Accepts a user-supplied URL for a webhook or outbound HTTP request.
2. Uses fast-uri to parse the URL and extract the hostname.
3. Checks the hostname against a block-list of internal IP ranges and sensitive internal services (e.g., metadata.internal, 169.254.169.254).

An attacker supplies a URL whose hostname uses Unicode characters that are visually identical to a blocked hostname but whose Unicode representation does not match the block-list string. fast-uri 3.1.2 returns the un-normalized Unicode form, the block-list check passes, and the service makes an outbound request to the attacker-controlled (or internal) destination — a classic Server-Side Request Forgery (SSRF) enabled by a canonicalization gap.


The Fix

What Changed in the Dependency

The fix is a version bump from fast-uri@3.1.2 to fast-uri@3.1.3. Here is the exact change 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": "3.1.3",
+  "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
+  "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",

The new integrity hash (sha512-i70LwG…) cryptographically pins the resolved tarball, ensuring the patched code — and only the patched code — is installed.

Forcing the Upgrade for Transitive Dependencies

Because fast-uri is often pulled in as a transitive dependency (a dependency of a dependency), a direct version bump alone may not be sufficient. The fix also adds an overrides entry in package.json:

 "overrides": {
   "refractor": "4.8.0",
-  "@opentelemetry/propagator-jaeger": "2.9.0"
+  "@opentelemetry/propagator-jaeger": "2.9.0",
+  "fast-uri": "3.1.3"
 },

The overrides field in npm (v8.3+) instructs the package manager to replace every resolved instance of fast-uri in the dependency tree — regardless of which package requested it — with version 3.1.3. This is the correct pattern when you need to patch a transitive dependency that you do not own directly.

Why the fsevents Change?

The diff also marks fsevents as "dev": true:

 "node_modules/fsevents": {
   "version": "2.3.2",
+  "dev": true,

This is a housekeeping correction that ensures the macOS file-system events native module is not bundled into production artifacts. While unrelated to the CVE, it reduces the production attack surface by eliminating an unnecessary native dependency.

How the Patch Fixes the Root Cause

In fast-uri@3.1.3, hostname canonicalization is applied before the parsed components are returned. Unicode hostnames are converted to their Punycode equivalents, ensuring that any downstream comparison operates on a single, consistent representation. The patched behavior for the homograph example above would be:

// Patched behavior in fast-uri 3.1.3
const { host } = fastUri.parse('http://аррle.com/admin');
// host is now 'xn--rrle-5cdd.com' — the canonical Punycode form
// Block-list check against 'apple.com' correctly passes (different host)
// or fails (if the Punycode form is also blocked)

Security policy checks now receive a deterministic, canonical hostname regardless of how the input was encoded.


Prevention & Best Practices

1. Always Canonicalize Before Comparing

Never compare a hostname extracted from user input directly against an allow-list or block-list without first normalizing it. In Node.js, the built-in URL class performs this normalization:

const url = new URL('http://аррle.com/path');
console.log(url.hostname); // 'xn--rrle-5cdd.com' — already Punycode

For cases where you must use a third-party parser, add an explicit normalization step:

import { toASCII } from 'punycode'; // or use the 'punycode.js' npm package
const normalized = toASCII(parsedHost);

2. Pin Transitive Dependencies with overrides

When a security fix lands in a transitive dependency, use npm's overrides (or Yarn's resolutions) to force the patched version across the entire tree immediately, without waiting for every intermediate package to release an update:

"overrides": {
  "fast-uri": ">=3.1.3"
}

3. Run Dependency Scanners in CI

Tools like Trivy, npm audit, and Snyk can detect known-vulnerable dependency versions before they reach production. Add them as a required CI step:

# Example: fail the build on high or critical findings
trivy fs --exit-code 1 --severity HIGH,CRITICAL .

4. Validate the Full URI, Not Just the Host

SSRF and redirect bypass vulnerabilities often exploit edge cases in URI parsing beyond the hostname — scheme confusion, IPv6 literals, embedded credentials. Use a defense-in-depth approach: validate scheme, normalize host, resolve path, and re-serialize before making any outbound request.

5. Relevant Standards

  • OWASP SSRF Prevention Cheat Sheet — covers allow-listing, DNS rebinding, and canonicalization requirements.
  • CWE-20: Improper Input Validation — the root class for failures to normalize user-supplied data before use.
  • CWE-184: Incomplete List of Disallowed Inputs — applies when a block-list can be bypassed through encoding variations.
  • RFC 5891 (IDNA 2008) — the authoritative specification for internationalized domain name canonicalization.

Key Takeaways

  • fast-uri@3.1.2 (and earlier 2.x / 4.x versions) must not be used in any code path that feeds parsed hostnames into security policy checks — the un-normalized Unicode output is a bypass waiting to happen.
  • The overrides field in package.json is the correct tool for patching transitive dependencies you do not control directly; without it, other packages in the tree can silently re-introduce the vulnerable version.
  • Homograph attacks exploit the gap between visual appearance and byte-level representation — canonicalization to Punycode closes that gap before comparisons are made.
  • A URI parsing library is a security boundary, not just a utility function. Its correctness directly determines whether allow-lists, block-lists, and SSRF defenses hold.
  • Trivy's dependency scanning caught this before any code change was needed — integrating scanner output into automated PR workflows (as done here) compresses the time between vulnerability disclosure and remediation to near-zero.

How Orbis AppSec Detected This

  • Source: User-supplied URI strings entering the application through HTTP request parameters or configuration values that accept webhook/callback URLs.
  • Sink: fast-uri's parse() function returning an un-normalized host component that is subsequently used in allow-list or block-list comparisons within the application's outbound request handling logic.
  • Missing control: No Punycode/ACE canonicalization was applied to the extracted hostname before it was compared against security policy strings, leaving the comparison vulnerable to Unicode homograph variants.
  • CWE: CWE-20 (Improper Input Validation) / CWE-184 (Incomplete List of Disallowed Inputs)
  • Fix: Upgraded fast-uri from 3.1.2 to 3.1.3 in package-lock.json and added "fast-uri": "3.1.3" to the overrides section of package.json to enforce the patched version across all transitive dependents.

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 textbook example of how a subtle implementation gap in a foundational library — one that handles something as routine as parsing a URL — can silently undermine every security control built on top of it. The fast-uri maintainers shipped a targeted fix in patch releases across all supported major versions, and the correct response is to adopt those patches immediately and enforce them throughout the dependency tree.

The broader lesson: treat URI parsing as a security-sensitive operation. Validate inputs, normalize hostnames to their canonical form before any comparison, and keep your dependency scanner in the critical path of every build. A one-line overrides entry and a patch-level version bump are a small price to pay for closing an SSRF or policy-bypass door that might otherwise go unnoticed until it is too late.


References

Frequently Asked Questions

What is a Unicode hostname canonicalization bypass?

It occurs when a URI parser returns different string representations of the same hostname depending on whether Unicode or Punycode encoding is used, allowing crafted hostnames to evade security checks that compare against a known-good list.

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

Always normalize hostnames to their Punycode (ASCII-compatible encoding) form before performing any allow-list or block-list comparison, and use a URI parsing library that guarantees consistent canonicalization.

What CWE is Unicode hostname canonicalization bypass?

It maps primarily to CWE-20 (Improper Input Validation) and CWE-184 (Incomplete List of Disallowed Inputs), because the parser fails to fully validate and normalize the hostname component before it is used in security decisions.

Is an allow-list enough to prevent hostname bypass?

Not on its own — if the URI parser does not canonicalize hostnames before returning them, an attacker can supply a Unicode variant of a blocked hostname that compares as different, bypassing the allow-list entirely.

Can static analysis detect Unicode hostname canonicalization bypass?

Yes. Tools like Trivy (which flagged this exact issue as CVE-2026-13676) scan dependency manifests for known-vulnerable package versions. SAST tools can also flag URI parsing calls that feed directly into security policy checks without a normalization step.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1861

Related Articles

critical

How Server-Side Template Injection happens in Node.js EJS and how to fix it

CVE-2022-29078 is a critical server-side template injection (SSTI) vulnerability in EJS versions prior to 3.1.7, where the `outputFunctionName` option is passed directly into generated code without sanitization, allowing attackers to execute arbitrary JavaScript on the server. The fix upgrades the EJS dependency from 2.7.4 to 3.1.7+ (resolved here as 6.0.1), eliminating the unsafe code generation path. Any Node.js application rendering EJS templates with user-influenced options is at risk of ful

high

How Prototype Pollution happens in JavaScript via defu and how to fix it

CVE-2026-35209 is a high-severity prototype pollution vulnerability in the `defu` JavaScript library (versions prior to 6.1.5), where a crafted `__proto__` key in the defaults argument can corrupt the global Object prototype. The fix upgrades `defu` from 6.1.4 to 6.1.5 in `pnpm-lock.yaml` and enforces the version via a workspace override, closing the attack surface in production code that depends on `defu` for deep object merging.

critical

How eval() Code Injection happens in JavaScript and how to fix it

A critical code injection vulnerability was discovered in `js/lib/jsencrypt.js` at line 195, where a direct `eval()` call executed a JavaScript string shim for the `process` object in browser environments. If an attacker could influence the string passed to `eval()`—through a compromised dependency, a man-in-the-middle attack, or supply chain tampering—they could achieve arbitrary JavaScript execution in any user's browser. The fix replaces the `eval()` call with the equivalent inline JavaScript

high

How Unsafe eval() in JavaScript Happens in React Components and How to Fix It

A high-severity code injection vulnerability was discovered in `TurnPlanner.tsx`, where the `parseInputExpr` function used JavaScript's `Function` constructor — effectively `eval()` — to evaluate user-provided mathematical expressions. The regex guard in place only checked for the presence of arithmetic operators, not whether the input was safe to execute, leaving the door open for arbitrary JavaScript injection. A targeted whitelist fix was applied to reject any input containing characters outs

high

How Prototype Pollution happens in Node.js and how to fix it

A high-severity prototype pollution vulnerability (CVE-2020-8203) was identified in the lodash library via the `zipObjectDeep` function, present as a transitive dependency through postcss in the project's `yarn.lock`. The fix upgrades postcss from 8.5.8 to 8.5.12 using a Yarn resolution override, eliminating the vulnerable lodash code path and reducing the attack surface against crafted CSS input. This change protects the application from object prototype manipulation that could lead to informat

critical

How Prototype Pollution happens in Node.js protobufjs and how to fix it

CVE-2023-36665 is a critical prototype pollution vulnerability in protobufjs that allows attackers to corrupt JavaScript's Object prototype by crafting malicious protobuf messages. The vulnerability existed in protobufjs 6.11.3 and was resolved by upgrading to 6.11.4 (and 7.2.5 for the v7 branch). Applications that parse user-supplied protobuf data are directly at risk of runtime behavior manipulation, privilege escalation, or denial of service.