Back to Blog
high SEVERITY9 min read

How Denial of Service via Infinite Loop happens in JavaScript and how to fix it

CVE-2026-67213 is a high-severity Denial of Service vulnerability in the popular nanoid JavaScript library, where a flaw in the `customAlphabet` random ID generation function could trigger an infinite loop, hanging the Node.js process indefinitely. The fix upgrades nanoid from version 3.3.11 to 3.3.18 (and adds a package-level override to enforce the safe version across the dependency tree) in the client application. Any application using nanoid's custom alphabet feature with attacker-influenced

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

Answer Summary

CVE-2026-67213 is a high-severity Denial of Service (DoS) vulnerability in the nanoid JavaScript library (CWE-835: Loop with Unreachable Exit Condition) affecting versions before 3.3.18 and 5.1.6. The flaw exists in nanoid's `customAlphabet` random ID generation function, where certain inputs could cause an infinite loop, hanging the Node.js event loop and making the application unresponsive. The fix is to upgrade nanoid to 3.3.18 (v3 branch) or 5.1.6 (v5 branch) and add a `package.json` override to enforce the patched version across all transitive dependencies.

Vulnerability at a Glance

cweCWE-835 (Loop with Unreachable Exit Condition)
fixUpgrade nanoid from 3.3.11 to 3.3.18 and add a package.json override to enforce the patched version across all transitive dependencies
riskAttacker can hang the Node.js event loop, causing complete application unavailability
languageJavaScript / Node.js
root causenanoid's random ID generation logic could enter an infinite loop under certain alphabet/size conditions before version 3.3.18
vulnerabilityDenial of Service via Infinite Loop in nanoid customAlphabet

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


Vulnerability at a Glance

Field Detail
CVE CVE-2026-67213
Severity High
Library nanoid
Affected versions < 3.3.18 (v3), < 5.1.6 (v5)
CWE CWE-835: Loop with Unreachable Exit Condition
Impact Denial of Service (infinite loop, event loop hang)
Fix Upgrade to nanoid 3.3.18 / 5.1.6

Summary

CVE-2026-67213 is a high-severity Denial of Service vulnerability in the popular nanoid JavaScript library, where a flaw in the customAlphabet random ID generation function could trigger an infinite loop, hanging the Node.js process indefinitely. The fix upgrades nanoid from version 3.3.11 to 3.3.18 and adds a package.json override to enforce the safe version across the entire dependency tree. Any application using nanoid's custom alphabet feature with attacker-influenced input was potentially at risk of complete availability loss.


Introduction

The client/package-lock.json file in this application locked nanoid at version 3.3.11 — a version containing a subtle but dangerous flaw in its random ID generation engine. Under specific conditions inside the customAlphabet function, nanoid's internal loop could reach a state where its exit condition becomes permanently unreachable, spinning the CPU at 100% and blocking Node.js's single-threaded event loop from processing any other work.

This matters because nanoid is one of the most downloaded JavaScript packages in existence, used by frameworks like Vite, PostCSS, and countless others as a transitive dependency. You may not even be calling nanoid directly — it might be three levels deep in your dependency tree — but a vulnerable version is a vulnerable version, regardless of how it got there.


The Vulnerability Explained

What is nanoid's customAlphabet?

nanoid is a tiny, fast, URL-safe unique string ID generator. Its customAlphabet API lets developers generate IDs using a custom character set:

import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('1234567890abcdef', 10);
nanoid(); // => 'a3f2b19c7d'

Under the hood, nanoid uses a rejection-sampling algorithm to ensure uniform randomness. It generates a pool of random bytes, maps each byte to an index in the alphabet, and discards any byte that would introduce statistical bias. The loop continues until enough unbiased characters have been collected to fill the requested ID length.

The Flaw: An Unreachable Loop Exit Condition

The vulnerability (CWE-835) lives in this rejection-sampling loop. In versions before 3.3.18, under certain combinations of alphabet size and requested ID length, the mathematical calculation of the "mask" used to filter random bytes could produce a mask value that causes the loop to reject every single candidate byte — forever. The loop's exit condition (having collected enough valid characters) becomes permanently unreachable.

The result is a tight, synchronous infinite loop that:

  1. Consumes 100% of one CPU core
  2. Blocks Node.js's event loop entirely (since JavaScript is single-threaded)
  3. Prevents the server from responding to any subsequent requests
  4. Requires a process kill or container restart to recover

The Vulnerable Dependency in package-lock.json

Before the fix, client/package-lock.json pinned nanoid at the vulnerable version:

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

And critically, client/package.json had an empty overrides object:

"overrides": {}

An empty override means that any transitive dependency pulling in nanoid — such as Vite, a testing framework, or a CSS toolchain — could resolve to the vulnerable 3.3.11 version, even if the top-level dependency was patched.

Attack Scenario

Consider a web application that uses nanoid (directly or transitively via Vite's dev tooling or a session management library) to generate IDs for user sessions, file uploads, or form tokens. If any code path allows an attacker to influence the alphabet or length parameters passed to customAlphabet — for example, through a query parameter, a configuration endpoint, or even indirectly through a crafted file upload that triggers ID generation — the attacker can send a single HTTP request that causes the server process to spin indefinitely.

Even without direct control over nanoid's parameters, if the vulnerable version is present and a triggerable code path exists, a single malicious request can take down the entire Node.js service.


The Fix

Two Coordinated Changes

The fix required changes to both client/package-lock.json and client/package.json — and understanding why both were necessary is important.

1. package-lock.json: Upgrading the Resolved Version

The lock file now resolves nanoid to the patched version:

"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 integrity hash has changed from the old sha512-N8SpfPUnUp1bK+... to the new sha512-DTg4MJbGMWkfi6VZ..., confirming the package content itself is different — this is not just a metadata change.

2. package.json: Enforcing the Override Across the Dependency Tree

The more critical change is the addition of the overrides field:

Before:

"overrides": {}

After:

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

Without this override, a transitive dependency that declares "nanoid": "^3.3.0" in its own package.json could still resolve to any version in the 3.3.x range — including the vulnerable 3.3.11. The overrides field in npm forces the entire dependency tree to use 3.3.18 for any package that depends on nanoid, regardless of what version range those packages request.

This is the defense-in-depth layer that makes the fix robust. Updating the lock file alone only protects the direct resolution; the override ensures no transitive path can sneak the vulnerable version back in.

Before vs. After at a Glance

Before After
nanoid version 3.3.11 3.3.18
Integrity hash sha512-N8SpfPUnUp1bK+... sha512-DTg4MJbGMWkfi6VZ...
Override enforced No ({}) Yes ("nanoid": "3.3.18")
Transitive deps protected

Prevention & Best Practices

1. Use npm audit and Dependency Scanners in CI

Integrate npm audit --audit-level=high into your CI pipeline so that high-severity vulnerabilities in dependencies are caught before they reach production:

# In your CI pipeline
- name: Audit dependencies
  run: npm audit --audit-level=high
  working-directory: client

Tools like Trivy (which detected this CVE), Snyk, and Socket.dev can catch vulnerable dependency versions even before a formal CVE is published.

2. Always Use overrides for Transitive Dependency Vulnerabilities

When a vulnerability exists in a transitive dependency (one you don't directly control), updating the lock file alone is not sufficient. Use npm's overrides (or Yarn's resolutions) to force the entire dependency tree to use the safe version:

// package.json
"overrides": {
  "nanoid": "3.3.18"
}

This pattern is essential for supply chain security.

3. Pin Integrity Hashes

The integrity field in package-lock.json is your defense against supply chain attacks. Always commit your lock file and verify that integrity hashes change when you upgrade a package — if a "version bump" doesn't change the hash, something is wrong.

4. Monitor the CWE-835 Pattern in Your Own Code

If you write custom ID generation or token generation logic, be especially careful with rejection-sampling loops. Always verify that the loop's exit condition is mathematically guaranteed to be reachable for all valid inputs:

// Dangerous pattern: loop may never exit if mask calculation is wrong
while (collected.length < targetLength) {
  const byte = randomByte();
  if (byte & mask) collected.push(alphabet[byte % alphabet.length]);
}

// Safe pattern: add a maximum iteration guard
let attempts = 0;
const MAX_ATTEMPTS = targetLength * 100;
while (collected.length < targetLength && attempts++ < MAX_ATTEMPTS) {
  // ...
}

5. Reference Security Standards


Key Takeaways

  • nanoid 3.3.11 is vulnerable to an infinite loop DoS — if your package-lock.json still references this version, you are exposed even if you never call nanoid directly.
  • An empty "overrides": {} in package.json provides zero protection — transitive dependencies can still resolve to vulnerable versions; you must explicitly pin the safe version.
  • The customAlphabet function's rejection-sampling loop is the specific code path where the exit condition becomes unreachable, making this a targeted and deterministic attack vector.
  • A single HTTP request can hang the entire Node.js event loop — because JavaScript is single-threaded, an infinite synchronous loop in any dependency is a complete availability kill switch.
  • Integrity hash verification matters — the hash changed from sha512-N8SpfPUnUp1bK+... to sha512-DTg4MJbGMWkfi6VZ..., confirming the fix is a real code change, not just a version label.

How Orbis AppSec Detected This

  • Source: The vulnerable nanoid 3.3.11 package resolved in client/package-lock.json, potentially reachable via any code path that calls customAlphabet with attacker-influenced parameters.
  • Sink: nanoid's internal rejection-sampling loop inside the customAlphabet function — a synchronous loop that can spin indefinitely when the mask calculation produces an unreachable exit condition.
  • Missing control: No version constraint or overrides enforcement in client/package.json to prevent the vulnerable 3.3.11 version from being resolved transitively.
  • CWE: CWE-835 — Loop with Unreachable Exit Condition
  • Fix: Upgraded nanoid from 3.3.11 to 3.3.18 in package-lock.json and added "nanoid": "3.3.18" to the overrides field in package.json to enforce the safe version across all transitive dependencies.

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 is a reminder that even the most innocuous-seeming utility libraries — a 130-byte ID generator — can carry high-severity vulnerabilities that threaten application availability. The infinite loop in nanoid's customAlphabet function is particularly dangerous because it targets Node.js's fundamental single-threaded architecture: one triggered loop means zero responses for every subsequent user.

The fix here is clean and surgical: upgrade to 3.3.18, change the integrity hash, and — critically — add the overrides enforcement so no transitive dependency can drag the vulnerable version back in. Neither change alone is sufficient; both are required for a complete remediation.

For developers building JavaScript applications: treat your package-lock.json as a security artifact, not just a build reproducibility tool. Audit it regularly, enforce overrides for known-vulnerable transitive dependencies, and integrate scanners like Trivy into your CI pipeline so vulnerabilities like this are caught automatically before they reach production.


References

Frequently Asked Questions

What is a Denial of Service via infinite loop vulnerability?

It's a flaw where crafted input causes a loop that never terminates, consuming 100% CPU and blocking the process from handling any other requests until it is killed or restarted.

How do you prevent infinite loop DoS vulnerabilities in JavaScript?

Keep dependencies up to date, use `npm audit` and tools like Trivy in CI, and add `overrides` in package.json to pin vulnerable transitive dependencies to safe versions.

What CWE is an infinite loop Denial of Service?

CWE-835: Loop with Unreachable Exit Condition, which describes loops that can never exit because the termination condition is never reachable given certain inputs.

Is rate limiting enough to prevent this type of DoS?

Not fully. Rate limiting reduces the frequency of triggering the loop but does not prevent a single request from hanging the event loop indefinitely once the vulnerable code path is reached.

Can static analysis detect this type of vulnerability?

Yes — tools like Trivy, Snyk, and npm audit can detect known-vulnerable dependency versions. Orbis AppSec used Trivy's CVE-2026-67213 rule to automatically flag and patch this dependency.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2113

Related Articles

high

How javascript.express.security.audit.express-check-csurf-middleware-usage.express-check-csurf-middleware-usage happens in Express.js and how to fix it

A publicly accessible Express.js API endpoint in `app/api/cameras.js` was missing CSRF protection, leaving state-changing requests (POST, PUT, DELETE, PATCH) vulnerable to cross-site request forgery attacks. The fix introduces Origin/Referer header validation middleware in `app/index.js` and removes a redundant Express instance from `cameras.js` that bypassed the application's middleware chain.

high

How Regular Expression Denial of Service happens in JavaScript and how to fix it

CVE-2026-33671 is a Regular Expression Denial of Service (ReDoS) vulnerability in the picomatch glob-matching library, triggered by specially crafted extglob patterns that cause catastrophic regex backtracking. The fix upgrades picomatch to version 4.0.4 (with overrides pinning all transitive copies) in the client's dependency tree, eliminating the vulnerable regex evaluation path. Left unpatched, any code path that passes user-influenced glob patterns to picomatch could be weaponized to stall a

high

How Denial of Service via Infinite Loop happens in JavaScript (nanoid) and how to fix it

A high-severity denial of service vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.18, where the `customAlphabet` function could enter an infinite loop during random ID generation. The fix upgrades the transitive nanoid dependency from 3.3.16 to 3.3.18 using pnpm overrides, ensuring the vulnerable code path is eliminated from the entire dependency tree including PostCSS.

high

How Information Disclosure via Unstripped Credential Headers Happens in Electron Apps and How to Fix It

A high-severity vulnerability (CVE-2026-54673) in the builder-util-runtime package allowed sensitive credential headers to leak during HTTP redirects in Electron applications. The fix upgrades builder-util-runtime from version 9.5.1 to 9.7.0, which properly strips authentication headers before following redirects to prevent information disclosure.

high

How Missing CSRF Middleware happens in Express.js and how to fix it

A high-severity CSRF vulnerability was discovered in `libProxy.js` of an Express.js application — the app had no CSRF middleware protecting its state-changing routes, leaving them open to cross-site request forgery attacks. The fix introduces a `csrf` token library, a `/csrf-token` endpoint to issue tokens, and a middleware that validates `x-csrf-token` headers or `_csrf` body fields on all non-safe HTTP methods. This proactive hardening removes an exploit primitive that could be chained with ot

high

How javascript.lang.security.detect-child-process.detect-child-process happens in Node.js and how to fix it

A command injection vulnerability was discovered in the audio processing plugin `audioedit.js`, where user-controlled input from downloaded media files was passed directly to shell commands via `exec()`. The fix replaces dangerous shell string interpolation with `execFile()` and argument arrays, eliminating the command injection attack surface entirely.