Servidor MCP para verificar dependencias npm y PyPI de forma deterministica.
Project description
Detector de Paquetes Fantasma
Servidor MCP que verifica dependencias npm y PyPI antes de instalarlas. Responde una sola pregunta con evidencia: ¿este paquete existe, es el que crees y trae algo conocido en su contra?
El veredicto es determinista. Dos ejecuciones con los mismos datos devuelven el mismo
JSON, byte a byte: los pesos viven congelados en rules_v1, los identificadores de
finding derivan de su contenido y no hay relojes ni UUID aleatorios en la respuesta.
Uso directo con uvx
uvx detector-paquetes-fantasma
El comando arranca el servidor sobre stdio. No necesita instalación previa, no descarga modelos y no toca la red hasta que una herramienta lo pide.
Para probar el paquete construido localmente:
python -m build --no-isolation
uvx --from dist/detector_paquetes_fantasma-0.1.0-py3-none-any.whl detector-paquetes-fantasma
Configuración en Kiro
Copia este bloque en .kiro/settings/mcp.json. Es el mismo contenido versionado en
src/detector_paquetes_fantasma/resources/kiro/mcp.json, y scripts/release/check_rc.py
falla si ambos divergen.
{
"mcpServers": {
"detector-paquetes-fantasma": {
"command": "uvx",
"args": ["detector-paquetes-fantasma"],
"transportType": "stdio",
"env": {},
"disabled": false,
"autoApprove": ["verify_package", "verify_manifest", "explain_risk"]
}
}
}
El hook PostFileSave versionado en .kiro/hooks/ verifica package.json y
requirements.txt al guardarlos. Nunca cancela el guardado: si el servidor no está,
tarda o responde algo inválido, avisa y sigue.
Herramientas
verify_package(ecosystem, name, version=None)
Resuelve la versión contra el registry oficial y devuelve un ScanResult.
{
"ecosystem": "npm",
"name": "aws-sdk-mcp-helper"
}
{
"schema_version": 1,
"request": {
"ecosystem": "npm",
"original_name": "aws-sdk-mcp-helper",
"normalized_name": "aws-sdk-mcp-helper",
"requested_specifier": null
},
"status": "COMPLETE",
"resolved_version": null,
"decision": {
"rules_version": "rules_v1",
"score": 70,
"severity": "HIGH",
"action": "BLOCK",
"critical_flags": ["PACKAGE_NOT_FOUND"]
},
"findings": [
{
"type": "package_not_found",
"source": "REGISTRY",
"confidence": "CONFIRMED",
"message": "el registry oficial afirma que el paquete no existe"
}
]
}
version acepta una versión exacta, un rango o nada. Con rango se resuelve la mayor
estable publicada y se avisa que un lockfile puede elegir otra. Un specifier que no se
puede resolver (workspace:*, file:../x, referencias VCS) no se ignora: se reporta
como unverifiable_entry sin llamar al registry.
verify_manifest(ecosystem, manifest_content, path=None)
Verifica todas las entradas de un package.json o requirements.txt.
manifest_content se trata siempre como dato: no se interpola en ningún comando.
{
"ecosystem": "npm",
"manifest_content": "{\"dependencies\": {\"expres\": \"4.17.1\", \"lodash\": \"4.17.20\"}}",
"path": "package.json"
}
La respuesta es un ManifestScanResult con un ScanResult por paquete,
unverifiable_entries para lo que no se pudo verificar y un aggregate_decision que
toma la acción más restrictiva, el score máximo y la unión estable de flags. Si falla
una dependencia, las demás se conservan.
explain_risk(result)
Explica un ScanResult o ManifestScanResult ya calculado. La explicación jamás
modifica score, severidad, acción ni flags: immutable_decision es una copia exacta de
la decisión recibida. Devuelve la narrativa, el motor usado (deterministic_template,
lexical_fallback, local_rag o bedrock) y los casos históricos relacionados del
corpus versionado.
Decisión y score
| Score | Severidad | Acción |
|---|---|---|
| cualquier flag crítico | HIGH | BLOCK |
| 50–69 | MEDIUM | REVIEW |
| 20–49 | LOW | CAUTION |
| 0–19 | MINIMAL | ALLOW |
Los cuatro flags críticos son PACKAGE_NOT_FOUND, OSV_ADVISORY_CONFIRMED,
TYPOSQUAT_CONFIRMED y STATIC_COMPOSITE_CONFIRMED. No suman puntos: imponen un piso
de 70, más 10 por cada tipo crítico distinto adicional. Sin flags críticos el score
nunca pasa de 69, así que una acumulación de señales débiles no puede fabricar un
BLOCK. Cada tipo aporta una sola vez: repetir el mismo mensaje no infla el resultado.
Configuración tipada
Todo se lee del entorno y se valida al arrancar. Un valor no interpretable produce un error con la variable responsable, no un default silencioso.
| Variable | Default | Qué controla |
|---|---|---|
DPF_OFFLINE |
false |
prohíbe cualquier salida de red |
DPF_CACHE_PATH |
detector-paquetes-fantasma.sqlite3 |
ruta de la base SQLite |
DPF_REGISTRY_TTL_HOURS |
168 |
frescura de metadata de registry |
DPF_OSV_TTL_HOURS |
6 |
frescura de advisories |
DPF_HTTP_TIMEOUT_SECONDS |
10 |
timeout por request |
DPF_DOWNLOAD_TIMEOUT_SECONDS |
30 |
timeout de descarga de artefactos |
DPF_MAX_CONCURRENCY |
8 |
requests simultáneas al registry |
DPF_MAX_DOWNLOAD_BYTES |
25000000 |
tamaño máximo descargado |
DPF_MAX_ARCHIVE_FILES |
2000 |
entradas máximas por archivo |
DPF_MAX_FILE_BYTES |
5000000 |
tamaño máximo por archivo extraído |
DPF_MAX_EXPANDED_BYTES |
50000000 |
tamaño máximo expandido |
DPF_RAG_MODEL_PATH |
sin valor | ruta local del modelo de embeddings |
DPF_BEDROCK_ENABLED |
false |
activa la redacción con Bedrock |
AWS_REGION |
sin valor | región requerida si Bedrock está activo |
Las credenciales nunca se persisten ni aparecen en logs: la configuración se serializa redactada.
Cache y modo offline
La cache es SQLite en modo WAL, con TTL separados para registry y OSV, y negative caching solo cuando la ausencia es autoritativa (un 404 del registry). Un 5xx o un timeout nunca se cachean como "no existe".
Con DPF_OFFLINE=true no se crea ni el cliente HTTP. Las respuestas se sirven de cache
declarando su edad en lenguaje llano (info de hace 6h) y bajando la cobertura a
PARTIAL o UNVERIFIABLE. Reutilizar dato viejo puede elevar la cautela, y esa
degradación se declara en los findings en lugar de fingir un veredicto completo.
Explicaciones: local por defecto, Bedrock opcional
El orden de fallback es Bedrock → RAG local → recuperación léxica → plantilla
determinista. Sin configuración adicional se usa la recuperación léxica sobre el corpus
de incidentes versionado, y toda explicación sin Bedrock se etiqueta como
generada localmente, sin Bedrock.
- RAG con embeddings: instala el extra
ragy apuntaDPF_RAG_MODEL_PATHa un modelo local. El servidor nunca descarga pesos por su cuenta y el import desentence-transformerses lazy: arrancar MCP no lo carga. - Bedrock: instala el extra
bedrock, defineDPF_BEDROCK_ENABLED=trueyAWS_REGION. El routing es determinista, Sonnet para scores 40–69 o señales contradictorias y Haiku para el resto. El payload lleva findings normalizados y procedencia, nunca código fuente completo, contenido de artefactos ni credenciales. Una respuesta que intente traer score, severidad, acción o flags se rechaza entera.
Límites del análisis estático
El análisis estático es una señal, no una sentencia. Es AST de Python y tree-sitter para JavaScript sobre el artefacto oficial, sin ejecutar nada y sin sandbox.
- No hay ejecución: no detecta comportamiento que solo aparece en runtime.
- El código ofuscado, minificado o generado dinámicamente puede evadirlo.
- Un finding aislado (
automatic_execution_isolated,sensitive_access_isolated,network_or_spawn_isolated) se marcasolo detección estática, sin confirmación externay nunca bloquea por sí solo. Solo el composite confirmado es crítico. - Si ningún parser puede analizar un archivo, se reporta
parser_failurecon el aviso de revisión manual; no se asume que sea limpio.
Scripts y reportes
python -m scripts.probes.cache_offline # cache, TTL y offline sin sockets
python -m scripts.scenarios.hallucinated_package # paquete alucinado
python -m scripts.scenarios.vulnerable_typo_manifest
python -m scripts.scenarios.offline_cache_replay
python -m benchmarks.full_manifest # manifest completo, cache y concurrencia
python -m scripts.reports.scan_secrets # reportes sin credenciales ni rutas personales
python -m scripts.reports.acceptance # agregado en reports/acceptance.json
python -m scripts.release.check_rc # verificación del release candidate
Los tests usan providers mockeados. La red queda reservada a los tests marcados live,
que solo corren con --live o RUN_LIVE_TESTS=1.
Roadmap
- Sandboxing del análisis de artefactos.
- MCP remoto en Lambda.
- Redis como cache compartida.
- OpenTelemetry para trazas y métricas.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file detector_paquetes_fantasma-0.1.0.tar.gz.
File metadata
- Download URL: detector_paquetes_fantasma-0.1.0.tar.gz
- Upload date:
- Size: 236.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4315754e3bd3e813b07fe490ec26f407eb424b0f6cca399d9a364078da28fc7d
|
|
| MD5 |
330046b33b45678e5805c67785d92c9c
|
|
| BLAKE2b-256 |
76c6de4693204414c002243ebafab6963c6bebd7c96eb0ec65c14d5a2fda7280
|
File details
Details for the file detector_paquetes_fantasma-0.1.0-py3-none-any.whl.
File metadata
- Download URL: detector_paquetes_fantasma-0.1.0-py3-none-any.whl
- Upload date:
- Size: 133.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a3652c0fe0d5eb8a8760de94b1a9e88546ae624cca4a676eef3f99e8e454ee9
|
|
| MD5 |
6a2db1d6afe102e68610b12d141be4e7
|
|
| BLAKE2b-256 |
53778877fd4330902fb2179fb5dee65d903983ef2f8e0e3acb509b69533de88c
|