Package Exports
- installsentry
- installsentry/dist/cli.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (installsentry) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
InstallSentry
Supply-chain blast-radius visualizer that traces npm install-time lifecycle scripts, file/network access, and secret-canary reads.
At a glance
- Problem:
npm installcan run lifecycle scripts in dependencies with access to your filesystem, env, and network. That risk is opaque if you only readpackage.jsonat the top level. - What this is: A CLI that (1) parses your
package-lock.jsonv3 into a graph, (2) runs a sandboxednpm install(host or optional Docker) with canary secrets, (3) loads a Node shim (NODE_OPTIONS=--require) to log I/O, (4) writes an HTML report and optional SARIF 2.1.0, and (5) an optional install-time gate (--ci) with strict or allowlisted network policy. See the threat model for what is and is not guaranteed.
Try it (from npm, after publish):
npx installsentry@latest scan ./path-to-your-app
npx installsentry@latest run ./path-to-your-app -o report.htmlOr from a clone (contributors / latest main):
git clone https://github.com/anasm266/installsentry.git && cd installsentry
npm ci && npm run build
node dist/cli.js scan ./path-to-your-app
node dist/cli.js run ./path-to-your-app -o report.htmlLimitations (read before trusting it for production): only package-lock.json lockfileVersion 3; npm (not pnpm / Yarn for the lockfile layer yet); per-event package is inferred from process.cwd() under the install root (not perfect if a script chdirs; see adversarial notes); the tool is observational—see docs/THREAT-MODEL.md. For --ci, the default is no network unless you set --allow-hosts (or a config file); that keeps the gate useful on real projects that hit the registry. For a deliberate exfil test, see tests/fixtures/malware-demo/ and docs/samples/ for static HTML and policy examples.
Security & responsible use: The malware-demo fixture is for local testing and learning on machines you are allowed to use. Do not point this tool (or the fixture) at systems you do not own, and do not use it to develop or deliver harmful code. InstallSentry is an educational / research tool; it is not a substitute for a commercial malware scanner, dependency policy, or formal supply-chain program.
Example: HTML report (malware-demo)
Full-page view of a generated report in the browser. The Secret Canary panel shows a fake AWS canary in an outbound request URL; Network includes registry traffic and the example.com probe; the right pane is the Cytoscape dependency graph. The file below is committed in this repository as docs/images/report-example.png so it always renders on GitHub.
To regenerate the screenshot locally: npm run docs:screenshot (uses the playwright devDependency; on Linux/macOS run npx playwright install chromium once. On Windows, the script uses the system Edge browser.)
What it does
Every time you run npm install, packages can execute lifecycle scripts (preinstall, install, postinstall) with full access to your environment, filesystem, and network. InstallSentry tells you exactly what happened inside that black box.
- Scans
package-lock.jsonfor packages with lifecycle scripts - Sandboxes
npm installin a disposable temp directory with fake secrets - Traces
fs.readFile,http/https(includingget),child_process.spawncalls via a runtime shim; canary substrings in outbound request URLs are treated as secret exfil - Detects if any package reads your fake canary tokens (npm, AWS, GitHub, SSH)
- Maps every suspicious event back to the root dependency that introduced it
- Reports everything in a single interactive HTML file with dependency graph, timeline, and blast-radius paths
- Gates CI with
--ciflag — fails the build if secrets are touched or unauthorized network calls are made
Demo
# Scan a project for lifecycle scripts
npx installsentry scan ./my-project
# Run sandboxed install and generate report
npx installsentry run ./my-project --output report.html
# CI mode (strict: any network egress or secret hit fails)
npx installsentry run ./my-project --ci
# CI with a registry allowlist and SARIF for GitHub Advanced Security or upload-artifact
npx installsentry run ./my-app --ci --allow-hosts "registry.npmjs.org" -o report.html --sarif results.sarifMalicious demo fixture (canary exfil)
The repo includes tests/fixtures/malware-demo/: a root project with a file: dependency whose postinstall reads AWS_SECRET_ACCESS_KEY and issues an HTTPS request with the canary in the query string. The shim flags canary substrings in outbound URLs as Secret Canary hits (and still logs Network Egress). After npm run build:
node dist/cli.js run tests/fixtures/malware-demo -o malware-report.html
node dist/cli.js run tests/fixtures/malware-demo --ciThe last command exits with a non-zero status: both secret exfil and network are detected. Traces attribute events to a lockfile path (e.g. node_modules/malice-local) from current working directory under the install root, so the Blast radius panel and graph can highlight the responsible package.
Architecture
┌─────────────────┐ ┌──────────────┐ ┌─────────────┐
│ package-lock │────▶│ Lockfile │────▶│ Dependency │
│ parser │ │ Parser │ │ Graph │
└─────────────────┘ └──────────────┘ └─────────────┘
│
┌─────────────────┐ ┌──────────────┐ │
│ HTML Report │◀────│ Analyzer │◀───────────┘
│ (Cytoscape.js) │ │ (blast radius│
└─────────────────┘ │ + risk score)│
└──────────────┘
▲
│
┌─────────────────┐ ┌──────────────┐
│ Secret Canary │ │ Sandbox │
│ Detection │◀────│ + Shim │
└─────────────────┘ │ (fs/net/cp) │
└──────────────┘How it works
- Lockfile analysis — Parses
package-lock.jsonv3 into a directed acyclic graph. Identifies which packages define npm lifecycle scripts the tool tracks (e.g.preinstall/install/postinstall/prepack/postpack/prepublishOnly/prepareand related hooks—seeLIFECYCLE_SCRIPT_NAMESinsrc/graph.ts). - Sandboxed Install — Creates a temp directory, copies
package.json+package-lock.json, and runsnpm installwith:- Fake environment variables (
NPM_TOKEN,AWS_ACCESS_KEY_ID,GITHUB_TOKEN,SSH_PRIVATE_KEY) containing canary strings - A Node.js runtime shim injected via
NODE_OPTIONS=--requirethat monkey-patchesfs,http,https, andchild_processto log every call to a JSONL trace file
- Fake environment variables (
- Trace Analysis — Reads the JSONL trace, detects canary reads, network egress, file writes, and subprocess spawns
- Blast Radius Mapping — For every suspicious package, walks the dependency graph backward to find the root dependency that transitively introduced it
- Report Generation — Produces a single self-contained
installsentry-report.htmlwith:- Interactive dependency graph (Cytoscape.js)
- Secret canary alerts
- Network egress log
- Blast-radius dependency paths
- CI gate status
Installation
Published package (Node 20+):
npm install -g installsentry
# or, no global install:
npx installsentry@latest <command>From source: clone, npm ci && npm run build, then node dist/cli.js … (see At a glance).
Usage
Scan
List all packages with lifecycle scripts:
installsentry scan ./my-projectRun
Perform a sandboxed install and generate a report (HTML by default, optional SARIF).
installsentry run ./my-project -o report.html
# Optional: SARIF 2.1.0, Docker runner, host allowlist
installsentry run ./my-project -o report.html --sarif out.sarif --runner host
installsentry run ./my-project -o report.html --runner docker --docker-image node:20-bookworm-slimDocker runner: requires Docker on PATH. The default image is node:20-bookworm-slim. The same temp project copy and trace file as the host path are used; the container only runs npm install. On Windows, use Docker Desktop and allow the temp drive to be shared if installs fail to find files.
Network policy and CI mode
- Default (no allow list): any outbound HTTP(S) in the trace fails
--ci(in addition to secret canary hits). Use this for maximum strictness in fully offline or pre-vendorized scenarios. - Allow list:
installsentry run ... --ci --allow-hosts "registry.npmjs.org"(comma-separated) so normal registry access does not fail the gate. You can set the same list in a project file:.installsentry.yaml,.installsentry.json, orinstallsentry.json(see docs/samples/example.installsentry.yaml). - Deny list:
--deny-hosts(orci.denyHostsin config) always fails matching hosts even if allowlisted.
installsentry run ./my-project --ci
installsentry run ./my-project --ci --allow-hosts "registry.npmjs.org"Security documentation and samples
- Threat model and limitations
- docs/samples/ — regenerate example HTML, policy template, and SARIF notes
Project structure
installsentry/
├── .github/
│ └── actions/installsentry/ # Composite “run this repo’s CLI” (needs prior build in workflow)
├── docs/
│ ├── THREAT-MODEL.md
│ └── samples/ # Regenerated report + policy / SARIF notes
├── src/
│ ├── cli.ts, config.ts, network-policy.ts, sarif.ts, install-runner.ts, docker-runner.ts, …
│ ├── lockfile.ts, graph.ts, sandbox.ts, report.ts, analyzer.ts, tracer.ts, types.ts, shim.cjs, …
├── tests/
│ ├── fixtures/ … malware-demo, lifecycle-coverage, adversarial/chdir-demo, …
│ └── *.test.ts
└── package.jsonDevelopment
# Install dependencies
npm install
# Build TypeScript
npm run build
# Watch mode
npm run dev
# Run tests (watch)
npm test
# CI-style one-shot: build, optional test fixture deps, vitest run, then typecheck
npm run test:allCI (GitHub Actions)
On every push/PR to main or master, the workflow in .github/workflows/ci.yml runs npm ci, npm run test:ci, and npm run lint on Node 20 and 22 (ubuntu-latest). For a visual overview of the same steps:
flowchart LR
push[push_or_PR]
gha[GitHub_Actions]
install[npm_ci]
test[test_ci]
pass[pass_or_fail]
push --> gha --> install --> test --> passReusable “run on a project” action (in this repository)
In a workflow in this repo (or after checkout in another repo that contains a built dist/), you can use the composite action to run the CLI. Example: build first, then analyze a fixture.
# Example (conceptual) — your paths may differ; requires Node and a built `dist/`
# See .github/actions/installsentry/action.yml for inputs: project-path, output, ci, allow-hosts, sarif-output, runner, docker-image
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci && npm run build
- uses: ./.github/actions/installsentry
with:
project-path: tests/fixtures/malware-demo
output: installsentry-out.html
ci: "false"For SARIF upload to GitHub Code Scanning, add a job step with github/codeql-action/upload-sarif (requires default security-events: write permission) and point to the sarif-output path. Published packages can instead run npx installsentry@<version> run … in a standard run step.
Publishing to npm (maintainers)
Full checklist: RELEASING.md. Changelog: CHANGELOG.md.
Short version: installsentry is available on the public registry (verify with npm view installsentry). Log in with npm login, run npm run test:all, then npm publish --access public from a clean tree. prepublishOnly runs test:ci so broken builds are blocked. After publish, tag v*.*.* in git and create a GitHub Release.
Why this exists
npm lifecycle scripts execute during npm install with the same privileges as the developer's shell. Supply-chain attacks have used postinstall scripts to steal environment variables, exfiltrate data, and drop malware. InstallSentry makes this attack surface visible, traceable, and gateable in CI.
Roadmap (high level)
- Network allow / deny and config file; SARIF; Docker install runner; threat model; samples; local composite action; expanded lifecycle names in the graph; adversarial fixture scaffolds
- pnpm / Yarn lockfile support
- Deeper per-package tracing (e.g. child
nodewith shim, or documented limits only) - Optional: standalone
installsentry-actionrepo that wrapsnpxwithout a checkout
License
MIT