- A lightweight set of scripts that simulate common NPM supply‑chain attack behaviors in a controlled way. Useful for testing detections, pipelines, and endpoint logging without risking real secrets.
- Uses fake tokens, a local POST‑aware mock server by default, and safe no‑op requests.
- Now available for both Linux/macOS (bash) and Windows (PowerShell)!
🪟 Windows Users: Check out the Windows PowerShell version →
- Native PowerShell scripts
- Built-in HTTP server (no Python needed!)
- Auto-installs dependencies (Node.js, npm, git)
- One-command setup
🐧 Linux/macOS Users: Continue below for bash version
- macOS or Linux with bash/zsh
- Node.js and npm
- Python 3 (for the local mock server)
- git and curl
- Optional: Go (for Scenario 2 TruffleHog auto‑install), yarn/wget (for optional variations)
OS prerequisites (Ubuntu/Debian)
sudo apt-get update && sudo apt-get install -y nodejs npm python3 curl netcatFor Windows users, see the Windows guide →
- Clone and enter the repo
git clone https://github.com/MHaggis/NPM-Threat-Emulation.git
cd NPM-Threat-Emulation- Initialize the test environment
source ./setup_test_env.sh- Exports fake tokens, sets
MOCK_WEBHOOK, and starts a POST‑aware mock server on:8080if you are not using an external webhook.
- Verify the webhook endpoint responds
curl -s -o /dev/null -w "%{http_code}\n" -X POST "$MOCK_WEBHOOK" -d test=1
# expected: 200- Run a single scenario (1–9)
./scenarios/scenario_1.sh- Or run all scenarios (60s pause between each, 1-9)
./run_npm_emulation_tests.sh- Stop the mock server only (optional)
./stop_server.sh- Reset between test runs (cleans temp, npm cache, and stops local server)
./reset_between_tests.sh- Cleanup everything (remove artifacts, caches, and stop server)
./cleanup_test_env.sh- Default: Local mock server at
http://localhost:8080/webhook-receiverstarted bysetup_test_env.sh. - Use a live webhook URL (e.g.,
https://webhook.site/<uuid>):- Interactive:
./choose_webhook.sh source ./setup_test_env.sh - Direct and persisted:
source ./use_webhook_url.sh https://webhook.site/<uuid> source ./setup_test_env.sh
- Interactive:
- Payloads received by the local server are saved under
tmp/payload_*.binfor verification.
Pretty-print each payload (skips non-JSON and shows a short preview when needed):
for f in tmp/payload_*.bin; do
echo "--- $f ---"
python3 - "$f" <<'PY'
import sys, json
p=sys.argv[1]; d=open(p,'rb').read()
try:
j=json.loads(d.decode('utf-8', errors='ignore'))
print(json.dumps(j, indent=2))
except Exception:
print(f"[non-json] {len(d)} bytes; first 64 bytes:"); print(d[:64])
PY
doneSummarize phases observed:
for f in tmp/payload_*.bin; do
python3 - "$f" <<'PY' 2>/dev/null || true
import sys, json
d=open(sys.argv[1],'rb').read()
print(json.loads(d.decode('utf-8',errors='ignore')).get('phase'))
PY
done | sed '/^$/d' | sort | uniq -c- 1 Malicious Postinstall: triggers
postinstallthat POSTs toMOCK_WEBHOOK. Also tries curl/wget/yarn variations. - 2 TruffleHog Scan: downloads real TruffleHog binary from GitHub releases (mimics Shai-Hulud); scans fake secrets and posts structured payload with base64-encoded results.
- 3 Workflow Injection: drops
.github/workflows/shai-hulud-workflow.ymlduring an npm run; workflow posts toMOCK_WEBHOOK. - 4 Package Patching: appends a small payload into
node_modules/left-pad/index.jsduring install and attempts a POST to the local server. - 5 Multi‑Stage Download: downloads stage1 and stage2 to
/tmp, then deletes them. Uses externalMOCK_WEBHOOKwhen set; otherwise local mock server endpoints. - 6 Worm Propagation: simulates
npm publish --dry-runacross 5 packages usingNPM_TOKENfrom the fake token. - 7 Cloud Metadata Probe: probes AWS/GCP/Azure metadata endpoints with short timeouts.
- 8 Repo Weaponization: creates a repo and commits a
data.jsonthat includes fake tokens and env details. - 9 Bundle Worm Chain: repacks a tarball with
bundle.jsand spawns/tmp/processor.sh(or.ps1on Windows) plus/tmp/migrate-repos.shfor defenders to track.
💡 All 9 scenarios work on both Linux/macOS and Windows!
flowchart TD
A[Setup env] --> B{Webhook}
B -->|Local| C[Local mock server]
B -->|Live| D[External webhook]
A --> S1[Scenario 1: postinstall exfil]
A --> S2[Scenario 2: trufflehog scan]
A --> S3[Scenario 3: workflow injection]
A --> S4[Scenario 4: package patching]
A --> S5[Scenario 5: multi-stage download]
A --> S6[Scenario 6: worm dry-run]
A --> S7[Scenario 7: cloud metadata]
A --> S8[Scenario 8: repo weaponization]
S1 --> E[POST events]
S4 --> E
S5 --> E
S3 --> E
E --> C
E --> D
setup_test_env.shexportsFAKE_*tokens and will startmock_server.pyunless you configured a liveMOCK_WEBHOOK.- To confirm the local server is up:
curl -X POST "$MOCK_WEBHOOK" -d ping=1 # expected HTTP 200
- If Python 3 is not installed, the local server will not start. Provide a live
MOCK_WEBHOOKinstead (see above). - Some scenarios use optional tools (e.g., TruffleHog, yarn, wget). They are skipped gracefully when not present.
- Port 8080 busy: set a live
MOCK_WEBHOOKusing the scripts above and re‑sourcesetup_test_env.sh. .envnot written: the scripts still exportMOCK_WEBHOOKin your shell. If needed, export it manually, thensource ./setup_test_env.sh.- npm or git prompts: the scripts are designed to run non‑interactively and ignore failures where safe.
- All secrets used are fake and for testing only.
- If you set a live
MOCK_WEBHOOK, requests will be sent to that URL. Use a URL you control.
- 🪟 Windows (PowerShell): Windows README →
- 🐧 Linux/macOS (Bash): See above instructions
Both versions implement the same 9 attack scenarios with platform-native tooling!
