sec-npm
v1.1.1
Published
Zero-Trust npm wrapper: audits packages (metadata, AST, import-time shadow execution) for supply-chain malware before install
Readme
🛡️ sec-npm
sec-npm is a zero-trust wrapper around npm install. Before anything touches your node_modules, it audits the package across static analysis, import-time shadow execution, and registry metadata to catch malware — including zero-days that npm audit cannot see.
[!TIP] Generic, not single-campaign. The engine applies the same heuristics to every package — obfuscation, exfil/network behavior, registry anomalies, OSV history. The joyfill/DEV#POPPER RAT was the first case it was hardened against, but the tool is not scoped to it: known-campaign markers live in an extensible threat feed (
lib/signatures.js) that grows with each new disclosure, and the heuristic layers detect novel implants on their own.
[!IMPORTANT] Why sec-npm? Tools like
npm auditonly match known vulnerabilities.sec-npmhunts zero-day implants: it de-obfuscates code, runs entry files in a sandboxednode:vmwhere host modules are traced stubs, checks for registry anomalies (version floods, prerelease-as-latest), queries the OSV known-malicious database, and blocks before install.
✨ Features
🎭 Import-Time Shadow Sandbox
Package entry files (from main/module/exports/bin) are executed inside a node:vm context built on traced Proxies:
fs,child_process,http(s),net,dns,tls,processare stubbed/blocked and logged — a network call or file write triggers an instant block.global.r = require-style primitive stashing is detected.- Honeytrap decoys (fake SSH keys,
.env) catch credential exfil attempts. Buffer.from(..., 'base64/hex')decodes are logged and fed back into signature detection.- ESM entry bundles are transparently transformed (
lib/esmTransform.js) and run through the same sandbox.
[!NOTE] Execution happens in-process inside
node:vmwith every host builtin replaced by a guarded stub. This is a containment boundary, not a full OS sandbox (no Docker). Untrusted code that runs as the same OS user still warrants a throwaway environment for truly hostile specimens.
🔍 Deep AST Static Analysis
acorn parses every .js/.cjs/.mjs file (symlinks and node_modules skipped) and flags:
- Known C2/IOC matches (domains, IPs) and blockchain C2 endpoints (Tron/Aptos/BSC RPCs — e.g. the
@joyfillRAT campaign). - Detached child processes (
child_process.spawn/exec/fork— including member-expression forms), runtimenpm install, dynamiceval/Functionchains. constructorproperty access, string-decoder/truncation payload tricks, high-entropy strings and identifiers.- Sensitive module imports and
process.envscraping.
🪤 Active Defense (Honey-Traps)
The sandbox ships with decoy credentials. Any attempt to read them triggers a critical alert.
🕒 Registry Anomaly Detection (Time-Travel)
Metadata history is scanned for:
- Version floods (≥ 50 same-base versions published within a 72h window — e.g. the 2,773-version
@joyfillflood; time-aware so legit slow-cadence prereleases are not flagged). - Campaign version markers — versions matching a known malicious batch pattern (e.g. joyfill's
-2773-beta.*) are instant-blocked even after the registry scrubs the code. Extensible inlib/signatures.js. - Prerelease-as-latest and tarball size anomalies.
- Sudden
postinstall/preinstallscript additions.
☁️ OSV Known-Malicious Lookup
Queries api.osv.dev (free, no API key) so known-malicious packages are rejected even before static analysis. Disable with --no-osv. Override the endpoint with SEC_NPM_OSV_URL.
🔒 Safe Installation
sec-npm iinstalls vianpm installas a child process; on win32 thenpm.cmdshim needs a shell, so the install target is character-whitelisted ([a-zA-Z0-9@/._~-]) before it ever reaches the command line — the registry-controlled name/version cannot inject shell syntax.- Installs the exact version that was audited (
pkg@auditedVersion) — no TOCTOU window where the registry serves a different, newer tarball. --forceis the only escape hatch; without it, a danger-level audit aborts the install.
🧠 Extensible Threat Feed
Known-campaign signatures live in lib/signatures.js: C2 IOCs, blockchain endpoints, persistence targets, and campaign version markers. When the next disclosure lands, append its markers to that one file — the analyzer, sandbox, and scanner all read from it. The heuristic layers above keep detecting novel implants without any signature.
🛠️ Usage
1. Secure Installation
sec-npm i <package-spec>
sec-npm install <name>@<version> # audits exactly this version, then installs itFlags: -d/--detailed, -t/--threshold <score> (default 50), -f/--force, --no-osv.
2. Deep Security Audit
sec-npm check <package-spec> -dFlags: -d/--detailed, -t/--threshold <score>, --json, --no-osv.
3. Quick Static Scan
sec-npm scan <package-name>Flags: --json.
Environment Variables
| Variable | Purpose |
| :--- | :--- |
| SEC_NPM_REGISTRY | Override registry base URL (default https://registry.npmjs.org) |
| SEC_NPM_OSV_URL | Override OSV API base URL (default https://api.osv.dev/v1/querybatch) |
⚖️ Comparison
| Feature | Standard npm | sec-npm | | :--- | :---: | :---: | | Known vulnerabilities (OSV) | ✅ | ✅ | | Zero-day / heuristic detection | ❌ | ✅ | | Import-time shadow execution | ❌ | ✅ | | Typosquatting shield | ❌ | ✅ | | Registry anomaly detection | ❌ | ✅ | | Honey-trap deception | ❌ | ✅ | | Install-time overhead | None | ~1s |
🧪 Tests
npm test # hermetic suite (fixtures + local mock registry, no external calls)
npm run test:live # optional live test against the malicious @joyfill/layouts specimen