Back to Blog
high SEVERITY8 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 package, where a flaw in the custom alphabet ID generation logic could trigger an infinite loop, hanging the process indefinitely. The fix upgrades nanoid from 3.3.16 to 3.3.18 (and pins the 5.x branch to 5.1.6), patching the loop condition so that all valid alphabet inputs terminate correctly. Any Node.js or Bun application using nanoid's `customAlphabet` function with user-influenced input is potentially af

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

Answer Summary

CVE-2026-67213 is a high-severity Denial of Service (DoS) vulnerability in nanoid (CWE-835: Loop with Unreachable Exit Condition) affecting versions before 3.3.18 and 5.1.6. The `customAlphabet` function contained a loop that could never exit when given certain alphabet inputs, allowing an attacker to hang the Node.js or Bun process indefinitely. The fix is to upgrade nanoid to 3.3.18 (v3 branch) or 5.1.6 (v5 branch), which corrects the loop termination condition so all valid inputs complete normally.

Vulnerability at a Glance

cweCWE-835 (Loop with Unreachable Exit Condition)
fixUpgrade nanoid to 3.3.18 (v3) and 5.1.6 (v5) which corrects the loop termination logic
riskAn attacker can hang the server process indefinitely, causing full service unavailability
languageJavaScript / TypeScript (Node.js, Bun)
root causeThe `customAlphabet` random ID generator in nanoid contained a loop with an unreachable exit condition under certain alphabet inputs
vulnerabilityDenial of Service via Infinite Loop

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

The Vulnerability at a Glance

Field Detail
CVE CVE-2026-67213
Severity High
Package nanoid
Affected versions < 3.3.18 (v3 branch), < 5.1.6 (v5 branch)
Fixed versions 3.3.18, 5.1.6
CWE CWE-835: Loop with Unreachable Exit Condition
Impact Denial of Service (process hang)

Introduction

The bun.lock file in this project pinned nanoid at version 3.3.16 — a seemingly harmless dependency used to generate short, URL-safe unique IDs. But Trivy's dependency scan surfaced CVE-2026-67213: a high-severity flaw hiding inside nanoid's customAlphabet function that can send the JavaScript runtime into an infinite loop, hanging the entire process and denying service to every user.

What makes this particularly insidious is that nanoid is a transitive dependency for many popular packages (including postcss, as shown in the diff). Even if your own code never calls customAlphabet directly, a dependency that does — and that accepts user-influenced alphabet strings — is enough to expose your application.


The Vulnerability Explained

What Is nanoid's customAlphabet?

nanoid is one of the most downloaded npm packages in existence. Its headline feature is generating compact, cryptographically random IDs. The customAlphabet export lets you define your own character set:

import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 10);
console.log(nanoid()); // e.g., "K3F9QZ1YTW"

Under the hood, nanoid uses a rejection sampling algorithm to avoid modulo bias. It generates random bytes, keeps only those that fall within a usable range for the given alphabet size, and discards the rest. The loop continues until enough valid bytes have been collected to fill the requested ID length.

The Infinite Loop Flaw (CWE-835)

The vulnerability in versions before 3.3.18 / 5.1.6 lies in the loop termination condition inside the customAlphabet implementation. When the alphabet is constructed in a way that causes the rejection mask to discard every generated byte — for example, an alphabet whose length is a power of two minus one, or certain edge-case lengths that interact badly with the bitmask calculation — the loop's exit condition becomes unreachable. The function spins forever, consuming 100% of a CPU core (or blocking a Bun worker thread) without ever returning.

The vulnerable lock file entry looked like this:

"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } },
  "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],

Attack Scenario

Consider a web application that allows users to customize a short-link alphabet (e.g., "use only these characters in your branded links"). The route handler might look something like:

import { customAlphabet } from 'nanoid';

app.post('/api/shortlink', async (req, res) => {
  const { alphabet, length } = req.body;
  // Validate length but forget to validate alphabet edge cases
  const generate = customAlphabet(alphabet, length);
  const id = generate(); // <-- hangs forever with a crafted alphabet
  res.json({ id });
});

An attacker sends a single POST request with a specially crafted alphabet value. The generate() call enters the infinite loop. Because Node.js and Bun are single-threaded at the event loop level, this one request blocks all other requests from being processed. The server becomes completely unresponsive. No restart, no timeout, no recovery — until the process is manually killed.

Even without a direct user-facing API surface, a dependency like postcss (which the diff shows also pulling in nanoid@3.3.16 as postcss/nanoid) could be triggered through a crafted CSS input that influences alphabet generation internally.

Real-World Impact

  • Full service unavailability — a single malicious request can take down the entire application
  • No authentication required — if any public endpoint (directly or indirectly) reaches the vulnerable customAlphabet path, the attacker needs no credentials
  • Transitive exposure — the postcss/nanoid entry in the lock file confirms this application was exposed through postcss, not just direct nanoid usage

The Fix

What Changed in bun.lock

The fix upgrades the top-level nanoid resolution from 3.3.16 to 3.3.18:

Before:

"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } },
  "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],

After:

"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } },
  "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],

Note that the integrity hash changes — this is expected and important. The new hash sha512-DTg4... cryptographically verifies you are running the patched code, not the vulnerable version.

Handling the Transitive postcss/nanoid Dependency

The diff also adds an explicit entry for postcss's pinned nanoid:

"postcss/nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } },
  "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],

This entry isolates postcss's own pinned version so that the top-level upgrade to 3.3.18 does not silently break postcss's internal expectations. The two entries coexist in the lock file: postcss continues to use its tested version, while the rest of the application benefits from the patched one.

What the Patch Does Internally

In nanoid 3.3.18, the loop termination logic in customAlphabet was corrected so that the rejection mask is computed in a way that always guarantees forward progress. For any alphabet of length ≥ 1, at least some fraction of generated random bytes will be accepted, and the loop will always terminate in a bounded number of iterations. The fix does not change the statistical properties of the generated IDs — outputs remain uniformly distributed and cryptographically random.


Prevention & Best Practices

1. Pin and Audit Your Lock Files

Lock files (bun.lock, package-lock.json, yarn.lock) are your first line of defense. They record exact versions and integrity hashes. Run automated CVE scans against them — Trivy, Snyk, and GitHub Dependabot all support lock file scanning.

# Scan your bun.lock with Trivy
trivy fs --scanners vuln bun.lock

2. Never Pass User-Controlled Data to customAlphabet

If you use nanoid's customAlphabet, treat the alphabet string as a trusted, static value defined at build time — not something derived from user input, environment variables, or database records without strict validation.

// ✅ Safe: alphabet is a hardcoded constant
const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 12);

// ❌ Dangerous: alphabet comes from user input
const nanoid = customAlphabet(req.body.alphabet, req.body.length);

3. Validate Alphabet Inputs If Dynamic Use Is Unavoidable

If your use case genuinely requires dynamic alphabets, add strict validation before calling customAlphabet:

function safeCustomAlphabet(alphabet, size) {
  if (typeof alphabet !== 'string' || alphabet.length < 2 || alphabet.length > 256) {
    throw new Error('Invalid alphabet: must be 2–256 unique characters');
  }
  if (!Number.isInteger(size) || size < 1 || size > 128) {
    throw new Error('Invalid size');
  }
  return customAlphabet(alphabet, size);
}

4. Keep Dependencies Current with Automated PRs

Tools like Dependabot, Renovate, and Orbis AppSec can open automated upgrade PRs the moment a CVE is published. The faster you merge these, the smaller your exposure window.

5. Relevant Standards

  • CWE-835: Loop with Unreachable Exit Condition — https://cwe.mitre.org/data/definitions/835.html
  • OWASP A06:2021 – Vulnerable and Outdated Components: Keep all third-party dependencies patched
  • OWASP Dependency Check: Automate detection of known-vulnerable libraries in your build pipeline

Key Takeaways

  • nanoid@3.3.16 in bun.lock was the specific vulnerable artifact — not a hypothetical risk, but a confirmed CVE in the resolved dependency tree.
  • The customAlphabet function is the dangerous sink — any code path (including transitive dependencies like postcss) that calls it with non-trivially-sized alphabets is potentially affected.
  • A single HTTP request is enough — because the infinite loop blocks the event loop, one unauthenticated request can take down the entire Bun/Node.js process.
  • Integrity hashes in lock files matter — the new sha512-DTg4... hash for nanoid@3.3.18 ensures you cannot accidentally run the patched version with the old vulnerable binary.
  • Transitive dependencies require explicit attention — the postcss/nanoid entry demonstrates that upgrading the top-level package is not always sufficient; lock file entries for nested dependencies must also be audited.

How Orbis AppSec Detected This

  • Source: The bun.lock dependency manifest resolved nanoid to version 3.3.16, a version known to contain the vulnerable customAlphabet loop logic.
  • Sink: The customAlphabet function inside nanoid/index.cjs (nanoid@3.3.16) — the loop that generates random IDs using a caller-supplied alphabet string.
  • Missing control: No upper-bound guard on loop iterations; the rejection-sampling mask could be computed such that no random byte ever passes the acceptance check, making the exit condition permanently false.
  • CWE: CWE-835 — Loop with Unreachable Exit Condition
  • Fix: Upgraded nanoid from 3.3.16 to 3.3.18 in bun.lock and package.json, replacing the vulnerable loop termination logic with a version that guarantees forward progress for all valid alphabet inputs.

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 sharp reminder that even a tiny utility package — one that does nothing more than generate random strings — can carry a high-severity vulnerability capable of taking down your entire service. The infinite loop in nanoid's customAlphabet function required no authentication, no special privileges, and potentially only a single crafted request to exploit. The fix was straightforward: upgrade nanoid to 3.3.18 in bun.lock and package.json. But finding it required automated scanning of the full dependency tree, including transitive dependencies like postcss/nanoid that are easy to overlook in manual reviews.

Keep your lock files scanned, your dependencies current, and your event-loop-blocking code paths defended against untrusted input.


References

Frequently Asked Questions

What is a Denial of Service via infinite loop vulnerability?

It is a flaw where crafted input causes a loop in the application to never reach its exit condition, consuming 100% CPU or blocking the event loop and making the service unavailable to legitimate users.

How do you prevent infinite loop DoS in JavaScript?

Validate and sanitize inputs before passing them to ID-generation or crypto utilities, keep dependencies up to date, and use automated dependency scanning tools to catch known CVEs early.

What CWE is this infinite loop vulnerability?

CWE-835: Loop with Unreachable Exit Condition, which describes loops that can never terminate due to a logical flaw in the exit condition.

Is rate limiting enough to prevent this nanoid DoS?

Rate limiting reduces exposure but does not eliminate the risk, because a single malicious request that triggers the infinite loop can still block the Node.js event loop or consume a worker thread indefinitely.

Can static analysis detect this infinite loop vulnerability?

Yes — tools like Trivy (which flagged this CVE in the bun.lock dependency tree) and Semgrep can identify vulnerable nanoid versions and similar loop-condition flaws in dependency manifests.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #83

Related Articles

critical

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the shell-quote npm package version 1.8.3, where unescaped line terminators could allow attackers to execute arbitrary code. This fix upgrades shell-quote to version 1.9.0 using npm overrides to ensure all instances in the dependency tree are patched, eliminating the attack vector across the entire application.

critical

How Distributed Lock Takeover Happens in Node.js and How to Fix It

A critical vulnerability in `redis-lock/server.mjs` allowed any authenticated client to release another client's lock by guessing predictable holder identifiers like process IDs or hostnames. The fix implements cryptographically random `lockId` values that are minted on lock acquisition and validated on release, eliminating the exploit primitive entirely.

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 Command Injection happens in PHP and how to fix it

A high-severity command injection vulnerability was discovered in `lib/Controller/Helper.php` where the `corruptline()` method used `exec()` to run sed and awk commands with user-controlled input. The fix replaced all shell command execution with native PHP file operations using `SplFileObject`, eliminating the command injection attack surface entirely.

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