Introduction
nanoid is the small, dependency-free ID generator that ends up in almost every JavaScript build tool's tree — in this case through postcss, which every markdown-to-HTML pipeline in a vitepress-based site touches. CVE-2026-67213 reports that nanoid versions before 5.1.6 contain an infinite loop reachable through the customAlphabet code path, the factory function developers use when they want IDs drawn from a restricted character set instead of nanoid's default alphabet. A generator that is supposed to return a short string in microseconds can instead spin forever, and because ID generation routinely happens on a hot path (every request, every build step, every record insert), a hang there stalls the whole process.
This repository never called customAlphabet directly — it inherited the vulnerable version transitively through postcss@8.5.24. That's the pattern worth internalizing: you don't have to write a single line that touches nanoid to be exposed by it. If anything in your tree resolves the vulnerable release, the vulnerable code ships in your node_modules regardless of whether your own code path exercises it.
Affected Versions
| Affected | < 5.1.6 (this project had resolved 3.3.16) |
| Fixed in | 5.1.6 (this project pins 5.1.16) |
| Ecosystem | npm |
| CVE / GHSA | CVE-2026-67213 / not assigned |
| CWE | unknown (not assigned in the advisory) |
The Vulnerability Explained
nanoid's customAlphabet() builds a generator that repeatedly reads random bytes and rejects any byte that falls outside the range needed to map fairly onto the requested alphabet — a standard rejection-sampling technique used to avoid modulo bias. The bug fixed for CVE-2026-67213 is that, before 5.1.6, that rejection loop could fail to make progress for certain alphabet configurations, so the loop never accumulates enough valid bytes to return and simply never exits.
Because the lockfile before this fix resolved to the vulnerable release, the exposure here didn't come from application code calling customAlphabet — it came from postcss@8.5.24, which lists nanoid as a direct dependency:
postcss@8.5.24:
dependencies:
nanoid: 3.3.16
Any code path that ends up invoking nanoid's ID generation — build tooling, plugin caches, anything postcss uses internally to name intermediate artifacts — inherits whatever bug lives in the resolved nanoid version. If that path is reachable with attacker-influenced input (for example, a build or SSR pipeline that lets a user's request shape what gets built), the practical impact is a hung Node.js process: no crash, no error log, just a worker that never comes back and stops serving anything else scheduled on the same event loop.
The Fix
The fix is a version bump, applied at the workspace level so it overrides every transitive resolution rather than patching one importer at a time. pnpm-workspace.yaml gained a new override entry next to the existing vite pin:
overrides:
vitepress>vite: 5.4.19
+ nanoid: 5.1.16
The lockfile reflects the same change for the resolved package entry, and notably the supported Node.js range moved forward with it:
- nanoid@3.3.16:
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ nanoid@5.1.16:
+ engines: {node: ^18 || >=20}
and the postcss dependency block that previously pinned nanoid: 3.3.16 now resolves nanoid: 5.1.16 automatically, without postcss itself needing a version bump. The override is what makes this effective: without it, pnpm would keep honoring whatever version range postcss declares, and a simple pnpm update could quietly reintroduce 3.3.16 the next time postcss's own manifest is re-resolved. Pinning at the workspace level closes that door for every current and future importer of nanoid in the tree.
Key Takeaways
- A vulnerable version of nanoid can enter your tree through a build tool you never call direct