JSPM

pnpm-shield

1.0.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 9
  • Score
    100M100P100Q64580F
  • License MIT

Supply chain attack protection audit tool for pnpm projects

Package Exports

    This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (pnpm-shield) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

    Readme

    ๐Ÿ›ก๏ธ pnpm-shield

    Supply chain attack protection audit tool for pnpm projects.

    npm version Node.js License: MIT Zero dependencies GitHub

    pnpm-shield audits your pnpm project and developer environment against the most common supply chain attack vectors โ€” postinstall script injection, dependency confusion, phantom dependencies, and accidental npm usage. It runs 13 checks, explains every finding with attack vectors and remediation steps linked to official docs, and can auto-fix all of them interactively.


    Why this matters

    The npm registry is the largest software registry in the world โ€” and one of the most targeted. Attackers abuse postinstall scripts, typosquatting, and account takeovers to execute arbitrary code on every machine that runs npm install or pnpm install. The attacks below all share one trait: they would have been stopped by a correctly configured pnpm environment.

    Incident Year Attack vector Source
    event-stream backdoor 2018 Malicious postinstall injected after maintainer handover Snyk
    eslint-scope credential theft 2018 Stolen npm credentials โ†’ postinstall exfiltrated .npmrc tokens ESLint
    ua-parser-js takeover 2021 npm account hijacked โ†’ cryptominer + RAT via postinstall GitHub Advisory
    dependency confusion 2021 Public package shadows internal name, executes on install Alex Birsan
    node-ipc sabotage 2022 Maintainer added destructive postinstall targeting Russian IPs Socket.dev
    colors + faker protest 2022 Maintainer corrupted own packages, breaking thousands of projects Snyk
    xz-utils backdoor 2024 2-year social engineering โ†’ malicious build script in release tarball Openwall
    polyfill.io CDN hijack 2024 Domain acquired โ†’ CDN injected malicious JS into 100k+ sites Sansec
    nx package compromise Aug 2025 Malicious nx versions published โ†’ credential theft + filesystem scan Arctic Wolf
    Shai-Hulud worm Sep 2025 Self-replicating npm worm stole cloud tokens and re-published infected packages Wiz
    axios maintainer compromise Mar 2026 Hijacked maintainer account โ†’ postinstall RAT in axios v1.14.1 Arctic Wolf
    TanStack / TeamPCP campaign Apr 2026 Poisoned CI/CD cache โ†’ malicious publishes across TanStack ecosystem Cybernews

    ๐Ÿ’ก Every postinstall attack in this list is blocked by ignore-scripts=true. The dependency confusion attacks are mitigated by a strict pnpm-lock.yaml and the packageManager field enforced by Corepack. pnpm-shield checks for all of these protections.


    Installation

    # Run directly without installing (recommended for one-off audits):
    pnpm dlx pnpm-shield
    
    # Install globally:
    pnpm add -g pnpm-shield
    
    # Add as a dev dependency in your project:
    pnpm add -D pnpm-shield

    Zero production dependencies. Everything uses Node.js built-ins.


    Usage

    # Run in the root of your project:
    pnpm-shield
    
    # Same command, shorter alias:
    pnpm-check
    
    # CI mode โ€” non-interactive, exits with code 1 on failures:
    pnpm-shield --ci
    
    # Show help:
    pnpm-shield --help
    
    # Show version:
    pnpm-shield --version

    Interactive menu

    After the audit runs, an interactive prompt lets you explore and fix findings without leaving the terminal.

    Commands

    Command Action
    ? Open an arrow-key browser across all 13 checks โ€” navigate with โ†‘โ†“, press Enter to read documentation
    ?N Read docs for check N directly, e.g. ?3
    fix Open a visual multi-selector for fixes โ€” navigate with โ†‘โ†“, toggle with Space, confirm with Enter
    all Apply all auto-fixable items at once
    q Quit

    Documentation panel

    Every check has an integrated documentation panel showing:

    • Why it matters โ€” the security rationale
    • Attack vector โ€” a concrete attack scenario
    • How to fix โ€” step-by-step remediation commands
    • Official references โ€” links to pnpm docs, Node.js docs, and security post-mortems

    What it checks

    pnpm-shield runs 13 security checks across three categories. All non-passing checks support auto-fix.

    ๐Ÿ–ฅ Environment

    # Check Severity Auto-fix
    1 pnpm is installed and in PATH CRITICAL โ€”
    2 Shell alias npm โ†’ pnpm HIGH โœ… Adds alias to shell config
    3 Corepack enabled and managing pnpm HIGH โœ… Runs corepack enable pnpm
    4 No foreign lockfiles (package-lock.json, yarn.lock, bun.lockb) CRITICAL โœ… Deletes foreign lockfiles

    โš™๏ธ pnpm / npm Configuration

    # Check Severity Auto-fix
    5 Global ignore-scripts = true CRITICAL โœ… pnpm config set ignore-scripts true
    6 Local .npmrc: ignore-scripts=true HIGH โœ… Appends to .npmrc
    7 Local .npmrc: save-exact=true MEDIUM โœ… Appends to .npmrc
    8 Local .npmrc: shamefully-hoist=false LOW โœ… Appends to .npmrc
    9 Local .npmrc: engine-strict=true MEDIUM โœ… Appends to .npmrc

    ๐Ÿ“ฆ package.json Hardening

    # Check Severity Auto-fix
    10 pnpm.onlyBuiltDependencies whitelist HIGH โœ… Adds [] to package.json
    11 packageManager field pinned to pnpm@X.Y.Z HIGH โœ… Sets current pnpm version
    12 engines.node range specified MEDIUM โœ… Sets >= current Node major
    13 pnpm-lock.yaml present HIGH โœ… Runs pnpm install

    Grading

    After the audit, your project receives a security grade:

    Grade Score Meaning
    A+ โ‰ฅ 92% Fortress โ€” all critical paths hardened
    A โ‰ฅ 84% Excellent โ€” minor optional improvements available
    B โ‰ฅ 76% Good โ€” a few medium-risk items to address
    C โ‰ฅ 60% Fair โ€” notable gaps that should be closed
    D < 60% Needs attention โ€” critical or multiple high failures

    Score = passed checks + 0.5 ร— warnings.


    Check details

    1. pnpm installed โ€” CRITICAL

    pnpm is the only mainstream package manager with onlyBuiltDependencies whitelisting, per-project ignore-scripts, and a content-addressable store with integrity verification.

    corepack enable pnpm
    # or:
    curl -fsSL https://get.pnpm.io/install.sh | sh

    ๐Ÿ“Ž pnpm Installation


    2. Shell alias npm โ†’ pnpm โ€” HIGH โœ…

    Even with pnpm fully configured, typing npm install by muscle memory invokes the real npm binary. npm ignores your .npmrc, your pnpm-lock.yaml, and your onlyBuiltDependencies whitelist.

    echo 'alias npm=pnpm' >> ~/.zshrc && source ~/.zshrc

    ๐Ÿ“Ž Typosquatting attacks


    3. Corepack managing pnpm โ€” HIGH โœ…

    Corepack (built into Node.js โ‰ฅ 16.9) reads "packageManager" in package.json and blocks npm and yarn project-wide. It also ensures every developer uses the exact same pnpm version.

    corepack enable
    corepack enable pnpm

    ๐Ÿ“Ž Corepack docs ยท pnpm + Corepack


    4. No foreign lockfiles โ€” CRITICAL โœ…

    A package-lock.json or yarn.lock alongside pnpm-lock.yaml creates two conflicting sources of truth. CI systems may pick the wrong one, installing different (potentially malicious) resolved versions.

    rm package-lock.json yarn.lock bun.lockb
    pnpm install
    git add pnpm-lock.yaml

    ๐Ÿ“Ž Dependency confusion attack


    5. Global ignore-scripts=true โ€” CRITICAL โœ…

    Packages can declare postinstall, preinstall, and install lifecycle scripts that run arbitrary shell commands. This is the primary vector for supply chain attacks (event-stream 2018, node-ipc 2022, xz-utils 2024).

    pnpm config set ignore-scripts true

    Note: With ignore-scripts=true, packages that legitimately need build scripts (e.g. esbuild, sharp) will break. Use check #10 (onlyBuiltDependencies) to whitelist exactly those packages.

    ๐Ÿ“Ž pnpm ignore-scripts ยท event-stream post-mortem


    6. Local .npmrc: ignore-scripts=true โ€” HIGH โœ…

    The global config can differ across machines and CI environments. A local .npmrc commits the rule into the repository, protecting every developer and every CI runner regardless of their global config.

    echo "ignore-scripts=true" >> .npmrc
    git add .npmrc

    7. save-exact=true โ€” MEDIUM โœ…

    By default pnpm saves deps with a ^ prefix (e.g. ^1.2.3), allowing any compatible update. An attacker who compromises a package can publish 1.2.4 with malicious code and every project using ^1.2.3 adopts it on the next install.

    echo "save-exact=true" >> .npmrc

    ๐Ÿ“Ž Semver hijacking


    8. shamefully-hoist=false โ€” LOW โœ…

    pnpm uses a strict, isolated node_modules layout by default. shamefully-hoist=true flattens it like npm, allowing packages to import dependencies they never declared (phantom dependencies).

    echo "shamefully-hoist=false" >> .npmrc

    ๐Ÿ“Ž Phantom dependencies


    9. engine-strict=true โ€” MEDIUM โœ…

    Some security patches are Node.js-version-specific. Running code on an EOL Node version may miss critical fixes.

    echo "engine-strict=true" >> .npmrc

    ๐Ÿ“Ž Node.js release schedule


    10. pnpm.onlyBuiltDependencies โ€” HIGH โœ…

    Even with ignore-scripts=true, you may need certain packages to run build scripts (e.g. esbuild, sharp). This whitelist gives surgical, auditable control over which packages may run scripts.

    // package.json
    {
      "pnpm": {
        "onlyBuiltDependencies": ["esbuild", "sharp"]
      }
    }

    An empty array [] blocks all postinstall scripts without exception.

    ๐Ÿ“Ž onlyBuiltDependencies


    11. packageManager field โ€” HIGH โœ…

    Tells Corepack the exact package manager and version the project requires. Corepack will then block npm and yarn and auto-download the correct pnpm version for any contributor.

    // package.json
    {
      "packageManager": "pnpm@11.1.1"
    }

    ๐Ÿ“Ž packageManager field


    12. engines.node range โ€” MEDIUM โœ…

    Declares the minimum Node.js version. Combined with engine-strict=true, pnpm refuses to install on incompatible environments, preventing use of EOL runtimes with known CVEs.

    // package.json
    {
      "engines": { "node": ">=20" }
    }

    ๐Ÿ“Ž engines field


    13. pnpm-lock.yaml present โ€” HIGH โœ…

    The lockfile pins exact resolved versions AND SHA-512 integrity hashes for every package in the full dependency tree. Without it, pnpm install resolves versions fresh each time and can silently adopt a compromised release.

    pnpm install      # generates pnpm-lock.yaml
    git add pnpm-lock.yaml
    # Never add pnpm-lock.yaml to .gitignore!

    ๐Ÿ“Ž pnpm lockfile format ยท Should lockfiles be committed?


    CI/CD integration

    GitHub Actions

    # .github/workflows/security.yml
    name: Security Audit
    
    on: [push, pull_request]
    
    jobs:
      pnpm-shield:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: 20
          - run: pnpm dlx pnpm-shield --ci

    Native git pre-commit hook (no extra dependencies)

    cat > .git/hooks/pre-commit << 'EOF'
    #!/bin/sh
    pnpm-shield --ci
    EOF
    chmod +x .git/hooks/pre-commit

    .npmrc

    ignore-scripts=true
    save-exact=true
    shamefully-hoist=false
    engine-strict=true

    package.json additions

    {
      "packageManager": "pnpm@11.1.1",
      "engines": { "node": ">=20" },
      "pnpm": {
        "onlyBuiltDependencies": []
      }
    }

    Project structure

    pnpm-shield.js        โ† Entry point (20 lines)
    lib/
      colors.js           โ† ANSI color constants
      docs.js             โ† Per-check documentation + official references
      checks.js           โ† Runs all 13 security checks
      ui.js               โ† Terminal output (header, results, doc panel, summary)
      selector.js         โ† Raw TTY arrow-key interactive selector (zero deps)
      fixes.js            โ† Auto-fix implementations for all 13 checks
      runner.js           โ† Orchestrates the full audit + interactive menu

    Contributing

    Pull requests are welcome. To add a new check:

    1. Add the result in lib/checks.js with a docKey and fix key
    2. Add documentation in lib/docs.js with why, attack, fix, and refs
    3. Add a fix handler in lib/fixes.js

    License

    MIT