CVE-2026-73086: nanoid Integer Overflow Corrupts CSPRNG Pool
Passing a size of 2147483648 or larger to nanoid() silently corrupts the shared random-number pool for the entire Node.js process, causing every ID generated afterward to be the predictable string…

The problem
nanoid versions before 3.3.12 and 5.1.11 accept an unbounded integer as the size parameter. Internally, size |= 0 coerces it to a signed 32-bit integer, so 2147483648 becomes -2147483648.
That negative value is passed to fillPool(), where neither guard condition fires. The pool is never refreshed and poolOffset is decremented by ~2.1 billion. Every subsequent nanoid() call reads out-of-bounds indices from the pool buffer, each returning undefined & 63 === 0, which maps to the character 'u' in urlAlphabet.
All session tokens, CSRF tokens, and API keys generated after the trigger are the same 21-character string.
Proof of concept
A working proof-of-concept for CVE-2026-73086 in nanoid, with the exact payload below.
import { nanoid } from 'nanoid'
// Before: normal random ID
console.log(nanoid()) // e.g. "V1StGXR8_Z5jdHi6B-myT"
// Trigger: one request with an oversized size value
try { nanoid(2147483648) } catch(e) {}
// After: every ID in the process is deterministic
console.log(nanoid()) // "uuuuuuuuuuuuuuuuuuuuu"
console.log(nanoid()) // "uuuuuuuuuuuuuuuuuuuuu"
console.log(nanoid()) // "uuuuuuuuuuuuuuuuuuuuu"The root cause (CWE-190) is the size |= 0 bitwise coercion, which silently wraps any value >= 2^31 to a large negative number. No range check existed before the fix, so fillPool() received a negative byte count and both its refresh conditions evaluated false, leaving poolOffset deeply negative.
The patch (commit 821dfed) adds a single guard at the very top of fillPool(): if (bytes < 0 || bytes > 1024) throw new RangeError('Wrong ID size'). This immediately throws on any out-of-range value before any pool state can be touched, preventing corruption entirely.
The fix
Upgrade nanoid to 3.3.12 (v3 line) or 5.1.11 (v5 line). If you cannot upgrade immediately, validate that any caller-supplied size is a positive integer within a sane range (e.g. 1-1024) before passing it to nanoid(). Never pass user-controlled values directly to nanoid(size) without sanitization.
Reported by Andrey Sitnik (ai).
Related research
- high · 7.5CVE-2026-59879CVE-2026-59879: Immutable.js List 32-bit Trie Overflow leading to Denial of Service
- high · 7.4pnpm Environment Secret Exfiltration via Proxy Settings in pnpm-workspace.yaml
- high · 7.1pnpm pacquet: Trust-Lockfile Dependency Alias Path Traversal
- highmysql2: Auth Plugin Downgrade Leaks Plaintext Credentials