Skip to content

Documentation as attack surface: npm libraries teaching insecure patterns in README examples #1560

Description

@ekreloff

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:

  1. Simplicity bias. README examples optimize for "getting started," not production security. The simplest version is often the insecure version.
  2. Documentation lag. Libraries get security hardening over time, but README examples are written once. The code evolves; the docs fossilize.
  3. 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

  1. Guidance for maintainers. A checklist or section in the WG's best practices on treating README code examples as security-critical code.
  2. Documentation security in review processes. Encourage README examples to undergo the same review standards as src/ changes.
  3. Security-annotated examples. When a simplified example omits a security property, annotate it: "⚠️ This uses Math.random() for simplicity. In production, use crypto.randomBytes()."
  4. 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

Activity

  1. ekreloff commented on Apr 2, 2026

    @ekreloff
    Author

    Update: 5th library review adds crypto-js (15.6M weekly downloads)

    Filed brix/crypto-js#534 — the README's AES encryption examples derive keys using EVP_BytesToKey with MD5 and 1 iteration. A GPU can test billions of candidate passphrases per second. Default cipher mode is CBC without authentication (padding oracle vulnerable).

    The pattern count is now 5 libraries, ~195M weekly downloads:

    Library Downloads/week CWE Issue
    crypto-js 15.6M CWE-328 (Weak Hash), CWE-916 (Insufficient KDF) #534
    axios 65M CWE-319 (Cleartext Transmission) GHSA-8wrj-g34g-4865
    node-jsonwebtoken 76M CWE-185 (Incorrect Regex) #1019
    cors 25M CWE-185 (Incorrect Regex) #408
    multer 13.5M CWE-330 (Insufficient Randomness) #1386

    The crypto-js finding is notable because the library is discontinued — these insecure examples will remain permanently in the README of the most-downloaded encryption library in the npm ecosystem. Updated analysis: The Documentation Attack Surface.

  2. ekreloff commented on Apr 3, 2026

    @ekreloff
    Author

    Update: axios supply chain compromise validates documentation attack surface pattern

    The axios supply chain compromise on March 31, 2026 was caught and remediated within 3 hours. But the documentation security issues I filed on the same library weeks earlier (axios#10614, GHSA-8wrj-g34g-4865) remain open.

    This is the pattern in action: the ecosystem has excellent tooling to detect supply chain attacks (the malicious versions were flagged within minutes), but zero tooling to detect insecure patterns taught by documentation. Teams are now auditing their lockfiles for axios@1.14.1 — but nobody is auditing code they copied from axios README examples.

    I posted a comment on the post-mortem suggesting teams add documentation pattern review to their incident response checklist.

    The portfolio now covers 5 libraries across ~195M weekly downloads. Updated analysis: Your Dependencies Have Two Attack Surfaces.

  3. Daniel15 commented on Apr 5, 2026

    @Daniel15

    Good research. This is likely to become even worse now that the usage of AI for code generation is more widespread - popular AI models have a bad habit of producing examples with subtle security issues, and AI-written code often doesn't go through a proper review process.

  4. Daniel15 commented on May 17, 2026

    @Daniel15

    @Neustradamus I thought crypto-js was deprecated because there's now native libraries to do crypto? Why is a new package needed?

  5. Neustradamus commented on May 17, 2026

    @Neustradamus

    @Daniel15: Badly, several projects use always this unsecure crypto-js library...

    What is the other solutions than crypto-es?

  6. Daniel15 commented on May 17, 2026

    @Daniel15

    @Neustradamus Web Crypto API works in both Node.js and in browsers. https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API and https://nodejs.org/api/webcrypto.html

    Node.js also has its older crypto API https://nodejs.org/api/crypto.html

  7. Neustradamus commented on May 17, 2026

    @Neustradamus

    @Daniel15: Thanks for your details! :)

    All users do not use Node.js :/

  8. felipeofdev-ai commented on Aug 3, 2026

    @felipeofdev-ai

    Proposed a maintainer-facing README security checklist under processes/: #1579 — defensive/docs-only, aligned with the guidance ideas in this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions