
A Public Package Scanner for The Community
Blazingly simple, Docker‑first npm supply‑chain scanner. One compose file runs:
This is the container‑only edition. The project can be built to scale using EC2, SQS and RDS. Most of it is setup for it in the toolset.
scan.yml (allowlists, thresholds, YARA)scan_runs) ready to go~/.aws)docker-compose.yml – services: db, enumerator, fetcher, analyzer, dashboard, init-dbenumerator/ – Node worker that builds the NDJSON queuefetcher/ – Node worker that downloads tarballs (+ uploads to S3 if enabled)analyzer/ – Python static analyzer (+ optional YARA inline)dashboard/ – Streamlit app (port 8501)infra/migrations.sql – core DB schema (packages, versions, findings, scores, indexes)infra/20251106_scan_runs.sql – scan history tablescan.yml – analysis configuration (rules, scoring, allowlists, YARA)scripts/run_pipeline.sh – run enumerate → fetch → analyzescripts/init_db.sh – bootstrap DB schemascripts/test_setup.sh – automated setup validationSCANNING_GUIDE.md – detailed scanning strategies and examplesPrereqs: Docker Desktop (or engine) with Compose v2.
curl -fsSL https://raw.githubusercontent.com/MHaggis/Package-Inferno/main/install.sh | bash
This clones the repo to ~/package-inferno and gives you instructions to get started.
Pull and run pre-built containers from GitHub Container Registry:
# Clone the repo (for config files and scripts)
git clone https://github.com/MHaggis/Package-Inferno.git
cd Package-Inferno
# Run with pre-built images
docker compose -f docker-compose.ghcr.yml up -d db
./scripts/init_db.sh
SEEDS="lodash,express" docker compose -f docker-compose.ghcr.yml run --rm enumerator
docker compose -f docker-compose.ghcr.yml run --rm fetcher
docker compose -f docker-compose.ghcr.yml run --rm analyzer
Available images:
ghcr.io/mhaggis/package-inferno/enumerator:mainghcr.io/mhaggis/package-inferno/fetcher:mainghcr.io/mhaggis/package-inferno/analyzer:mainRun the test script to validate your installation:
./scripts/test_setup.sh
This will:
docker compose up -d db
./scripts/init_db.sh
./scripts/run_pipeline.sh
docker compose up -d dashboard
# open http://localhost:8501
Findings land under ./out/findings/*.findings.json and in the findings table when DB is enabled.
PackageInferno supports multiple scanning strategies depending on your goals:
| Mode | Use Case | Speed | Coverage | Command |
|---|---|---|---|---|
| Specific Seeds | Test/investigate known packages | Fastest | Targeted | SEEDS="pkg1,pkg2" |
| Small Batch | Validate setup, sample scan | Fast | 10-100 pkgs | MAX_CHUNKS=2 CHUNK_LIMIT=10 |
| Full Registry | Comprehensive supply chain audit | Hours-Days | 2M+ pkgs | MAX_CHUNKS=0 CHUNK_LIMIT=100 |
| Changes Feed | Monitor new releases (included automatically) | Real-time | Recent updates | Built-in |
Target specific packages you want to analyze:
# Single command with seeds
export SEEDS="lodash,express,axios"
./scripts/run_pipeline.sh
# Or from a file
echo -e "react\nvue\nangular" > packages.txt
export SEEDS_FILE=packages.txt
./scripts/run_pipeline.sh
How I tested initially: Used SEEDS="is-odd,is-even" for quick validation.
Scan packages paginated from npm's registry:
# Clean previous runs
rm -rf downloads/* out/*
# Scan 2 pages of 10 packages each (20 packages)
export MAX_CHUNKS=2 # Number of pages
export CHUNK_LIMIT=10 # Packages per page
unset SEEDS # Important: disable seeds mode
# Run individual steps for better visibility
docker compose run --rm enumerator # Discovers and queues
docker compose run --rm fetcher # Downloads tarballs
docker compose run --rm analyzer # Scans for threats
Example output:
config: chunkLimit=10, maxChunks=2
checking recent changes feed...
changes feed: enqueued 2 new versions
enumerating via _all_docs (fresh scan)
page 1/2 count: 10
page 2/2 count: 10
done, enqueued 22 (22 new versions)
Scan the entire npm registry:
export MAX_CHUNKS=0 # 0 = unbounded
export CHUNK_LIMIT=100 # Larger batches for efficiency
./scripts/run_pipeline.sh
Warning: This will run for hours/days and scan hundreds of thousands of packages. Monitor disk space and database size.
The enumerator saves state to ./out/enumerator_state.json with cursor position:
{
"last_seq": "0",
"last_startkey": "package-name",
"last_run": "2025-11-23T19:24:49.123Z",
"last_processed": 22,
"last_new": 22
}
Simply re-run the pipeline and it will resume from the last cursor:
./scripts/run_pipeline.sh # Automatically resumes
To force a fresh scan:
rm -f out/enumerator_state.json
./scripts/run_pipeline.sh
From a 2-page scan of 22 packages, here's what PackageInferno detected:
-- Top suspicious packages by score
SELECT p.name, s.score, s.label, COUNT(f.id) as findings
FROM packages p
JOIN versions v ON p.id = v.package_id
JOIN scores s ON v.id = s.version_id
LEFT JOIN findings f ON v.id = f.version_id
GROUP BY p.name, s.score, s.label
ORDER BY s.score DESC;
-- Results:
name | score | label | findings
-----------------------+-------+------------+----------
rendition | 606 | malicious | 153
vs-deploy | 454 | malicious | 119
--123hoodmane-pyodide | 213 | malicious | 46
What made rendition so suspicious?
url_outside_allowlist - Non-allowlisted domainssuspicious_pattern - Shell/eval patternsadvanced_obfuscation - Hex encoding, XOR, string arraysbig_base64_blob - Large encoded payloadsurl_in_code - Embedded URLsThe scoring system (configured in scan.yml) aggregates these findings to produce a risk score and label (clean, suspicious, or malicious).
Open http://localhost:8501 after running docker compose up -d dashboard
Features:
Direct SQL access for custom analysis: