Skip to content

manuxstack/pnpm-shield

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ›‘οΈ 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

Recommended baseline configuration

.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

About

πŸ›‘οΈ Supply chain security audit for pnpm projects β€” 13 checks, interactive docs, and auto-fix for postinstall injection, dependency confusion, phantom deps and accidental npm usage. Zero dependencies.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors