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 Server-Side Template Injection Happens in EJS and How to Fix It

CVE-2022-29078 is a critical server-side template injection vulnerability in EJS versions prior to 3.1.7 that allows attackers to execute arbitrary code through the `outputFunctionName` parameter. The fix involves upgrading EJS from 2.6.1 to 3.1.7, which implements proper input validation for template rendering options. This vulnerability could allow remote code execution if user-controlled data reaches the template engine without sanitization.

high

How Denial of Service via Invalid UTF-8 Input Happens in Go and How to Fix It

A high-severity Denial of Service vulnerability in golang.org/x/text (CVE-2026-56852) allowed attackers to crash applications by sending malformed UTF-8 input. The fix involved upgrading the dependency from v0.33.0 to v0.39.0, which tightens UTF-8 validation logic and prevents untrusted input from triggering resource exhaustion. This vulnerability demonstrates why timely dependency updates are critical for maintaining application stability and security.

critical

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

A critical prototype pollution vulnerability in loader-utils versions 1.4.0 and 2.0.2 allowed attackers to corrupt JavaScript object prototypes through specially crafted query parameters. The fix upgrades loader-utils to patched versions 1.4.1 and 2.0.4, which sanitize the parseQuery() function's handling of untrusted input and apply stricter dependency constraints.

critical

How missing authorization and code injection happen in Mindustry JavaScript mods and how to fix it

A `TapEvent` handler in `scripts/CommandBlock.js` exposed a full administrative command palette — including a `run-javascript` command that piped player-supplied text straight into `new Function(text)()` — behind nothing more than a team-membership check. Any player who happened to share a team with the block could execute arbitrary JavaScript (and, through Rhino's Java bridge, arbitrary host code) inside the game runtime. The fix adds an explicit `if (!e.player.admin) return;` guard at the top

critical

How URL Injection happens in Node.js template literals and how to fix it

A URL injection vulnerability in `lib/client.js` allowed user-controlled `repo`, `branch`, and `file` parameters to be interpolated directly into fetch URLs without encoding, enabling potential URL manipulation and request hijacking. The fix introduces per-segment percent-encoding via a new `encodePathSegments` helper, neutralizing special characters before they reach the URL construction layer. This closes an exploit primitive that automated attack tooling could chain with other weaknesses.

high

How Dependabot Missing Cooldown Periods Enable Supply Chain Attacks and How to Fix It

A critical security vulnerability in `.github/dependabot.yml` was exposing a Node.js library to supply chain attacks by automatically updating to newly published packages without a safety delay. By adding a 7-day cooldown period to each package ecosystem configuration, the project now protects against malicious or unstable package versions that could affect downstream consumers.