Introduction
In the weDocs repository, a HIGH severity security vulnerability was lurking in an unlikely place: the package-lock.json file. Specifically, the fast-uri dependency at version 3.1.0 contained CVE-2026-13676, a security policy bypass that could allow attackers to circumvent hostname-based access controls through clever Unicode manipulation.
The fast-uri library is a high-performance URI parser used throughout the JavaScript ecosystem, often as a transitive dependency of popular packages like ajv (JSON Schema validator). When parsing internationalized domain names (IDNs), version 3.1.0 failed to properly canonicalize Unicode hostnames—meaning example.com and еxample.com (with a Cyrillic 'е') could be treated as different hosts when they should be recognized as identical.
This matters for any developer building applications with hostname-based security policies, CORS configurations, or allowlist/blocklist logic. The vulnerability was discovered through automated scanning and fixed by upgrading to fast-uri 4.1.2 using npm's overrides feature.
The Vulnerability Explained
The Technical Root Cause
The vulnerability stems from improper Unicode hostname canonicalization in fast-uri 3.1.0. When processing URIs containing internationalized domain names, the library failed to apply proper normalization rules defined in RFC 3490 (IDNA) and RFC 3987 (IRI).
In the vulnerable codebase, package-lock.json locked fast-uri at version 3.1.0:
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
This version, while performant, lacked robust handling of Unicode equivalence in hostnames. Modern browsers and security tools expect canonicalization—the process of converting various Unicode representations into a single standard form.
How the Attack Works
Consider a security policy that blocks requests to evil.com. An attacker could register еvil.com where the first character is U+0435 (Cyrillic small letter IE) instead of U+0065 (Latin small letter E). To the human eye, these appear identical. To a vulnerable URI parser, they might be treated as completely different hosts.
Attack Scenario:
- An application uses fast-uri to parse and validate incoming webhook URLs against an allowlist
- The policy allows
trusted-partner.combut blocksmalicious.com - Attacker submits
https://truѕted-partner.comwith a Cyrillic 'ѕ' (U+0455) instead of Latin 's' (U+0073) - fast-uri 3.1.0 fails to canonicalize this to punycode (
xn--truted-partner-...) - The allowlist check passes (string doesn't match
malicious.com) - The request proceeds to the attacker-controlled server
- Sensitive webhook data is exfiltrated
Real-World Impact
For weDocs and similar applications, this vulnerability creates risks in:
- CORS policy enforcement: Bypassing origin restrictions
- SSRF protection: Circumventing hostname-based blocklists
- Redirect validation: Tricking URL validators into approving malicious destinations
- Content Security Policy: Evading connect-src directives
The Trivy scanner flagged this with rule CVE-2026-13676, noting the dependency was "present in dependency tree, not confirmed reachable"—a common scenario where transitive dependencies carry risk even if not directly invoked by application code.
The Fix
Specific Changes Made
The remediation involved two coordinated changes across package.json and package-lock.json:
1. package.json: Adding npm Overrides
--- a/package.json
+++ b/package.json
@@ -59,5 +59,8 @@
"react-responsive-carousel": "^3.2.23",
"react-router-dom": "^6.6.2",
"sweetalert2": "^11.7.1"
+ },
+ "overrides": {
+ "fast-uri": "4.1.2"
}
}
The overrides field (npm 8.3+) forces all dependencies in the tree to use fast-uri 4.1.2, regardless of their individual version requirements. This is crucial because fast-uri is often a transitive dependency—direct dependencies might specify ^3.0.0, but the override ensures the patched version wins.
2. package-lock.json: Version Lock Update
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "weDocs",
- "version": "2.3.0",
+ "version": "2.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "weDocs",
- "version": "2.3.0",
+ "version": "2.4.1",
"license": "GPL",
"dependencies": {
"@dnd-kit/core": "^6.0.7",
@@ -15629,9 +15629,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
- "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.2.tgz",
+ "integrity": "sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==",
"funding": [
{
"type": "github",
The lockfile update at line 15632 in package-lock.json (shown in the diff context) ensures reproducible builds use the secure version. The integrity hash changes from sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== to sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==, cryptographically verifying the package content.
Why This Fix Works
fast-uri 4.1.2 implements proper IDNA2008 (Internationalized Domain Names for Applications) canonicalization:
- Unicode normalization: Applies NFKC normalization to decompose compatibility characters
- Punycode conversion: Transforms Unicode hostnames to ASCII-compatible encoding (
xn--...) - Bidirectional character handling: Rejects characters that could enable homograph attacks
- Contextual rules: Enforces protocol-appropriate character restrictions
By forcing version 4.1.2 through overrides, the fix ensures consistent canonicalization across all code paths that parse URIs, eliminating the attack surface for Unicode-based policy bypasses.
Prevention & Best Practices
Dependency Management Strategies
1. Use npm overrides for security-critical transitive dependencies
{
"overrides": {
"vulnerable-package": "secure-version"
}
}
2. Enable automated vulnerability scanning in CI/CD
- Integrate Trivy, Snyk, or npm audit in your build pipeline
- Fail builds on HIGH/CRITICAL CVEs in dependencies
3. Maintain accurate lockfiles
- Commit package-lock.json to version control
- Review lockfile changes in pull requests for unexpected version downgrades
URI Handling Best Practices
Validate after canonicalization: Always apply security checks after full URI parsing and normalization:
// ❌ Vulnerable: string comparison before canonicalization
if (url.includes('trusted.com')) { /* ... */ }
// ✅ Safe: parse and canonicalize first
const parsed = new URL(url);
if (parsed.hostname === 'trusted.com') { /* ... */ }
Use defense in depth for hostname validation:
1. Parse with a robust library (fast-uri 4.x, WHATWG URL API)
2. Convert to punycode for storage/comparison
3. Apply additional allowlist checks on the canonical form
4. Consider visual similarity detection for high-risk applications
Detection Tools
| Tool | Rule/Feature | Purpose |
|---|---|---|
| Trivy | CVE-2026-13676 |
Detects vulnerable fast-uri versions in lockfiles |
| npm audit | Built-in | Flags known vulnerabilities in dependencies |
| Semgrep | javascript.lang.security.audit |
Custom rules for URI validation patterns |
| OWASP ZAP | Active scanner | Runtime detection of IDN-based attacks |
Key Takeaways
-
fast-uri 3.1.0's Unicode handling was insufficient for security-critical hostname comparisons—the library prioritized speed over correctness for internationalized domains
-
npm overrides provide surgical precision for transitive dependency fixes—without waiting for upstream packages to update their requirements, you can force secure versions immediately
-
The
package-lock.jsonchange at line 15632 was as important as thepackage.jsonoverride—lockfiles ensure reproducible, auditable builds across environments -
"Not confirmed reachable" does not mean "not exploitable"—transitive dependencies in popular parser libraries often process untrusted input indirectly through API boundaries
-
Version 2.4.1 of weDocs now enforces IDNA2008-compliant canonicalization—all Unicode hostnames are normalized to punycode before security policy evaluation
How Orbis AppSec Detected This
Source: The vulnerability originated from user-influenced URI input that flows through the application's dependency tree, potentially reaching fast-uri.parse() or related methods via transitive dependencies like ajv.
Sink: The vulnerable code path was fast-uri version 3.1.0 in node_modules/fast-uri as locked in package-lock.json:15632, specifically the hostname parsing logic that failed to apply proper Unicode canonicalization.
Missing control: The dependency lacked proper IDNA2008-compliant Unicode normalization, allowing semantically equivalent hostnames with different Unicode representations to bypass string-based security comparisons.
CWE: CWE-115: Misinterpretation of Input — specifically, the failure to convert input to a canonical form before validation.
Fix: The automated PR upgraded fast-uri to 4.1.2 using npm's overrides mechanism, ensuring all transitive dependencies use the patched version with proper Unicode hostname canonicalization.
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 demonstrates how even "invisible" infrastructure—transitive dependencies deep in package-lock.json—can harbor significant security risks. The fast-uri vulnerability reminds us that Unicode handling is a security-critical concern, not merely an internationalization feature.
The weDocs fix showcases modern dependency management best practices: using npm overrides to force security updates without waiting for upstream maintainers, and maintaining precise lockfile control for reproducible, auditable builds.
For developers building URI validation logic, the lesson is clear: always canonicalize before comparing, and keep your parsing libraries current. The difference between 3.1.0 and 4.1.2 isn't just version numbers—it's the difference between a bypassable security control and robust protection against homograph attacks.