DEV Community

Cover image for I Built a Zero-Dependency Supply-Chain Security Scanner for Node.js — 21 Checks, One Command
Satyendra Vemulapalli
Satyendra Vemulapalli

Posted on • Edited on • Originally published at github.com

I Built a Zero-Dependency Supply-Chain Security Scanner for Node.js — 21 Checks, One Command

The npm install trust problem

When you run npm install, you're trusting hundreds of strangers to not run malicious code on your machine. And that trust has been broken — repeatedly.

Packages like event-stream, ua-parser-js, and coa were hijacked to steal credentials, install backdoors, and exfiltrate data through postinstall scripts. By the time npm install finishes, the damage is already done.

The traditional answer? npm audit. But that only catches known CVEs. It completely misses:

  • Typosquatted packages hiding in your lockfile
  • Dropper packages that curl | sh in postinstall
  • Tampered package.json files swapped after install
  • C2 domains hardcoded in dependencies
  • Stolen npm tokens used to publish without CI/CD provenance

I needed something better. So I built it.


Meet sec-check — one command, 21 security checks, zero dependencies

npx @sathyendra/security-checker
Enter fullscreen mode Exit fullscreen mode

That's the entire setup. No config files, no API keys, no build step. It scans your project and tells you what's wrong:

──────────────────────────────────────────────────────────────────────
  @sathyendra/security-checker — Diagnostic Report
──────────────────────────────────────────────────────────────────────
  🚨 CRITICAL: plain-crypto-js detected in node_modules       [FIXABLE]
  🚨 LOCKFILE: flatmap-stream found in dependency tree         [FIXABLE]
  🚨 DROPPER: suspicious-pkg has postinstall with curl | sh   [MANUAL]
  ⚠️  PROVENANCE: "axios@1.7.0" — Manual Publish Detected      [MANUAL]
  ⚠️  OUTDATED: lodash@3.10.1 — 2 major versions behind        [MANUAL]
──────────────────────────────────────────────────────────────────────
  5 threat(s) found | 2 fixable | 3 require manual review
  Run with --fix to auto-remediate fixable threats.
──────────────────────────────────────────────────────────────────────
Enter fullscreen mode Exit fullscreen mode

Why zero dependencies?

A security tool that pulls in 200 transitive dependencies is an oxymoron. Every dependency is attack surface. sec-check uses only Node.js built-ins (fs, path, crypto, child_process, os, https) — nothing from npm. The tool that checks your supply chain has no supply chain of its own.


What does it actually check?

Here's the full list of 21 detection modules:

# Check What it catches
1 Malicious packages Known bad packages (e.g. plain-crypto-js) in node_modules
2 npm audit High/critical CVEs
3 Deep lockfile audit Malicious packages buried in the full dependency tree
4 Dropper detection Packages with postinstall scripts but no real code — the "install-and-run" pattern
5 Integrity checksums Post-install tampering (hash mismatch vs lockfile and registry)
6 Decoy swap detection package.md, .bak artifacts — the exact trick used in the Axios attack
7 TeamPCP / WAVESHAPER RAT artifacts, persistence mechanisms across Windows/macOS/Linux
8 C2 domains Known command-and-control domains in your hosts file
9 Cross-ecosystem (PyPI) Malicious PyPI packages from the same campaigns
10 Python stager detection Suspicious .py files with backdoor patterns in Node.js projects
11 Malicious .pth files Python's "importless execution" technique in site-packages
12 Provenance verification Popular packages published without CI/CD provenance (stolen token indicator)
13 Shadow execution LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS --require hijacking
14 Outdated dependencies Major version drift = no security patches
15 Registry configuration Dependency Confusion via non-official or HTTP registries
16 Lifecycle script injection `curl
17 Dependency script sandboxing Same analysis on every dependency's lifecycle scripts
18 Secrets detection {% raw %}.env files, hardcoded NPM_TOKEN, AWS keys, GitHub tokens, PEM keys
19 Package cache IOC scan Scans ~/.npm, ~/.yarn/cache, ~/.pnpm-store for known-malicious package names
20 Phantom dependency detection Finds packages your code require()s or imports that aren't declared in package.json
21 Dist/build output scan Scans dist/, build/, out/, lib/ for IOC strings and obfuscation patterns

Plus bonus checks: npm doctor, lockfile enforcement, SSRF/C2 blocklist scan, environment variable audit, and Lockfile Sentinel (hash comparison against known-compromised database).


The killer feature: Zero Trust Shield

Instead of the traditional "install and hope" workflow, Shield gives you defense-in-depth:

sec-check --shield
Enter fullscreen mode Exit fullscreen mode
  ▸ Stage 1: Pre-flight — scan lockfile & config BEFORE downloading anything
  ▸ Stage 2: Isolated Install — npm install --ignore-scripts (no code execution)
  ▸ Stage 3: Post-vetting — full integrity & security scan on downloaded files
Enter fullscreen mode Exit fullscreen mode

This is the workflow that would have caught every major npm supply-chain attack. The malicious postinstall never runs because Stage 2 uses --ignore-scripts, and Stage 3 catches any artifacts before you run your code.


OWASP Top 10 mapping

Three Shield features map directly to OWASP risk categories, so you can reference them in compliance conversations:

OWASP Risk Feature What it does
A03: Injection Script Blocker Flags dangerous commands in lifecycle hooks before execution
A05: Misconfiguration Registry Guard Rejects non-official or HTTP registries across .npmrc, env, and lockfile
A08: Integrity Failures Lockfile Sentinel Compares lockfile hashes against known-compromised database

CI/CD integration in 30 seconds

Add one line to your GitHub Actions workflow:

- name: Security scan
  run: npx @sathyendra/security-checker
Enter fullscreen mode Exit fullscreen mode

Exit code 1 = threats found = pipeline fails. That's it.

For structured output (dashboards, artifact collection):

sec-check --json > scan-results.json
Enter fullscreen mode Exit fullscreen mode

For compliance artifacts:

sec-check --vex-out   # CycloneDX VEX document
sec-check --sbom      # CycloneDX SBOM
Enter fullscreen mode Exit fullscreen mode

Auto-fix what can be fixed

Not into manual cleanup? The --fix flag handles the boring stuff:

sec-check --fix
Enter fullscreen mode Exit fullscreen mode
Threat What --fix does
Malicious packages npm uninstall <package>
Audit vulnerabilities npm audit fix
Integrity mismatches npm ci (clean reinstall)
Swap artifacts Deletes the artifact file

Threats that need human judgment (provenance issues, C2 indicators, system artifacts) stay as [MANUAL] — the tool never makes destructive decisions for you.


Contribute threat intelligence

Found a suspicious package that sec-check doesn't know about yet? Add it locally or submit it to the community database:

# Add to your project's local IOC list
sec-check --add-ioc <package-name>

# Pre-fill a GitHub issue to submit to the community database
sec-check --submit-ioc <package-name>

# Same, but open the issue URL automatically in your browser
sec-check --submit-ioc <package-name> --open

# Submit multiple patterns at once (interactive batch mode)
sec-check --submit-ioc-batch
Enter fullscreen mode Exit fullscreen mode

Project-local IOCs are stored in .sec-check-ioc.json. Commit it to share threat intelligence with your whole team without waiting for a database update.


Quick start

# One-shot scan (no install needed)
npx @sathyendra/security-checker

# Install as dev dependency
npm install --save-dev @sathyendra/security-checker

# Auto-fix fixable threats
sec-check --fix

# Protect every future install
sec-check --init

# Full Zero Trust workflow
sec-check --shield
Enter fullscreen mode Exit fullscreen mode

Try it now

The best way to evaluate it is to point it at your own project:

cd your-project
npx @sathyendra/security-checker
Enter fullscreen mode Exit fullscreen mode

If it finds something, that's a good thing — you just caught it before an attacker exploited it.

Links:


If this is useful, a ⭐ on GitHub or an npm i helps more than you'd think. Questions, feature requests, or "it flagged something weird" — open an issue, I read all of them.

Top comments (2)

Collapse
 
rehman_00001 profile image
Abdul Rehman

Cool project! I was building something similar on my own, but focused just on scanning through node_modules for a specific affected package version (ex: axios@1.41.0)

Just one doubt! Does this also scan through npm/yarn/pnpm cache folders? peer/phantom dependencies, or other indirect dependencies that are not listed in package.json, but are bundled inside the dist/bin folder?

Collapse
 
sathyendrav profile image
Satyendra Vemulapalli

Thanks so much — and great minds think alike! 😄 Scanning for specific affected versions is a really practical use case. You might want to check out the --sbom flag which inventories all installed packages with their exact versions in CycloneDX format — could complement what you're building.

To answer your questions directly — yes, the latest release (v1.24.0, just shipped today!) now covers all three:

npm/yarn/pnpm cache folders — Check #19 scans ~/.npm/_cacache, ~/.yarn/cache, and ~/.pnpm-store for package names matching the malicious IOC list. The risk is that a malicious package can be re-installed silently from a warm cache even after you've removed it from node_modules.

Phantom/ghost dependencies — Check #20 walks your project source files (.js, .mjs, .ts, etc.) and flags any require()/import specifiers that aren't declared in package.json under any dependency field. It filters out all Node.js built-ins. These are dangerous because they rely on a transitive dep pulling them in — remove or update that transitive dep and your code silently breaks, or worse, an attacker claims that package name on the registry.

Bundled dist/bin content — Check #21 scans your own dist/, build/, out/, and lib/ output for known malicious package names embedded as string literals, known C2 domains in URLs, and obfuscation patterns (eval(atob(...)), execSync(Buffer.from(...)) chains). This catches supply-chain injection into your build pipeline before you publish.

All zero-dependency, just:

npx @sathyendra/security-checker
Enter fullscreen mode Exit fullscreen mode

Would love to see what you're building too — feel free to open an issue or PR if you want to contribute version-pinned affected-package detection!