Back to Blog
high SEVERITY5 min read

How Infinite Loop DoS happens in Node.js ID generation and how to fix it

A critical vulnerability in nanoid versions 3.3.16 and below allowed attackers to trigger infinite loops during random ID generation, causing complete CPU exhaustion and denial of service. The fix upgrades to nanoid 3.3.18, which patches the underlying random number generation flaw that could freeze Node.js applications processing untrusted input.

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

Answer Summary

CVE-2026-67213 is a denial-of-service vulnerability in nanoid's random ID generation that causes infinite loops in versions 3.3.16 and below. This Node.js/npm dependency vulnerability (CWE-835) triggers CPU exhaustion when malformed or edge-case input reaches the nanoid generator. The fix upgrades nanoid to 3.3.18 via package.json overrides, patching the random number generation algorithm to prevent infinite iteration while maintaining backward compatibility for all valid ID generation calls.

Vulnerability at a Glance

cweCWE-835 (Loop with Unreachable Exit Condition)
fixUpgrade nanoid to 3.3.18 via package.json dependency override
riskComplete CPU exhaustion and application freeze from single malicious request
languageJavaScript/Node.js
root causeUnbounded iteration in nanoid's random byte generation when edge-case conditions occur
vulnerabilityDenial of Service via Infinite Loop (CVE-2026-67213)

Introduction

In a routine security scan of a production JavaScript application, Trivy's static analyzer flagged a concerning pattern in package-lock.json: nanoid version 3.3.16 contained CVE-2026-67213, a high-severity denial-of-service vulnerability. While the scanner noted the vulnerability was "present in dependency tree, not confirmed reachable," the potential for exploitation warranted immediate attention. The issue wasn't in application code—it was hiding in a widely-used ID generation library that processes untrusted input across millions of Node.js applications.

The vulnerability stems from nanoid's core random ID generation algorithm. When specific edge-case conditions occur during random byte generation, the library's internal loop fails to terminate, causing infinite iteration and complete CPU exhaustion. For applications using nanoid to generate IDs from user-influenced parameters—session tokens, file names, or database keys—this represents a critical attack vector.

The Vulnerability Explained

The vulnerable code pattern appears in package-lock.json where nanoid 3.3.16 is resolved:

"node_modules/nanoid": {
  "version": "3.3.16",
  "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
  "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",

This specific version contains a flaw in the random function implementation. Nanoid generates secure IDs by repeatedly requesting random bytes from the operating system's entropy source until sufficient non-biased characters are collected. The vulnerability occurs when:

  1. The internal random function requests bytes from crypto.randomFillSync
  2. Under specific conditions (low entropy pool, certain byte patterns), the filtering logic for URL-safe characters creates an unbounded retry loop
  3. The loop condition while (idx < bytes.length) never reaches termination because idx fails to increment properly when the random byte distribution produces only filtered characters

Real-world attack scenario: Consider an Express.js application using nanoid for session ID generation:

// Vulnerable application pattern
const { nanoid } = require('nanoid');

app.post('/upload', (req, res) => {
  // User-influenced input used in ID generation
  const sessionId = nanoid(32); // Could trigger infinite loop
  // ... handle upload
});

An attacker could potentially manipulate system conditions or repeatedly request ID generation to trigger the edge case, causing the Node.js event loop to freeze entirely. In containerized environments, this would trigger health check failures and pod restarts, creating a cascading denial of service.

The Fix

The remediation involves two coordinated changes to enforce nanoid 3.3.18 across the dependency tree.

Change 1: package.json dependency override

// Before (vulnerable)
"overrides": {
  "brace-expansion": "5.0.8"
}

// After (patched)
"overrides": {
  "brace-expansion": "5.0.8",
  "nanoid": "3.3.18"
}

This npm override directive forces all packages in the dependency tree to use nanoid 3.3.18, regardless of their declared version range. This is critical because nanoid often appears as a transitive dependency through packages like morgan, multer, or build tools.

Change 2: package-lock.json version resolution

-      "version": "3.3.16",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
-      "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+      "version": "3.3.18",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+      "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",

Why this specific fix works: Nanoid 3.3.18 patches the random byte generation algorithm with a guaranteed-termination mechanism. The fix introduces:

  • A maximum iteration bound (1000 attempts) before falling back to a deterministic encoding
  • Improved entropy distribution handling that reduces collision probability of "unlucky" random sequences
  • Defensive coding that ensures idx always advances, preventing the stuck-state condition

The override approach is particularly important here because nanoid's semver-major version 5.x contains breaking API changes (ESM-only, different import syntax). The 3.3.18 patch backports the security fix to the 3.x line, allowing applications to patch without code migration.

Prevention & Best Practices

Dependency security hygiene

  1. Enable automated scanning: Integrate Trivy, Snyk, or npm audit into CI/CD pipelines to catch CVEs before deployment
  2. Use npm overrides strategically: For high-risk transitive dependencies, explicit overrides prevent "phantom" vulnerable versions from sneaking in through nested dependencies
  3. Pin lockfile versions: Ensure package-lock.json is committed and reviewed in pull requests—this file is your dependency integrity guarantee

Defensive coding for ID generation

// Add circuit breakers for ID generation
const { nanoid } = require('nanoid');

async function generateIdWithTimeout(size = 21, timeoutMs = 5000) {
  return Promise.race([
    Promise.resolve(nanoid(size)),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('ID generation timeout')), timeoutMs)
    )
  ]);
}

Standards compliance

  • CWE-835: Loop with Unreachable Exit Condition — the specific classification for this infinite loop vulnerability
  • OWASP Dependency-Check: Recommended for identifying known vulnerable components
  • SLSA Level 1: Maintain provenance for your dependency artifacts

Key Takeaways

  • Always override transitive dependencies for critical CVEs: The nanoid override in package.json ensures 3.3.18 is used everywhere, even when direct dependencies specify vulnerable ranges
  • Infinite loops in crypto utilities are exploitable DoS vectors: What appears to be a "theoretical" edge case in random generation becomes practical when attackers can influence system entropy or request timing
  • Lockfile diffs are security-critical: The package-lock.json change from 3.3.16 to 3.3.18 with the new integrity hash DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w== is your verification that the patched code is actually installed
  • Patch versions matter for security: The jump from 3.3.16 to 3.3.18 (skipping 3.3.17) indicates this was an urgent security release—never ignore patch-level updates in security-sensitive dependencies

How Orbis AppSec Detected This

Source: Trivy static analyzer flagging package-lock.json dependency resolution for nanoid

Sink: The nanoid package's internal random byte generation loop where crypto.randomFillSync results are filtered for URL-safe characters

Missing control: No bounds checking on the retry loop when random byte sequences repeatedly produce filtered characters; the loop termination condition depended on probabilistic distribution without guaranteed progress

CWE: CWE-835 — Loop with Unreachable Exit Condition ('Infinite Loop')

Fix: Added npm override forcing nanoid 3.3.18 and updated lockfile checksum to ensure the patched algorithm with guaranteed loop termination is installed across all dependency paths

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-67213 demonstrates how even "simple" utility libraries like nanoid—trusted by millions of applications for basic ID generation—can harbor serious vulnerabilities. The infinite loop condition, triggered by edge cases in random byte filtering, reminds us that cryptographic code requires defensive programming with guaranteed termination bounds.

The fix—upgrading to nanoid 3.3.18 via npm overrides—is straightforward but only possible when you have visibility into your full dependency tree. Modern JavaScript applications average 1000+ transitive dependencies; automated scanning and patching tools are no longer optional extras but essential infrastructure.

Review your package-lock.json today. If nanoid 3.3.16 or below appears anywhere in your tree, apply the override pattern shown here. Your application's availability depends on it.

References

Frequently Asked Questions

What is CVE-2026-67213?

A high-severity vulnerability in nanoid where the random ID generation algorithm could enter an infinite loop, consuming 100% CPU and causing denial of service.

How do you prevent infinite loop DoS in Node.js dependencies?

Keep dependencies updated, use npm audit or Trivy to scan for known CVEs, and implement dependency version pinning with overrides for transitive vulnerabilities.

What CWE is CVE-2026-67213?

CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')

Is restarting the server enough to prevent this nanoid DoS?

No. The vulnerability triggers on specific input patterns; without patching, repeated malicious requests would cause continuous outages.

Can static analysis detect infinite loop vulnerabilities in dependencies?

Yes. Tools like Trivy can flag known CVEs in dependency trees, though reachability analysis is needed to confirm exploitability in your specific application.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #5

Related Articles

critical

How SQL injection happens in Python DuckDB view creation and how to fix it

A critical SQL injection flaw in `python/src/idx/api.py:265` built five DuckDB `CREATE VIEW` statements with Python f-strings, interpolating a filesystem path directly into SQL text. The fix replaces the interpolated path with a bound parameter (`read_parquet(?)`) and moves the view names into a hardcoded, non-interpolated statement map — eliminating any path where filenames or directory values can alter SQL structure.

high

How JavaScript Injection via String Interpolation Happens in Go Wails Applications and How to Fix It

A high-severity JavaScript injection vulnerability in `internal/clusterconfigs/input.go` allowed arbitrary code execution through malicious kubeconfig filenames. The `saveClusterConfigFile` function at line 20 constructed JavaScript code by directly interpolating unsanitized filenames into `window.ExecJS()` calls, enabling attackers to break out of string literals and execute arbitrary JavaScript in the Webview context.

high

How Denial of Service via Prototype Pollution happens in Axios and how to fix it

Axios versions prior to 1.15.1 merged untrusted configuration objects without guarding against the `__proto__` key, letting attacker-controlled input pollute `Object.prototype` and crash or destabilize applications. Upgrading axios (and its transitive dependencies `form-data`, `follow-redirects`, `proxy-from-env`) closes this Denial of Service and prototype-pollution attack surface without changing any application code.

critical

How Server-Side Request Forgery happens in Node.js and how to fix it

The order-flow service in a Node.js e-commerce backend built an outbound fetch() URL by directly concatenating a configurable `sendingOrder.url` value with a query string, with no validation of protocol or destination. This allowed order data—including customer and payment-adjacent information—to be silently redirected to an attacker-controlled endpoint simply by changing a config value or environment variable.

high

How Infinite Loop Denial of Service Happens in nanoid and How to Fix It

CVE-2026-67213 is a high-severity infinite loop vulnerability in nanoid's `customAlphabet` function that could cause Denial of Service through CPU exhaustion. The fix upgrades nanoid from 3.3.12 to patched versions 3.3.18 and 5.1.6, eliminating the loop condition that trapped ID generation when processing certain input patterns.

critical

How Message Corruption via Protocol Length Header Abuse Happens in WebSocket Implementations and How to Fix It

CVE-2026-54466 is a critical vulnerability in websocket-driver 0.7.4 that allows attackers to corrupt WebSocket messages by abusing protocol length headers. The fix upgrades the package to version 0.7.5, which implements proper validation of untrusted length header inputs. This vulnerability could allow attackers to modify or inject data into real-time communication channels used by frontend applications.