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.


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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1861

Related Articles

critical

LDAP Filter Injection in da_unique_email_validator Fixed

The registration-time email uniqueness validator, `da_unique_email_validator`, formatted the submitted email address straight into an LDAP search filter with Python's `%` operator, so filter metacharacters in the email were interpreted as filter syntax. The fix wraps the value in `ldap.filter.escape_filter_chars()` (and imports the `ldap.filter` submodule explicitly), so a submitted address is always treated as a literal attribute value. Any deployment with `ldap login` enabled and a bind accoun

high

installPlugin(): Unvalidated npm Package Names Reach npm install

A plugin manager service exposed an `installPlugin(plugin: PluginInfo)` method that passed `plugin.packageName` and `plugin.version` straight into the platform's npm install routine with no validation, no blocklist, and no integrity verification of the fetched tarball. Because npm treats a non-semver "version" as a fetch specifier — a tarball URL, a git ref, a local path — an attacker who could influence the plugin listing could get arbitrary code installed and executed with full Electron/Node p

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.

critical

eval() in Async Function Constructor Enables Runtime Escape

The eval.mjs command handler used raw `eval()` to execute JavaScript expressions, creating a critical code injection path if owner credentials are compromised. The fix replaces `eval()` with the `AsyncFunction` constructor and explicitly shadows `process`, `require`, and other runtime globals as parameters, preventing evaluated code from reaching the Node.js runtime even when authentication boundaries fail.

high

How Regular Expression Denial of Service (ReDoS) Happens in Node.js trim-newlines and How to Fix It

CVE-2021-33623 exposed a Regular Expression Denial of Service (ReDoS) vulnerability in the npm package `trim-newlines` versions 1.0.0 and earlier. The vulnerable `.end()` method used an inefficient regex pattern that could cause severe performance degradation when processing malicious input. Upgrading to version 4.0.1 patches the regex implementation and eliminates the attack surface.

critical

How CSS Injection via Weak Pattern Validation happens in Vue.js and how to fix it

A critical CSS injection vulnerability in `testpage/App.vue` allowed attackers to bypass weak HTML5 pattern validation and load malicious stylesheets. The fix replaces direct variable assignment with a hardened `setCustomStylesheetHref()` method using strict regex validation.