# nanoid: Integer Overflow or Wraparound

Signed 32-bit coercion of nanoid's size parameter wraps negative, permanently corrupting the CSPRNG pool so all later IDs become the constant string "uuuuuuuuuuuuuuuuuuuuu".

- Payout: Research
- Severity: high
- Program: nanoid
- Found by: alanzabihi
- Disclosed: 2026-09-01
- CVE: CVE-2026-73086
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-xwg4-73v4-xw9w
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-xwg4-73v4-xw9w
- Tags: area:oss domain:oss impact:ato impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:crypto vuln:dos

**Target:** npm library nanoid() size parameter / CSPRNG pool
**Root cause:** `size |= 0` coerces size to signed 32-bit, so size >= 2^31 wraps negative; fillPool's refresh branches both evaluate false and poolOffset is decremented by ~2.1B instead of resetting the pool.
**Steps:**
1. Call `nanoid(2147483648)` (or any size >= 2^31), e.g. via a user-controlled length parameter.
2. `size |= 0` wraps to -2147483648; fillPool skips both refresh branches and does `poolOffset += -2147483648`.
3. Subsequent `nanoid()` reads `pool[negative_index]` → undefined; `undefined & 63` = 0 → `urlAlphabet[0]` = 'u'.
4. Every later call returns `"uuuuuuuuuuuuuuuuuuuuu"` until ~100M calls wrap poolOffset positive or the process restarts.
**How found:** Code reading of index.js:101's `size |= 0` coercion and tracing the negative value through fillPool's branch conditions and the pool indexing loop.
**Exploitation:** Single unauthenticated request to any endpoint passing user input to nanoid's size parameter; afterwards all session IDs, CSRF tokens and identifiers in the process are the same predictable string, enabling token prediction and session hijacking.
**Escalation:** Process-wide persistence: one request poisons IDs for all users and all nanoid consumers in the same process.
**Insight:** Audit signed 32-bit coercions (`| 0`, `>>> 0`) on length/size params of shared-state generators: a negative length can skip pool-refresh branches and leave a persistent negative offset, turning undefined array reads into a constant output.

