Summary
Across 4 reviews of high-profile npm libraries — totaling ~180 million weekly downloads — I found the same pattern: the library code handles security correctly, but the README teaches developers to copy insecure patterns.
One finding resulted in a GitHub Security Advisory (GHSA-8wrj-g34g-4865) filed at the maintainer's request.
This isn't a bug in any single library. It's a systemic issue in how the npm ecosystem documents security-sensitive operations.
Findings
1. axios — beforeRedirect bypasses credential-stripping (65M weekly downloads)
The README example re-injects credentials via beforeRedirect after follow-redirects deliberately strips them on protocol downgrades (HTTPS→HTTP). The callback fires after the security mechanism (follow-redirects line 478), directly bypassing it.
2. node-jsonwebtoken — Unanchored regex audience matching (76M weekly downloads)
Documentation allows jwt.verify(token, key, { audience: /api\.myapp\.com/ }) without ^/$ anchors. An attacker can bypass with aud: "evil-api.myapp.com.attacker.com".
3. cors — Unanchored regex origin validation (25M weekly downloads)
README example /example\.com$/ matches evil-example.com. Combined with credentials: true, allows authenticated CORS access from attacker-controlled domains. The library's own test suite uses the correct pattern.
4. multer — Math.random() for filenames (13.5M weekly downloads)
README's diskStorage example uses Math.random() (~30 bits entropy) while the library's default uses crypto.randomBytes(16) (128 bits). If uploads are web-accessible, filenames can be enumerated.
The Pattern
Three forces create this systematically:
- Simplicity bias. README examples optimize for "getting started," not production security. The simplest version is often the insecure version.
- Documentation lag. Libraries get security hardening over time, but README examples are written once. The code evolves; the docs fossilize.
- Copy-paste as learning. Developers copy README examples. A library's documentation IS its API for most users. When the docs teach
Math.random(), that's what gets deployed.
Potential Actions for the WG
- Guidance for maintainers. A checklist or section in the WG's best practices on treating README code examples as security-critical code.
- Documentation security in review processes. Encourage README examples to undergo the same review standards as
src/ changes.
- Security-annotated examples. When a simplified example omits a security property, annotate it: "⚠️ This uses Math.random() for simplicity. In production, use crypto.randomBytes()."
- Automated documentation linting. Explore running security linters on README code blocks (e.g., if
eslint-plugin-security flags Math.random() in source, flag it in docs too).
Methodology
Each library was reviewed by examining README code examples against the library's actual implementation, looking for cases where the documentation teaches a weaker pattern than what the library provides by default. Findings were verified by reading the library source code.
Full review reports and meta-pattern analysis: gist
Summary
Across 4 reviews of high-profile npm libraries — totaling ~180 million weekly downloads — I found the same pattern: the library code handles security correctly, but the README teaches developers to copy insecure patterns.
One finding resulted in a GitHub Security Advisory (GHSA-8wrj-g34g-4865) filed at the maintainer's request.
This isn't a bug in any single library. It's a systemic issue in how the npm ecosystem documents security-sensitive operations.
Findings
1. axios —
beforeRedirectbypasses credential-stripping (65M weekly downloads)The README example re-injects credentials via
beforeRedirectafterfollow-redirectsdeliberately strips them on protocol downgrades (HTTPS→HTTP). The callback fires after the security mechanism (follow-redirects line 478), directly bypassing it.2. node-jsonwebtoken — Unanchored regex audience matching (76M weekly downloads)
Documentation allows
jwt.verify(token, key, { audience: /api\.myapp\.com/ })without^/$anchors. An attacker can bypass withaud: "evil-api.myapp.com.attacker.com".3. cors — Unanchored regex origin validation (25M weekly downloads)
README example
/example\.com$/matchesevil-example.com. Combined withcredentials: true, allows authenticated CORS access from attacker-controlled domains. The library's own test suite uses the correct pattern.4. multer —
Math.random()for filenames (13.5M weekly downloads)README's
diskStorageexample usesMath.random()(~30 bits entropy) while the library's default usescrypto.randomBytes(16)(128 bits). If uploads are web-accessible, filenames can be enumerated.The Pattern
Three forces create this systematically:
Math.random(), that's what gets deployed.Potential Actions for the WG
src/changes.eslint-plugin-securityflagsMath.random()in source, flag it in docs too).Methodology
Each library was reviewed by examining README code examples against the library's actual implementation, looking for cases where the documentation teaches a weaker pattern than what the library provides by default. Findings were verified by reading the library source code.
Full review reports and meta-pattern analysis: gist