Skip to content

About

A 5-second morning supply-chain safety check for npm/PyPI/Docker/Go/Rust: known-malicious packages, CVEs, zero-hour deps, release-cooldown & digest pinning, plus build-manifest and AI-agent-config auto-exec checks (Shai-Hulud/Miasma). Zero-dependency CLI + Claude Code plugin.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

9 Commits

Folders and files

Repository files navigation

supply-chain-scan

A 5-second morning safety check for the project you're about to work on. Before your first install/update of the day, scan its dependencies for known-malicious packages, CVEs, freshly-published (zero-hour) deps, and missing release-cooldown / digest pinning β€” across npm, PyPI and Docker, in one command.

npx supply-chain-scan
  • πŸ›‘οΈ Catches what npm audit misses β€” known-malicious packages (the OSV MAL- feed), not just CVEs.
  • πŸŒ… Built for a morning routine β€” point it at the repo you're opening; get a clear go / review verdict.
  • 🧩 npm + PyPI + Docker in a single pass.
  • πŸ“¦ Zero runtime dependencies. A supply-chain tool shouldn't be a supply-chain risk β€” read all of it in 5 minutes.
  • πŸ’» Cross-platform (Windows / Linux / macOS), no account, no CI, no telemetry.

Why

The 2025–26 wave of npm and PyPI supply-chain attacks (hijacked maintainers β†’ malicious release β†’ fast adoption) and malicious/typosquatted Docker images all exploit the same blind spot: tools that only know about already-reported CVEs. supply-chain-scan layers the free defenses that actually cover the gap:

Threat Defense in this tool
Known-malicious package (npm/PyPI/Go/Rust) osv-scanner + the OSV MAL- feed
Known CVE osv-scanner + the package manager's audit
A CVE that is exploited in the wild right now CISA KEV cross-check: every finding matched against the Known Exploited Vulnerabilities catalog β€” a match is shown as KNOWN EXPLOITED and outranks severity
Malicious release of a component you deploy but never build (the LiteLLM incident, Oct 2026: a poisoned Docker/PyPI release no lockfile ever sees) deployed-stack watch (stack.yaml): OSV.dev queried per exact pinned version, only advisories new since the last successful run reported
Zero-hour malicious release (not yet reported) freshness heuristic (flag deps published < N days ago) + the release-cooldown config check
Install-time execution of a malicious preinstall/postinstall script (the keyv/Shai-Hulud vector, Aug 2026) install-time protection check: flags whether Aikido Safe Chain is active β€” it blocks the install in real time, before this scan (or any retrospective tool) ever sees the lockfile
Mutable / hijackable Docker base image digest-pinning check (@sha256:) + optional image CVE scan
Install-time code execution outside package.json scripts (the phantom gyp trick β€” Shai-Hulud/Miasma, 2026) build-manifest scan: flags shell-command substitution in binding.gyp/Makefile/setup.py
Obfuscated worm payload dropped as a root JS file (e.g. bun_environment.js) undeclared-large-JS check: oversized root .js/.ts that isn't a declared entry point
Repo config that auto-runs when you open the folder in an AI coding agent (the Miasma reaches Azure wave, 2026) agent/IDE-config scan: flags on-open execution in .devcontainer, .vscode/tasks.json, .claude hooks, .mcp.json

No single open-source CLI combined these into a one-command morning verdict across all these ecosystems β€” so here it is.

The AI-agent config trigger

The June 2026 Miasma wave (which had GitHub disable 73 Microsoft Azure repos) did not require installing a poisoned package. It planted repository config files that execute the moment a developer opens the folder in Claude Code, Cursor, Gemini CLI or VS Code β€” a devcontainer.json postCreateCommand, a VS Code task set to runOn: folderOpen, a Claude Code hook, or an .mcp.json server command. The scan flags exactly those auto-on-open execution points (and nothing as noisy as an ordinary settings.json), so you see them before your editor runs them.


Install

One-time: the scan engine

supply-chain-scan drives Google's free osv-scanner for the malicious/CVE layer. Install it once, globally:

OS Command
macOS / Linux brew install osv-scanner
Windows winget install Google.OSVScanner (or scoop install osv-scanner)
Any a release binary from osv-scanner/releases, or go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest

(If it's missing, the scan tells you the exact command for your OS and still runs the other checks.)

The tool β€” pick one

As an npm CLI (any editor / terminal):

npx supply-chain-scan [project-dir]      # no install
npm install -g supply-chain-scan         # or install globally

As a Claude Code plugin (adds a /supply-chain-scan skill to your morning routine):

/plugin marketplace add Flo5k5/supply-chain-scan
/plugin install supply-chain-scan@flo5k5-tools

Usage

supply-chain-scan [project-dir]      # defaults to the current directory

  --fresh-days <N>   flag deps published less than N days ago   (default 3)
  --max-js-mb <N>    flag undeclared root JS/TS files larger than N MB (default 0.5)
  --no-recursive     scan only the root dir, not nested packages (recursive by default)
  --depth <N>        max directory depth for the recursive scan (default 5)
  --no-agent-configs skip the agent/IDE auto-exec-on-open check (on by default)
  --images           also pull & scan Docker base images for CVEs (slow, opt-in)
  --stack <file>     deployed-component inventory (auto-detects stack.yaml at the root)
  --no-stack         skip the deployed-stack check even if an inventory exists
  --no-kev           skip the CISA KEV (known-exploited) cross-check
  --no-state         report every deployed-stack advisory every run (no delta)
  --reset-state      drop the per-project delta state, then re-baseline
  --json             machine-readable output
  --no-color         disable ANSI colors
  -h, --help / -v, --version

Exit codes (scriptable): 0 clean Β· 1 review (findings) Β· 2 setup (osv-scanner not installed).

Every CVE the scan reports is cross-checked against the CISA KEV catalog (cached 24h): a match is shown as KNOWN EXPLOITED and outranks severity. A source that cannot be reached (KEV offline, OSV API down) is reported under blind spots β€” a clean verdict then means "clean among what could be checked", never silence. Likewise, a directory with nothing scannable in scope says so explicitly: absence of scope is not a clean bill of health.

Deployed stack (stack.yaml)

Lockfiles describe what a repo builds β€” not the pinned versions that actually run in production. Drop a stack.yaml next to the deploy config and the daily scan asks OSV.dev for each pin, reporting only advisories new since the last successful run (first run = baseline; state lives per project in ~/.cache/supply-chain-scan/):

# stack.yaml β€” deployed pins this scan must judge
components:
  - name: litellm
    version: "1.106.2"
    ecosystem: PyPI      # npm | PyPI | Go | crates.io | NuGet
  - name: follow-redirects
    version: "1.16.1"
    ecosystem: npm
β–Ά deployed stack β€” stack.yaml vs OSV.dev
  β„Ή 2 pinned component(s): litellm@1.106.2, follow-redirects@1.16.1
  βœ“ litellm@1.106.2 [PyPI] β€” no known advisories
  βœ— follow-redirects@1.15.0 [npm] β€” NEW GHSA-8r9p-f939-6h3c (aka CVE-2026-xxxx) [high], fixed in 1.16.1 β€” KNOWN EXPLOITED (CISA KEV)

(Docker images aren't OSV-queryable packages β€” keep them under the digest-pinning check, which the scan already runs over Dockerfiles and compose files.)

Monorepos (recursive by default)

The scan walks the workspace and picks up lockfiles and Dockerfiles in nested packages β€” package-lock.json/pnpm-lock.yaml/yarn.lock, requirements.txt/uv.lock/poetry.lock, go.sum, Cargo.lock and NuGet's packages.lock.json β€” skipping node_modules, build output and vendored dirs. Point it at the repo root and every package gets scanned in one pass (the header shows +N nested lock(s)). On a very large workspace this can be slow (osv-scanner time grows with the dependency tree) β€” bound it with --depth <N> or fall back to --no-recursive.

Example

πŸ”’ Supply-chain scan β€” acme-api
   /home/you/code/acme-api
────────────────────────────────────────────────────────────
   detected: npm(pnpm) Β· Docker(1 img)

β–Ά osv-scanner β€” known-malicious + CVE
  ⚠ 3 known vulnerabilities across 1 package(s):
      axios@1.6.0 (GHSA-8hc4-vh64-cxmj) [npm]
      …and 2 more
β–Ά audit β€” CVE advisories
  βœ“ pnpm: no high/critical advisories
β–Ά pinning / cooldown
  ⚠ npm: no minimumReleaseAge β€” add to pnpm-workspace.yaml: `minimumReleaseAge: 4320` (3 days)
β–Ά install-time protection β€” Aikido Safe Chain
  β„Ή not installed β€” this scan only judges a lockfile that already resolved; safe-chain blocks a malicious install in real time, before any preinstall/postinstall script runs
  β„Ή install once: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh
β–Ά freshness β€” deps changed recently & published < 3d ago (zero-hour)
  ⚠ left-pad@9.9.9 [npm] β€” published 1d ago (fresh; vet before trusting)
β–Ά Docker β€” base image pinning
  ⚠ 1/1 base image(s) NOT pinned by digest (mutable tag β€” can be re-pointed):
      node:22-alpine  (Dockerfile) β†’ pin to image@sha256:<digest>

────────────────────────────────────────────────────────────
⚠️  REVIEW β€” see the ⚠/βœ— lines above before installing or updating dependencies.

The one thing to configure per project: a release cooldown

The cheapest, highest-leverage defense is to refuse versions published in the last few days, so a compromised release is detected and pulled before it lands in your lockfile. The scan checks for it and offers the snippet:

  • pnpm β†’ pnpm-workspace.yaml: minimumReleaseAge: 4320 (3 days; pnpm 11 defaults this on at 1 day)
  • uv (Python) β†’ pyproject.toml [tool.uv]: exclude-newer = "2026-01-01T00:00:00Z"
  • Docker β†’ pin every FROM to a digest: FROM node:22-alpine@sha256:…

How it fits a layered defense

  1. Release cooldown (above) β€” keeps brand-new, most-likely-malicious versions out long enough to be caught.
  2. supply-chain-scan (this tool) β€” the morning gate: known-malicious + CVE + fresh-dep + pinning. Runs after an install has already happened, against whatever the lockfile resolved to.
  3. (optional, free) Aikido Safe Chain β€” the real-time layer. Wraps npm/yarn/pnpm/bun/pip/poetry and blocks a malicious install before its preinstall/postinstall script runs, closing the one gap a retrospective scan structurally cannot: the install that never gets a chance to finish. This tool checks whether it's installed and tells you if not.
  4. (optional, free) the Socket.dev GitHub App β€” behavioral detection of novel malware on every PR. This tool catches known and fresh; Socket adds the unknown-behavioral layer.

Privacy

No telemetry. The only network calls are public lookups: registry.npmjs.org / pypi.org for publish dates, and OSV.dev (via osv-scanner) for advisories. Nothing about your project is uploaded.

Roadmap

  • Auto-download a pinned, checksum-verified osv-scanner binary (--install-scanner).
  • Go (go.sum) and Rust (Cargo.lock) β€” shipped in 0.2.0 (osv-scanner parses them).
  • A GitHub Action / pre-commit recipe for teams that want this in CI too.

License

MIT Β© Flo5k5

About

A 5-second morning supply-chain safety check for npm/PyPI/Docker/Go/Rust: known-malicious packages, CVEs, zero-hour deps, release-cooldown & digest pinning, plus build-manifest and AI-agent-config auto-exec checks (Shai-Hulud/Miasma). Zero-dependency CLI + Claude Code plugin.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages