Back to Blog
high SEVERITY5 min read

How Denial of Service via Infinite Loop happens in Node.js and how to fix it

A critical Denial of Service vulnerability (CVE-2026-67213) in the nanoid package allowed attackers to trigger infinite loops during random ID generation. This fix upgrades nanoid from version 3.3.11 to 3.3.18 using npm overrides, eliminating the infinite loop condition in the customAlphabet function that could crash Node.js applications.

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

Answer Summary

CVE-2026-67213 is a Denial of Service vulnerability in nanoid (before version 5.1.6 and 3.3.18) where the customAlphabet function can enter an infinite loop during random ID generation. This affects Node.js applications using nanoid for generating unique identifiers. The fix involves upgrading nanoid to version 3.3.18 or 5.1.6, which can be enforced using npm overrides in package.json to ensure all nested dependencies use the patched version.

Vulnerability at a Glance

cweCWE-835 (Loop with Unreachable Exit Condition)
fixUpgrade nanoid to 3.3.18 using npm overrides
riskApplication crash and service unavailability
languageJavaScript/Node.js
root causeInfinite loop in nanoid's customAlphabet random ID generation
vulnerabilityDenial of Service via Infinite Loop

Introduction

In this repository's dependency tree, Trivy flagged a HIGH severity vulnerability in the nanoid package—a widely-used library for generating compact, URL-safe unique IDs. The vulnerable version 3.3.11 was present in package-lock.json, creating a potential Denial of Service attack vector through an infinite loop in the random ID generation logic.

The nanoid package is a transitive dependency, meaning it's pulled in by other packages in the dependency tree rather than being directly required. This makes it particularly insidious—developers may not even realize they're using a vulnerable version until a security scanner catches it.

The Vulnerability Explained

CVE-2026-67213 affects nanoid versions before 5.1.6 (for the 5.x line) and before 3.3.18 (for the 3.x line). The vulnerability exists in the customAlphabet function, which allows developers to generate IDs using a custom set of characters.

What Causes the Infinite Loop?

The customAlphabet function generates random IDs by:
1. Creating random bytes
2. Mapping those bytes to characters in the provided alphabet
3. Repeating until the desired ID length is reached

The bug occurs when certain edge conditions in the random byte generation and alphabet mapping logic create a situation where the loop's exit condition can never be satisfied. This happens when:

  • The alphabet size and requested ID length create specific mathematical conditions
  • The random byte masking logic fails to produce valid indices
  • The loop continues indefinitely, waiting for valid random values that never come

The Vulnerable Dependency

Looking at the original package-lock.json:

"node_modules/nanoid": {
  "version": "3.3.11",
  "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
  "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="
}

This version contains the flawed loop logic that can be exploited.

Attack Scenario

An attacker could exploit this vulnerability in several ways:

  1. Direct API Abuse: If an application exposes an endpoint that generates IDs with user-controlled parameters (custom alphabet or length), an attacker could craft requests that trigger the infinite loop.

  2. Indirect Triggering: Even without direct control, high-volume requests to ID-generating endpoints could statistically trigger the edge case, causing intermittent service disruption.

  3. Resource Exhaustion: Once triggered, the infinite loop consumes 100% of a CPU core. In a single-threaded Node.js environment, this effectively freezes the entire application.

The impact is severe: complete service unavailability until the process is manually killed or crashes from resource exhaustion.

The Fix

The fix involves two key changes to ensure the patched version of nanoid is used throughout the dependency tree.

Change 1: npm Overrides in package.json

{
  "overrides": {
    "nanoid": "3.3.18"
  }
}

The overrides field in package.json is crucial here. It forces npm to use version 3.3.18 for all instances of nanoid in the dependency tree, regardless of what version other packages request. This is essential because:

  • nanoid is likely a transitive dependency (pulled in by other packages)
  • Simply updating direct dependencies might not update nested versions
  • The override ensures consistent, patched versions everywhere

Change 2: Updated package-lock.json

"node_modules/nanoid": {
  "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=="
}

The lock file now pins the patched version with its new integrity hash, ensuring reproducible builds always pull the secure version.

Additional Cleanup

The fix also cleaned up some dependency metadata, changing devOptional: true to dev: true for packages like baseline-browser-mapping and caniuse-lite. While not directly related to the CVE, this improves dependency hygiene.

Why This Specific Fix Works

Version 3.3.18 contains fixes to the random byte generation and loop termination logic in customAlphabet. The patched version:

  • Adds proper bounds checking on the loop iteration count
  • Implements safeguards against the mathematical edge cases
  • Ensures the loop always terminates within a reasonable number of iterations

Prevention & Best Practices

1. Regular Dependency Auditing

Run security audits regularly:

npm audit
# or
npx trivy fs --scanners vuln .

2. Use Dependency Overrides Strategically

When transitive dependencies have vulnerabilities:

{
  "overrides": {
    "vulnerable-package": "^patched.version"
  }
}

3. Implement Timeouts

For any operation that could potentially hang:

const timeout = (promise, ms) => Promise.race([
  promise,
  new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Timeout')), ms)
  )
]);

4. Use Process Managers

Deploy with PM2, Docker, or Kubernetes with proper health checks and automatic restarts:

livenessProbe:
  httpGet:
    path: /health
    port: 3000
  initialDelaySeconds: 30
  periodSeconds: 10

5. Monitor for Infinite Loops

Set up CPU usage alerts that trigger when a process exceeds normal thresholds for extended periods.

Key Takeaways

  • Transitive dependencies are attack vectors: nanoid wasn't directly required but still posed a critical risk through the dependency tree
  • npm overrides are essential for security: When nested dependencies are vulnerable, overrides ensure patches propagate throughout the tree
  • Infinite loops in ID generation are particularly dangerous: They affect every request that needs a new ID, potentially bringing down entire services
  • Version 3.3.11 → 3.3.18 fixes the customAlphabet infinite loop: Always verify your nanoid version is at least 3.3.18 (3.x) or 5.1.6 (5.x)
  • Lock file integrity hashes changed: The new SHA512 hash DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w== confirms the patched version

How Orbis AppSec Detected This

  • Source: The nanoid package imported as a transitive dependency via package-lock.json
  • Sink: The customAlphabet() function's internal loop in nanoid's random ID generation
  • Missing control: No loop iteration bounds or timeout safeguards in vulnerable versions
  • CWE: CWE-835 (Loop with Unreachable Exit Condition)
  • Fix: Upgraded nanoid to 3.3.18 using npm overrides to ensure all dependency tree instances use the patched version

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 a seemingly simple utility library can harbor critical vulnerabilities. The infinite loop in nanoid's customAlphabet function could bring down production Node.js applications with no warning. By using npm overrides to force version 3.3.18 across the entire dependency tree, this fix ensures that all code paths using nanoid are protected against this Denial of Service attack.

Remember: your application's security is only as strong as its weakest dependency. Regular auditing, prompt patching, and proper dependency management are essential practices for maintaining secure Node.js applications.

References

Frequently Asked Questions

What is a Denial of Service via Infinite Loop vulnerability?

A vulnerability where malicious or unexpected input causes code to enter an infinite loop, consuming CPU resources indefinitely and making the application unresponsive or crashing it entirely.

How do you prevent Denial of Service vulnerabilities in Node.js?

Use timeouts for operations, validate all inputs, keep dependencies updated, implement rate limiting, and use process managers that can restart crashed applications.

What CWE is Infinite Loop DoS?

CWE-835 (Loop with Unreachable Exit Condition) covers infinite loops that cause denial of service by consuming resources without termination.

Is input validation enough to prevent infinite loop DoS?

Input validation helps but isn't always sufficient. Library-level bugs like this one require patching the vulnerable dependency itself, as the loop condition exists in internal code paths.

Can static analysis detect infinite loop vulnerabilities?

Yes, static analyzers like Trivy can detect known CVEs in dependencies, and advanced tools can identify loop conditions that may not terminate, though library-level bugs often require CVE database matching.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #9

Related Articles

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How dependabot-missing-cooldown happens in GitHub Actions/Node.js and how to fix it

The repository's `.github/dependabot.yml` had no cooldown period configured, meaning Dependabot could immediately propose updates to newly published package versions with zero time for the community to flag malware or instability. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, forcing a 7-day waiting period before new releases are surfaced as update PRs.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.