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

critical

How CORS Misconfiguration happens in Node.js with Hono and how to fix it

CVE-2026-54290 is a HIGH severity CORS misconfiguration in the Hono web framework where the CORS middleware incorrectly reflects any `Origin` header back to the client — including credentials — when the `origin` option defaults to a wildcard. Upgrading `hono` from `4.12.16` to `4.12.34` in `package-lock.json` and pinning the version via `overrides` in `package.json` closes the vulnerability. Left unpatched, this flaw could allow malicious cross-origin sites to make credentialed requests and read

high

How package_managers.pnpm.pnpm-missing-minimum-release-age.pnpm-minimum-release-age happens in pnpm workspaces and how to fix it

A pnpm workspace configuration was missing the `minimumReleaseAge` setting, allowing freshly published (and potentially malicious) package versions to be installed immediately. The fix adds a 7-day quarantine period along with `blockExoticSubdeps` and `trustPolicy: no-downgrade` to harden the supply chain against package takeover attacks.

critical

How WebSocket Protocol Handler Vulnerabilities happen in Node.js Dependencies and how to fix it

A critical vulnerability (CVE-2026-54466) was discovered in websocket-driver version 0.7.4, a WebSocket protocol handler used in the dependency tree. The vulnerability allowed attackers to exploit flaws in WebSocket frame parsing, potentially leading to denial of service or protocol-level attacks. The fix upgraded websocket-driver to version 0.7.5, which patches the protocol handling vulnerabilities and hardens input validation for untrusted WebSocket frames.

high

How Silent Form Limit Bypasses Happen in Starlette and How to Fix Them

CVE-2026-54283 is a high-severity Denial of Service vulnerability in Starlette where form size limits set on `request.form()` were silently ignored for `application/x-www-form-urlencoded` content, allowing attackers to submit arbitrarily large payloads that could exhaust server resources. The fix upgrades Starlette from version 0.49.1 to 0.50.0, where the form parser correctly enforces configured limits for both multipart and URL-encoded content types. This change was applied to `agent/sandbox/u

critical

How Server-Side Request Forgery (SSRF) Happens in Node.js fetch Tools and How to Fix It

A critical Server-Side Request Forgery (SSRF) vulnerability in `plugins/tools/fetch.js` allowed attackers to access internal resources and cloud metadata endpoints by passing arbitrary URLs to the fetch command. The fix adds hostname resolution and private IP range validation before executing any HTTP requests, preventing attackers from targeting internal infrastructure.

high

How IP Address Parsing Inconsistencies Cause SSRF and Trust-Boundary Bypass in Node.js Applications

The `ip-address` library version 10.2.0 contained a critical parsing inconsistency where the `Address4` decoder interpreted leading-zero octets as decimal numbers, while most DNS resolvers and network systems interpreted them as octal. This mismatch allowed attackers to bypass IP-based access controls and SSRF filters. Upgrading to version 10.3.1 fixes this vulnerability by aligning the library's parsing behavior with standard resolver behavior.