Back to Blog
high SEVERITY7 min read

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

A Denial of Service vulnerability in nanoid versions prior to 3.3.18 allowed attackers to trigger an infinite loop during random ID generation, potentially hanging Node.js processes indefinitely. The fix upgrades nanoid from 3.3.16 to 3.3.18 in both `package-lock.json` and `package.json`, and adds an `overrides` entry to ensure the patched version is enforced across the entire dependency tree. This is a high-severity issue that any project using nanoid for ID generation—directly or transitively—

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

Answer Summary

CVE-2026-67213 is a Denial of Service vulnerability (CWE-835: Loop with Unreachable Exit Condition) in the nanoid npm package affecting versions before 3.3.18 and before 5.1.6. An attacker who can influence the conditions under which nanoid generates random IDs can trigger an infinite loop, causing the Node.js process to hang and become unresponsive. The fix is to upgrade nanoid to 3.3.18 (v3 branch) or 5.1.6 (v5 branch) and add a `"overrides": { "nanoid": "3.3.18" }` entry in `package.json` to enforce the patched version across all transitive dependencies.

Vulnerability at a Glance

cweCWE-835 (Loop with Unreachable Exit Condition)
fixUpgrade nanoid to 3.3.18 (v3) / 5.1.6 (v5) and enforce the version via package.json overrides
riskAttacker can hang the Node.js process, causing full service unavailability
languageJavaScript / Node.js
root causenanoid 3.3.16 contained a loop in its random byte generation path that could become unreachable under certain conditions, spinning forever
vulnerabilityDenial of Service via Infinite Loop in Random ID Generation

How Denial of Service via Infinite Loop Happens in Node.js and How to Fix It


The Vulnerability at a Glance

Field Detail
CVE CVE-2026-67213
Package nanoid
Affected versions < 3.3.18 (v3 branch), < 5.1.6 (v5 branch)
Severity HIGH
CWE CWE-835: Loop with Unreachable Exit Condition
Fix Upgrade to nanoid 3.3.18 / 5.1.6

Introduction

The package-lock.json file in this project pinned nanoid at version 3.3.16—a version containing a high-severity Denial of Service vulnerability. nanoid is one of the most widely used npm packages for generating compact, URL-safe unique IDs; it appears in the dependency trees of millions of Node.js projects, often pulled in transitively by tools like PostCSS, Vite, or CSS preprocessors rather than as a direct dependency. That ubiquity is exactly what makes CVE-2026-67213 dangerous: you may not even know you're running vulnerable code.

The flaw lives in nanoid's random byte generation loop. Under specific conditions, the loop's exit condition becomes unreachable, causing the Node.js event loop to spin at 100% CPU and never return control to the application. For any server handling user-influenced requests that trigger ID generation, this means a single malicious (or even accidental) request can take down the process entirely.


The Vulnerability Explained

What nanoid Does

nanoid generates short, random, URL-safe strings like V1StGXR8_Z5jdHi6B-myT. It does this by drawing random bytes from a cryptographically secure source and mapping them through an alphabet. The generation loop repeatedly samples random bytes and discards any that fall outside the alphabet's probability range—a standard rejection-sampling pattern.

Where It Goes Wrong

In nanoid 3.3.16, the rejection-sampling loop contained a condition that could, under specific entropy or input conditions, become permanently unsatisfiable. Instead of eventually drawing a valid byte and exiting, the loop would spin indefinitely. This is classified as CWE-835: Loop with Unreachable Exit Condition.

The vulnerable version in package-lock.json before the fix:

"node_modules/nanoid": {
  "version": "3.3.16",
  "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
  "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="
}

How an Attacker Exploits This

Consider a web application that uses PostCSS (which depends on nanoid) to process user-uploaded CSS files, or a build API that generates unique asset IDs for uploaded stylesheets. An attacker who can trigger nanoid's ID generation—directly or via a library call—under the conditions that expose the loop bug can cause the Node.js worker process to hang indefinitely.

Concrete attack scenario:

  1. Attacker sends a POST request to /api/compile-css with a crafted stylesheet payload.
  2. The server's PostCSS pipeline calls nanoid internally to generate a unique processing ID.
  3. nanoid 3.3.16 enters the infinite loop during byte rejection sampling.
  4. The Node.js event loop is blocked. No other requests are processed.
  5. The service becomes completely unresponsive until the process is manually restarted.

Because Node.js is single-threaded by default, a single hung request blocks the entire server. Even with a cluster of workers, an attacker can exhaust all workers with a small number of concurrent requests.

Real-World Impact

  • Full service outage for any application running a single Node.js process
  • Cascading failure in clustered environments if enough workers are targeted simultaneously
  • No authentication required if the code path triggering nanoid is publicly accessible
  • Hard to diagnose: the process stays "alive" (not crashed), so health checks may not immediately flag the problem

The Fix

What Changed

The fix involves three concrete changes across two files:

1. package-lock.json — Upgraded nanoid from 3.3.16 to 3.3.18

Before:

"node_modules/nanoid": {
  "version": "3.3.16",
  "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
  "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="
}

After:

"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 change confirms a genuinely different package is now installed—not just a metadata update. nanoid 3.3.18 patches the loop condition so the exit path is always reachable, eliminating the infinite spin.

2. package.json — Added an overrides block

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

This is the critical second half of the fix. Without the overrides entry, a transitive dependency (like an older version of PostCSS or another tool) could still resolve its own copy of nanoid at 3.3.16, leaving the vulnerability present in the dependency tree even after package-lock.json is updated. The overrides field in npm 8.3+ forces all packages in the dependency tree to use nanoid 3.3.18, regardless of what version they individually specify.

Why Both Files Matter

File Purpose of Change
package-lock.json Records the exact resolved version and integrity hash for the top-level nanoid install
package.json overrides Enforces the patched version across all transitive dependencies that also depend on nanoid

Updating only package-lock.json would fix the direct dependency but leave transitive copies of nanoid at the vulnerable version. The overrides block closes that gap completely.


Prevention & Best Practices

1. Run Dependency Scanners in CI/CD

Tools like Trivy, npm audit, and Snyk can detect known CVEs in your package-lock.json before they reach production. In this case, Trivy flagged CVE-2026-67213 against the nanoid entry in package-lock.json. Add a step like this to your pipeline:

# Using npm audit
npm audit --audit-level=high

# Using Trivy
trivy fs --exit-code 1 --severity HIGH,CRITICAL .

2. Use overrides for Transitive Dependency Control

When a vulnerability exists in a transitive dependency that you don't control directly, npm's overrides (npm 8.3+) or Yarn's resolutions field lets you enforce a minimum safe version:

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

3. Pin Lockfiles and Audit Them

Always commit package-lock.json to version control and treat changes to it as security-relevant. A diff showing a version bump in a lockfile should trigger a review against known CVE databases.

4. Monitor Transitive Dependencies

Use npm ls nanoid to see every path in your dependency tree that resolves nanoid:

npm ls nanoid
# my-app@1.0.0
# └─┬ postcss@8.x.x
#   └── nanoid@3.3.16  ← vulnerable

This makes transitive exposure visible before scanners flag it.

5. OWASP and CWE References

  • OWASP A06:2021 – Vulnerable and Outdated Components: This vulnerability is a textbook example of why keeping dependencies current matters.
  • CWE-835: Loop with Unreachable Exit Condition — the root cause classification for this infinite loop bug.

Key Takeaways

  • nanoid 3.3.16's rejection-sampling loop could spin forever, making the Node.js event loop permanently unresponsive on a single malicious or unlucky request.
  • Updating package-lock.json alone is not sufficient—transitive copies of nanoid pulled in by tools like PostCSS also needed to be pinned via package.json's overrides block.
  • The overrides pattern is essential for enforcing patched versions of transitive dependencies in npm projects; without it, vulnerable sub-dependencies can persist invisibly.
  • Trivy's static scan of package-lock.json caught this before any runtime impact—demonstrating that lockfile scanning is a high-value, low-effort security control.
  • A single hung Node.js process blocks all requests in that worker; DoS via infinite loop is not a theoretical risk but a practical, complete service outage.

How Orbis AppSec Detected This

  • Source: The nanoid package version 3.3.16 recorded in package-lock.json under node_modules/nanoid, reachable via the PostCSS dependency chain.
  • Sink: nanoid's internal random byte generation loop—the function responsible for rejection-sampling random bytes during ID creation—contains the unreachable exit condition.
  • Missing control: No version constraint or overrides entry existed to prevent nanoid 3.3.16 from being resolved for transitive dependents, leaving the vulnerable loop reachable via any code path that triggers ID generation.
  • CWE: CWE-835 — Loop with Unreachable Exit Condition.
  • Fix: Upgraded nanoid to 3.3.18 in package-lock.json and added "overrides": { "nanoid": "3.3.18" } to package.json to enforce the patched 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 sharp reminder that high-severity vulnerabilities don't always look dramatic in a diff—sometimes the entire fix is a version number change in a lockfile and a four-line overrides block. But the impact of leaving nanoid 3.3.16 in place is anything but subtle: a single request that triggers the infinite loop brings down the Node.js process entirely.

The two-part fix here—bumping the resolved version in package-lock.json and adding an overrides entry in package.json—is the correct, complete remediation pattern for transitive dependency vulnerabilities in npm projects. If your project uses PostCSS, Vite, or any other tool that transitively depends on nanoid, run npm ls nanoid today and make sure you're not still on 3.3.16.


References

Frequently Asked Questions

What is a Denial of Service via infinite loop vulnerability?

It is a flaw where crafted or unexpected input causes a program to enter a loop that never terminates, consuming 100% CPU and making the application unresponsive to legitimate requests.

How do you prevent infinite loop DoS in Node.js dependencies?

Keep dependencies up to date, use `npm audit` or a scanner like Trivy regularly, and use `overrides` in package.json to enforce minimum safe versions of transitive dependencies.

What CWE is an infinite loop Denial of Service?

CWE-835: Loop with Unreachable Exit Condition, which describes loops whose termination condition can never be satisfied under certain inputs.

Is rate limiting enough to prevent this type of DoS?

Not fully. Rate limiting can reduce the frequency of triggering the vulnerability, but if a single request can cause an infinite loop, the process hangs regardless of request rate. The root cause must be patched.

Can static analysis detect infinite loop vulnerabilities in dependencies?

Static analysis tools like Trivy, Snyk, and npm audit can detect known CVEs in dependencies by matching version ranges. They flagged CVE-2026-67213 in nanoid 3.3.16 exactly this way.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #369

Related Articles

high

How Denial of Service via Unbounded Intermediate Arrays happens in JavaScript and how to fix it

CVE-2026-69152 is a high-severity Denial of Service vulnerability in the `brace-expansion` npm package (versions prior to 1.1.18/2.1.4/3.0.6/5.0.9) that allows attackers to crash a Node.js application by crafting glob patterns that generate unbounded intermediate arrays, effectively bypassing the earlier CVE-2026-14257 mitigation. The fix upgrades `brace-expansion` from 1.1.14 to 1.1.18 in `frontend/package-lock.json`, closing the bypass and restoring safe memory bounds during pattern expansion.

high

How Quadratic CPU Consumption happens in JavaScript YAML parsing and how to fix it

A high-severity denial-of-service vulnerability (GHSA-5p4m-2wfm-xmqj) was discovered in js-yaml affecting both the 3.x and 4.x branches, where parsing YAML documents containing `!!omap` tags triggers quadratic CPU consumption. The fix upgrades js-yaml from `^4.1.1` to `5.2.0` in the project's GitHub Actions workflow dependencies, closing the attack surface for any untrusted YAML input processed by CI/CD tooling.

critical

How Missing Rate Limiting happens in Express.js and how to fix it

Two public API endpoints in `server.js` — `/api/health` and `/api/contact` — were exposed without any rate limiting middleware, allowing attackers to exhaust server resources or spam an SMTP server with unlimited requests. The fix adds rate limiting to both endpoints, with stricter controls on the resource-intensive `/api/contact` route that triggers email sending operations. This change closes a directly exploitable denial-of-service vector in a production web service.

high

How Denial of Service via Specific Input Sequence happens in JavaScript (marked) and how to fix it

CVE-2026-41680 is a high-severity Denial of Service vulnerability in the marked Markdown parsing library, affecting versions prior to 18.0.2. By supplying a crafted input sequence to the parser, an attacker can cause the application to hang or exhaust resources, making the frontend unavailable. Upgrading marked from 18.0.0 to 18.0.2 in both `package.json` and `package-lock.json` closes the vulnerability without affecting valid Markdown rendering.

high

How Quadratic CPU Consumption happens in JavaScript YAML parsing and how to fix it

A high-severity denial-of-service vulnerability in js-yaml (GHSA-5p4m-2wfm-xmqj) caused quadratic CPU consumption when resolving `!!omap` YAML types in both the 3.x and 4.x branches. The fix upgrades js-yaml from 3.14.2 to 3.15.1 and from 4.1.1 to 4.3.1, eliminating the algorithmic complexity exploit while leaving all valid YAML inputs unaffected.

high

How Denial of Service via Unbounded Data Happens in JavaScript and how to fix it

CVE-2025-58754 is a high-severity Denial of Service vulnerability in the popular axios HTTP client library, caused by the absence of a data size check on incoming response or request payloads. An attacker who can influence the size of data processed by axios could exhaust server memory or CPU, bringing down dependent Node.js applications. The fix upgrades axios from version 1.8.4 to 1.18.0, closing the unbounded data processing path.