Static, offline risk triage for npm packages. Point it at a package and it tells you what the package does at install time and what its install code can reach — without executing anything, without touching the network, and without an account. A triage signal for humans and CI, not a verdict of malice.
$ pkgpeek ./some-package
pkgpeek — cool-utils@1.0.3 (1 dependency)
verdict: HIGH
- postinstall script: runs a local file: scripts/setup.js; that file reaches:
child_process; network; environment / secrets; base64-decoded blob
install scripts
[HIGH] postinstall
$ node ./scripts/setup.js
- runs a local file: scripts/setup.js
- that file reaches: child_process
- that file reaches: network
- that file reaches: environment / secrets
- that file reaches: base64-decoded blob
npm runs a package's lifecycle scripts (preinstall, install, postinstall,
prepare) automatically on npm install, before any of its code is imported.
That is the single most abused supply-chain vector — steal a token, phone home,
drop a binary — and nothing about a normal npm install shows it to you.
pkgpeek surfaces exactly that, and it does it the way the other tools in this space don't:
- Local & offline. No cloud, no account, no sending your dependency tree to a SaaS. (Socket is cloud; OpenSSF package-analysis is heavy dynamic infra.)
- Behavior, not just CVEs.
npm audit/osv-scannertell you about known vulnerabilities; pkgpeek tells you what a package does. They compose well. - A single fast binary, CI-native (JSON output, exit codes).
pkgpeek <PACKAGE_DIR> # human-readable triage
pkgpeek <PACKAGE_DIR> -o json # machine-readable JSON
pkgpeek <PACKAGE_DIR> --fail-on high # exit non-zero only on HIGHExit code: 0 below the --fail-on threshold (default medium), 1 at or
above it, 2 on error.
This is an early, deliberately narrow tool.
- npm only for now (pip and cargo, and an "audit an MCP server" mode, are on the roadmap).
- Install-time execution is the headline signal, backed by a real AST pass
(via the
oxcparser) over the code an install hook runs — so arequire('child_process')counts but the same text in a comment or string does not. - Typosquatting is flagged when a package name (or a dependency) is one edit —
including a transposition — from a well-known package (
expresss,lodahs), checked against a bundled list so it stays offline. - Obfuscation is flagged from its form: machine-generated hex identifiers
(
_0x…), long encoded string blobs, and dense escapes — the backstop for when capability detection is deliberately evaded. - Package-wide inventory. Beyond the install script, every source file in the
package (skipping
node_modules) is scanned, so a payload hidden in themainentry or any other module — the code that runs onrequire()— still shows up. The capability inventory is informational; obfuscated package code raises the verdict only at high confidence, so legitimate minified bundles don't cry wolf. - Still to come: the pip / cargo ecosystems, and an "audit an MCP server" mode.
- Static and offline by design. No code is executed; the network is never touched, so registry-only signals (maintainer changes, download counts) are out of scope until an explicit, opt-in online mode.
- A triage signal, not proof. Legitimate packages run install scripts and use the network too. pkgpeek tells you where to look, not what to conclude.
Dual-licensed under either of MIT or Apache-2.0, at your option.