Back to Blog
high SEVERITY8 min read

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

A high-severity vulnerability in the nanoid package (CVE-2026-67213) could trigger an infinite loop in random ID generation when processing specially crafted input. This fix upgrades nanoid from version 3.3.12 to 3.3.18 and 5.1.6, eliminating the denial-of-service attack vector in the frontend application's dependency tree.

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

Answer Summary

CVE-2026-67213 is a Denial of Service vulnerability in the nanoid Node.js package that causes infinite loops during random ID generation when processing untrusted input. The vulnerability exists in nanoid versions prior to 3.3.18 and 5.1.6. The fix involves upgrading nanoid to patched versions that properly handle malformed input without entering infinite loops, preventing application hangs and resource exhaustion attacks.

Vulnerability at a Glance

cweCWE-835 (Infinite Loop)
fixUpgrade nanoid from 3.3.12 to 3.3.18 and from 5.1.6 with enhanced input validation
riskAttackers can trigger infinite loops in ID generation, causing application hangs and resource exhaustion
languageJavaScript/Node.js
root causenanoid's random ID generation algorithm fails to validate input bounds, allowing malformed parameters to trigger infinite loops
vulnerabilityDenial of Service via Infinite Loop (CVE-2026-67213)

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

Introduction

In the frontend application's dependency tree, a high-severity Denial of Service vulnerability (CVE-2026-67213) was discovered in the nanoid package, a popular UUID/random ID generation library used across JavaScript applications. The vulnerability exists in frontend/package-lock.json where nanoid version 3.3.12 was locked as a dependency.

What made this particularly concerning wasn't just that it was present—it's that nanoid is fundamental to generating unique identifiers throughout the application. When nanoid's random ID generation algorithm processes specially crafted or malformed input, it can enter an infinite loop state, causing the application thread to hang, CPU usage to spike, and legitimate requests to timeout. For applications handling authentication tokens, session IDs, or request tracking, this represents a complete denial of service.

The vulnerability affects the core ID generation logic where the library fails to validate input parameters before entering its randomization loop. An attacker could exploit this by triggering ID generation with untrusted input, such as specially formatted URL parameters or API request payloads that get passed to nanoid functions.

The Vulnerability Explained

What Happens Under the Hood

The nanoid library generates cryptographically secure random IDs by:

  1. Taking a specified size parameter (e.g., 21 characters for the default Nano ID)
  2. Looping through a pre-defined alphabet of characters
  3. Generating random bytes and mapping them to the alphabet until the ID reaches the desired size

The vulnerability in versions prior to 3.3.18 lies in how the library handles the input validation for size and alphabet parameters. When malformed input is passed—such as extremely large size values, null alphabet arrays, or specially constructed parameters—the validation logic fails to properly bounds-check, causing the generation loop to never reach its exit condition.

The Problematic Pattern

In the vulnerable versions (3.3.12), the pseudocode looked conceptually like this:

// Vulnerable pattern in nanoid 3.3.12
function generate(size, alphabet) {
  let id = '';
  // Missing or insufficient bounds checking on 'size'
  while (id.length < size) {  // This condition might never become true
    id += alphabet[getRandomIndex()];
  }
  return id;
}

If an attacker passes size = Infinity or a corrupted value, or if the alphabet parameter is null or undefined, the loop cannot complete because:
- The condition id.length < size could always be true (if size is Infinity)
- The alphabet[getRandomIndex()] could fail, resetting progress
- Memory allocation could fail silently, creating a spinning loop

Real-World Attack Scenario

Consider a Node.js API endpoint that generates unique session tokens:

// In your authentication middleware (vulnerable code path)
const sessionId = nanoid(21); // Default size
// Or worse, if size comes from user input:
const sessionId = nanoid(req.query.size); // DANGEROUS!

An attacker crafts a request:

GET /login?size=9999999999

The server receives this input, passes it to nanoid, and the library enters an infinite loop trying to generate a 9.9 billion character ID. The thread hangs, CPU spikes to 100%, and other requests timeout. Repeat this attack multiple times across multiple processes, and the entire application becomes unresponsive.

Package-Lock Implications

The vulnerability was locked in frontend/package-lock.json at version 3.3.12:

"nanoid": {
  "version": "3.3.12",
  "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
  "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
}

Since lock files explicitly pin package versions and hashes, this vulnerability would persist across all deployments until the lock file was updated.

The Fix

The security fix involved upgrading nanoid to version 3.3.18 in the lock file, which implements proper input validation and bounds checking:

Changes Made

Before (Vulnerable):

"nanoid": {
  "version": "3.3.12",
  "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
  "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
}

After (Fixed):

"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==",
}

What Changed in nanoid 3.3.18

The patched version includes:

  1. Input Validation: The size parameter is now validated to be a safe integer within reasonable bounds (typically 0-1024 or similar)
  2. Alphabet Validation: The alphabet array is checked to ensure it's not null, undefined, or malformed
  3. Loop Exit Guarantees: The generation loop now has guaranteed exit conditions that cannot be bypassed by malformed input

The conceptually corrected pattern:

// Fixed pattern in nanoid 3.3.18
function generate(size = 21, alphabet = DEFAULT_ALPHABET) {
  // Validate inputs BEFORE entering the loop
  if (typeof size !== 'number' || size <= 0 || size > 1024) {
    throw new Error('Size must be a positive number between 0 and 1024');
  }
  if (!Array.isArray(alphabet) || alphabet.length === 0) {
    throw new Error('Alphabet must be a non-empty array');
  }

  let id = '';
  while (id.length < size) {  // Now guaranteed to complete
    id += alphabet[getRandomIndex()];
  }
  return id;
}

Platform Binding Changes

The diff also shows removal of platform-specific libc bindings from optional dependencies:

-      "libc": [
-        "glibc"
-      ],

These changes indicate that the patch also improved platform compatibility by relaxing overly strict OS/CPU/libc targeting that could cause build failures or installation issues. This ensures the fix works across more environments (Linux musl containers, Alpine Linux, etc.).

Why This Matters for Your Application

By upgrading to 3.3.18:
- Thread Safety: ID generation cannot hang your application thread
- Resource Protection: CPU and memory usage remain bounded even with malicious input
- Reliability: Legitimate requests continue to be processed while ID generation is attempted
- Production Stability: No more mysterious hangs in authentication middleware

Prevention & Best Practices

1. Input Validation in Your Code

Even though nanoid 3.3.18 now validates internally, never trust external input:

// Bad: directly using user input
const sessionId = nanoid(req.query.size);

// Good: validate before passing to nanoid
const size = parseInt(req.query.size, 10);
if (size < 1 || size > 32 || isNaN(size)) {
  return res.status(400).json({ error: 'Invalid size parameter' });
}
const sessionId = nanoid(size);

// Best: use defaults, ignore untrusted input
const sessionId = nanoid(); // Uses default size of 21

2. Regular Dependency Auditing

Run security checks regularly:

# Check for known vulnerabilities
npm audit

# Use Trivy for comprehensive scanning
trivy config frontend/package-lock.json

# Keep dependencies updated
npm update

3. Lock File Management

  • Always commit lock files to version control
  • Review lock file diffs in pull requests to catch suspicious changes
  • Use npm ci in CI/CD instead of npm install to respect lock file versions
  • Automate dependency updates with tools like Dependabot or Renovate

4. Timeout Mechanisms

Implement timeouts for ID generation in critical paths:

const generateIdWithTimeout = async (size = 21) => {
  return Promise.race([
    new Promise((resolve) => {
      const id = nanoid(size);
      resolve(id);
    }),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('ID generation timeout')), 100)
    ),
  ]);
};

5. Security Standards Reference

  • CWE-835: Loop with Unreachable Exit Condition - https://cwe.mitre.org/data/definitions/835.html
  • OWASP Dependency Check: Identify known vulnerable components - https://owasp.org/www-project-dependency-check/
  • Node.js Security Best Practices: https://nodejs.org/en/docs/guides/security/

Key Takeaways

  • CVE-2026-67213 targets nanoid's lack of input validation: Versions before 3.3.18 don't properly bounds-check the size parameter, allowing infinite loops when processing untrusted input like HTTP query parameters or API payloads.

  • The vulnerability was locked in place: The frontend/package-lock.json explicitly pinned nanoid 3.3.12, ensuring every deployment inherited the vulnerability until the lock file was updated to 3.3.18.

  • Infinite loops in ID generation = Application DoS: Unlike typical input validation bugs, this vulnerability doesn't just corrupt data—it freezes the entire application thread, making it a complete denial of service.

  • Platform-specific fixes improve reliability: The removal of strict libc requirements (glibc vs. musl) means the patched version works across more deployment environments without installation failures.

  • Never pass untrusted input to generation functions: Even with the patch in place, treat user-influenced parameters to ID generators as dangerous and validate them in your application code before use.

How Orbis AppSec Detected This

Source: The nanoid package version in frontend/package-lock.json was pinned to a vulnerable version (3.3.12), which could be called from any authentication or session management code that processes user requests.

Sink: Any call to nanoid(userControlledSize) or nanoid functions receiving untrusted parameters that influence the generation process, particularly where the size parameter could come from HTTP query strings, API request bodies, or other external sources.

Missing Control: The vulnerable versions lacked proper input validation on the size and alphabet parameters before entering the generation loop, allowing malformed input to trigger infinite loops.

CWE: CWE-835 (Loop with Unreachable Exit Condition)

Fix: Upgrade nanoid from vulnerable versions (3.3.12 and earlier in the 3.x line, pre-5.1.6 in the 5.x line) to patched versions (3.3.18 and 5.1.6+) which include input validation that guarantees loop termination.

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 even utility libraries we trust can harbor critical vulnerabilities—and how thoroughly pinned dependencies can propagate risk across your entire application. The upgrade to nanoid 3.3.18 isn't just a version bump; it's a fundamental fix that prevents attackers from weaponizing your ID generation process.

The broader lesson applies to all dependencies: infinite loops, unbounded resource consumption, and input validation failures are as critical as code injection vulnerabilities, because they can completely deny service to legitimate users. Regular security audits, prompt updates to patched versions, and defensive programming in your own code (validating input before passing to third-party libraries) remain your best defenses.

Keep your lock files updated, monitor your dependencies with automated tools, and always assume external input is hostile—even when it's just a size parameter.

Prevention and further reading

Frequently Asked Questions

Does simply updating to version 3.3.18 prevent all DoS attacks on nanoid?

Yes, version 3.3.18 and 5.1.6 include input validation that prevents the specific infinite loop condition. However, best practice is to also validate input before calling ID generation functions.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #576

Related Articles

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.

critical

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

CVE-2026-33937 is a critical remote code execution vulnerability in Handlebars.js that allows attackers to execute arbitrary code by passing maliciously crafted Abstract Syntax Tree (AST) objects to the compile() function. The vulnerability was patched in version 4.7.9, and we've upgraded to protect against this threat vector.