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.


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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #83

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

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 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.