
SecureAI-Scan es una herramienta CLI que analiza bases de código TypeScript y JavaScript en busca de problemas de seguridad específicos de aplicaciones impulsadas por IA: inyección de prompts, abuso de herramientas MCP, envenenamiento de datos RAG, violaciones de confianza de agentes y más.
La mayoría de los escáneres en este ámbito buscan una palabra clave y la llaman hallazgo. SecureAI-Scan rastrea la ruta real de origen → flujo → sumidero a través de código real resuelto por imports, y un escaneo por defecto solo te muestra aquello que puede demostrar. Sin cuenta, sin carga en la nube, nada sale de tu máquina.
Cubre el OWASP Top 10 oficial para aplicaciones LLM 2026, el Top 10 para aplicaciones agénticas (2026) y el MCP Top 10 desde la semana de lanzamiento.
npx --yes [email protected] scan .
No se requiere cuenta, carga en la nube, intérprete de Python ni configuración. TypeScript, JavaScript, Python, configuraciones de MCP y paquetes de Agent Skill se detectan automáticamente.
**Candidato de lanzamiento `0.9.0` medido:** 136/136 pruebas · 88.08% de cobertura de sentencias · 12,676 archivos en 9 repositorios públicos · 0 huellas nuevas de nivel predeterminado frente a la línea base revisada. [Evidencia](https://github.com/akanthed/secureai-scan/blob/main/docs/benchmarks/v0.9.0.json) · [metodología y límites](https://github.com/akanthed/secureai-scan/blob/main/docs/ReleaseAssurance.md)```
▌ HIGH AI001 Prompt injection via user input
PROVEN LLM01:2026 Prompt Injection
source src/chat.ts:8 request data `req.body.input`
flow src/chat.ts:13 passed as `systemPrompt`
sink src/chat.ts:10 openai.chat.completions.create — system role (OpenAI)
fix Keep system prompts static; pass user input as a user-role message.
¿Es para ti? SecureAI-Scan está deliberadamente enfocado a riesgos de LLM, MCP y RAG/agentes: inyección de prompts, envenenamiento de herramientas, manejo inseguro de salidas, control de acceso a almacenes vectoriales y envenenamiento de habilidades de agentes. No es un escáner SAST general ni de secretos, y no intenta serlo; un paquete conocido como malicioso sin carga útil con forma de LLM (por ejemplo, una dirección de exfiltración hardcodeada en una llamada a una API de correo) es detectado por la lista de avisos offline (DEP003), no por una regla de patrón. Si tu código habla con un LLM, un servidor MCP, un almacén vectorial o incluye Agent Skills, esto está hecho para ti.
Nuevo: escaneo estático de configuración para LiteLLM Proxy (config.yaml): secretos hardcodeados, endpoints de proveedor en texto plano, guardarraíles ausentes. Consulta Reglas (LLC001–LLC003).
proven (flujo de datos trazado o hecho de configuración analizado), likely (sumidero resuelto, un salto heurístico) o heuristic. Un escaneo predeterminado muestra solo proven + likely. Las heurísticas son opcionales mediante --paranoid.openai, @anthropic-ai/sdk, ai, @google/genai, LangChain, Bedrock, …). Tu cliente de Google Maps nunca volverá a marcarse como LLM.npm run regression escanea repositorios públicos reales (OpenAI/Anthropic/Vercel AI SDKs, servidores MCP oficiales, LlamaIndex) contra una línea base confirmada y revisada manualmente, y falla ante cualquier hallazgo nuevo proven/likely. Consulta Pruebas y evaluación comparativa para ver los números reales de antes/después, o Lo que encontramos escaneando repositorios reales para conocer la historia detrás de ellos: una tasa de detección de 6/6 en un corpus etiquetado de habilidades maliciosas, y por qué no llamamos a llama_index "vulnerable" por un hallazgo honesto a nivel de biblioteca. Escrito de discusión →--output report.sarif coloca los hallazgos en línea en las pull requests y en la pestaña Security.secureai-scan bom . construye un inventario derivado de la sintaxis de SDKs, IDs de modelos, almacenes vectoriales, frameworks de agentes y servidores MCP, mapeado a las necesidades de documentación de OWASP LLM Top 10 / EU AI Act..mcp.json, claude_desktop_config.json, .cursor/mcp.json: servidores npx -y sin fijar versión, secretos en línea, transportes HTTP en texto plano.command/args del transporte stdio de MCP construidos a partir de datos de solicitud: el patrón detrás de la divulgación de RCE en MCP STDIO de 2026.SKILL.md: las Agent Skills se cargan en el contexto por completo, así que una skill envenenada es una descripción de herramienta envenenada con otro nombre.SKILL.md, y cada comprobación de contenido se ejecuta contra variantes desofuscadas del texto. Esto apunta a las técnicas publicadas — homoglifos, división con cero ancho, cargas útiles almacenadas en .git/ o build/, exfiltración oculta en un archivo *.test.ts — que eludieron a >90% de los nueve escáneres encuestados en Cloak and Detonate (arXiv:2607.02357). Consulta Resistencia a evasión.scripts/sync-advisories.js. Se ejecuta offline en cada escaneo, sin necesidad de bandera. Un CVE solo se dispara cuando tu versión fijada está demostrablemente dentro del rango afectado; un paquete documentado como malicioso se dispara incluso con un rango ambiguo, porque instalar un backdoor es irrecuperable.SecureAI-Scan no es un reemplazo de una herramienta SAST general ni de un escáner de contenedores/IaC: ejecútalo junto a una, no en lugar de una. Está construido a propósito para la superficie de ataque LLM/MCP/RAG y enfatiza la evidencia de flujo de datos sobre hallazgos planos de palabras clave.
| SecureAI-Scan | Semgrep (reglas OSS) | Trivy | GitHub Advanced Security | |
|---|---|---|---|---|
| Inyección de prompts (origen→sumidero trazado) | ✅ flujo de datos resuelto por importaciones | ⚠️ solo reglas de patrón, mantenidas por la comunidad | ❌ | ⚠️ CodeQL puede, pero sin conjunto de reglas específico de IA |
| Envenenamiento de herramientas MCP / riesgo de configuración | ✅ MCP007–010, escáner de configuración | ❌ | ❌ | ❌ |
Envenenamiento de Agent Skills (SKILL.md) | ✅ resistente a evasión, consciente de paquetes | ❌ | ❌ | ❌ |
| Mala configuración de RAG / almacén vectorial | ✅ VEC001–004 | ❌ | ❌ | ❌ |
| Avisos de paquetes de IA conocidos como maliciosos | ✅ DEP003, offline, consciente de versión | ❌ | ⚠️ feed CVE general, no específico de IA | ⚠️ Dependabot, feed CVE general |
| SAST general (SQLi, XSS, path traversal) | ❌ fuera de alcance por diseño | ✅ | ❌ | ✅ |
| Escaneo de contenedores / IaC | ❌ | ❌ | ✅ | ⚠️ mediante CodeQL/Actions |
| Niveles de evidencia (proven/likely/heuristic) | ✅ | ❌ los hallazgos son planos | ❌ | ⚠️ CodeQL tiene algunos, no ajustados a IA |
| Salida SARIF (code scanning de GitHub) | ✅ | ✅ | ✅ | nativa |
| Se ejecuta offline, sin cuenta | ✅ | ✅ (reglas OSS) | ✅ | ❌ requiere GitHub |
Si ya ejecutas Semgrep o GHAS, consérvalos: añade SecureAI-Scan para la superficie de riesgo que no modelan en absoluto.
¿Prefieres hacer preguntas primero? Prueba el Asesor de Seguridad de IA de SecureAI-Scan en ChatGPT gratis.
¿A punto de ejecutar un servidor MCP que encontraste en GitHub o Twitter? Pega primero su descripción de herramientas en MCP X-Ray — comprueba Unicode oculto, instrucciones inyectadas y paquetes conocidos como maliciosos en tu navegador, sin instalación.
secureai-scan scan . de principio a fin, salida real contra un archivo real (pequeño, deliberadamente vulnerable) — fuente:

Formas de ataque que el escáner traza de principio a fin:
| Flujo de datos de envenenamiento de herramientas MCP | Flujo de datos de inyección de contexto RAG |
|---|---|
![]() | ![]() |
El que necesitas el 95% de las veces:```bash secureai-scan scan .
Todo lo demás está ahí cuando lo necesites. `secureai-scan scan . --help` muestra todo esto en la terminal, agrupado de la misma manera:
**Uso diario**
| Indicador | Qué hace |
|------|---------------|
| *(ninguno)* | hallazgos `proven` + `likely` — el valor predeterminado, no se necesitan indicadores |
| `--paranoid` | también incluye hallazgos de nivel `heuristic` |
| `-s, --severity <nivel>` | solo muestra hallazgos en/por encima de `low`\|`medium`\|`high`\|`critical` |
| `--output <archivo>` | escribe un informe completo — `.sarif` (escaneo de código de GitHub), `.json`, `.md` o `.html` |
**Alcance de qué reglas se ejecutan**
| Indicador | Qué hace |
|------|---------------|
| `-r, --rules <lista>` | ejecuta solo estos ID de regla, p. ej. `AI001,MCP007` |
| `--only-ai` / `--only-mcp` / `--only-vec` / `--only-skl` | ejecuta solo una categoría de reglas |
| `--check-dependencies` | también verifica `package.json`/`requirements.txt` contra el registro npm/PyPI para detectar errores tipográficos y paquetes alucinados (`DEP001`/`DEP002`). Se activa automáticamente si seleccionas esas reglas directamente con `-r` — nunca necesitas recordar pasar ambos. No es necesario para `DEP003` (paquetes maliciosos conocidos), que siempre se ejecuta sin conexión |
**CI / flujo de trabajo**
| Indicador | Qué hace |
|------|---------------|
| `--fail-on <severidad>` | sale con `1` si existen hallazgos en/por encima de esta severidad |
| `--baseline <archivo>` | rastrea solo problemas nuevos/cambiados contra una línea base guardada |
| `--policy <archivo>` | carga umbrales, rutas omitidas y reglas bloqueadas desde un `.secureai-policy.json` (se detecta automáticamente si está presente — `secureai-scan init` crea uno) |
**Avanzado**
| Indicador | Qué hace |
|------|---------------|
| `--min-confidence <0-1>` | más granular que `--paranoid`: oculta hallazgos por debajo de una puntuación de confianza exacta (`0.9` proven / `0.65` likely / `0.35` heuristic) |
| `--limit <n>` | máximo de grupos de reglas mostrados en la terminal (predeterminado `10`) — el detalle completo siempre va a `--output` |
| `--debug` | imprime cada archivo escaneado y qué reglas se ejecutaron |
**Escanea antes de instalar — sin clonar, sin configurar:**```bash
secureai-scan skill anthropics/skills # a GitHub "owner/repo" shorthand
secureai-scan skill https://github.com/… # or a full git URL
secureai-scan skill ./some/local/skill-dir # or a local path
secureai-scan mcp some-mcp-server-package # a bare npm package name
secureai-scan mcp owner/mcp-server-repo # or git, same as `skill`
skill y mcp obtienen el objetivo y lo escanean, y luego eliminan la copia descargada (--keep para inspeccionarla en su lugar). Nada de lo obtenido se ejecuta jamás: un objetivo npm se descarga con npm pack — solo el tarball, sin install, sin scripts de ciclo de vida — y un objetivo git es un simple git clone --depth 1. Este es el momento que más importa: antes de que una skill llegue a ~/.claude/skills/ o un servidor llegue a .mcp.json, no después.
Otros comandos:```bash secureai-scan bom . --output AI_BOM.md # AI Bill of Materials secureai-scan explain AI001 # why + exploit + fix example, for any rule secureai-scan threat-model . # THREAT_MODEL.md with the OWASP coverage matrix — example: docs/examples/THREAT_MODEL.example.md secureai-scan init # policy file + CI workflow, one-time setup
Suprime un hallazgo revisado en el código:```ts
// secureai-ignore AI001: reviewed, input sanitized via allowlist
name: SecureAI-Scan on: [pull_request] permissions: contents: read security-events: write jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: akanthed/[email protected] with: scanner-version: 0.10.0 fail-on: high
Los hallazgos aparecen como anotaciones en línea en el PR y en la pestaña de Seguridad del repositorio. (`secureai-scan init` genera un flujo de trabajo equivalente usando la CLI directamente.)
¿El escaneo está limpio? Añade la insignia a tu propio README:```md
[](https://github.com/akanthed/SecureAI-Scan)
¿Prefieres detectar hallazgos antes de que se suban? Añade este repositorio como fuente de gancho de pre-commit en lugar de, o junto a, la GitHub Action:```yaml repos:
El hook escanea todo el proyecto en cada commit (no solo los archivos modificados — un rastreo de flujo de datos hacia el archivo A puede depender del archivo B, que un escaneo parcial pasaría por alto) y bloquea el commit en hallazgos de severidad `high`+ por defecto. Anula el umbral en tu propia configuración:```yaml
- id: secureai-scan
args: ["--fail-on", "critical"]
42 reglas, mapeadas al OWASP Top 10 oficial para Aplicaciones LLM (2026) — además, cuando corresponde, al OWASP Top 10 para Aplicaciones Agénticas (2026, ASI), al OWASP MCP Top 10 (2025) y a un artículo de la Ley de IA de la UE. Consulta la cobertura y límites de la versión 2026; threat-model genera la matriz para cada proyecto escaneado.
| Regla | Qué demuestra | OWASP |
|---|---|---|
| AI001 | La entrada del usuario fluye hacia un prompt de sistema/desarrollador (fuente rastreada → sumidero, incluso a través de límites de función/archivo) | LLM01 |
| AI002 | El contenido del prompt o los secretos se escriben en registros (en archivos que usan un SDK de LLM) | LLM02 |
| AI003 | Llamada a LLM en un manejador de solicitudes sin verificación de autenticación antes | LLM06 |
| AI004 | Objeto completo de usuario/sesión serializado en un prompt (la selección de campos no se marca) | LLM02 |
| AI005 | La salida del LLM llega a sumideros de eval/exec/SQL/HTML | LLM10 |
| AI006 | Herramientas de alto impacto (eliminar, pagar, desplegar, …) expuestas sin una puerta de aprobación | LLM03 |
| AI007 | Contenido RAG recuperado interpolado en prompts privilegiados | LLM01 |
| AI008 | Secretos incrustados en el texto del prompt de sistema | LLM08 |
| AI009 | Entrada de usuario sin límites / límites de tokens faltantes | LLM06 |
| AI010 | Contenido externo obtenido fluye hacia los prompts | LLM01 |
| AI011 | Salida del agente elevada a rol de sistema en llamadas posteriores | LLM03 |
| AI012 | Salida del LLM analizada sin validación de esquema | LLM10 |
| MCP001 | Los metadatos de herramientas MCP llegan al prompt de sistema sin validación | LLM01 |
| MCP002 | URL del servidor MCP construida a partir de la entrada del usuario | LLM04 |
| MCP003 | Resultados de herramientas MCP elevados a rol de sistema | LLM10 |
| MCP004 | Servidor MCP lanzado como paquete npx -y sin fijar versión | LLM04 |
| MCP005 | Secreto insertado en una configuración MCP confirmada | LLM02 |
| MCP006 | Servidor MCP sobre HTTP en texto plano | LLM04 |
| MCP007 | Unicode invisible/bidi oculto en nombres o descripciones de herramientas MCP | LLM01 · MCP03 |
| MCP008 | Frases de inyección dirigidas por el agente en descripciones de herramientas MCP |
secureai-scan explain <RULE_ID> proporciona el recorrido del exploit y un ejemplo de código antes/después para cualquier regla.
Tres superficies de escaneo independientes alimentan una única lista de hallazgos fusionada y deduplicada:``` ┌─────────────────────┐ *.ts / *.js ───▶ │ ts-morph AST rules │───┐ │ (import-resolved │ │ │ sinks + dataflow) │ │ └─────────────────────┘ │ │ ┌─────────────────────┐ │ ┌──────────────┐ ┌─────────────────┐ *.py ───▶ │ tree-sitter AST + │───┼───▶ │ scan.ts │───▶ │ evidence filter │ │ local taint flow │ │ │ merge/dedupe│ │ → confidence │ └─────────────────────┘ │ │ + suppress │ │ → severity │ │ │ (// secure- │ │ → baseline diff │ .mcp.json, ┌─────────────────────┐ │ │ ai-ignore) │ │ → report │ SKILL.md ───▶ │ Config/bundle scan │──┘ └──────────────┘ └─────────────────┘ │ (off-disk, evasion- │ │ │ resistant) │ ▼ └─────────────────────┘ terminal · sarif · json · md · html
package.json, requirements.txt ─▶ dependency-guard.ts (advisories.ts, offline, version-aware)
Cada regla AST solo llama a una función "llamada LLM" si se resuelve mediante importaciones reales a un SDK conocido — nunca solo por coincidencia de nombres. Consulta [`docs/Architecture.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Architecture.md) para el desglose completo de cada superficie, y [`docs/DetectionEngine.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/DetectionEngine.md) para saber cómo funciona el contrato de niveles de evidencia.
## Servidor MCP (úsalo desde Claude)
El paquete incluye un servidor MCP que expone `scan_repository`, `explain_rule`, `generate_bom` y `scan_untrusted_target` (obtiene y escanea una skill o servidor MCP antes de que Claude recomiende instalarlo — mismo comportamiento de obtener-sin-ejecutar que los comandos CLI `skill`/`mcp`):```json
{
"mcpServers": {
"secureai-scan": {
"command": "node",
"args": ["/path/to/secureai-scan/mcp-server/index.js"]
}
}
}
Para usuarios de Claude Code / Claude.ai, skills/secureai-scan/SKILL.md enseña a Claude cuándo ejecutar un escaneo (al revisar código de IA/LLM, o al comprobar un servidor MCP/Skill de Agente antes de instalarlo) y cómo leer los resultados — sin necesidad de un proceso separado, a diferencia del servidor MCP anterior. Copia el directorio skills/secureai-scan/ en tu .claude/skills/ para usarlo.
En julio de 2026, dos investigaciones públicas demostraron que los escáneres de Skills de Agente se eluden trivialmente.
Cloak and Detonate (Ji, Xu, Li, Gao, Wei, Wang & Cheung — HKUST, arXiv:2607.02357) realizó un estudio adversarial contra nueve escáneres de skills publicados usando 1.613 skills maliciosas reales. Dos familias de ataques, ambas preservando el payload exactamente:
.git/, con un SKILL.md de cobertura benigno que lo reconstruye en la primera ejecución. ≥90% de evasión en todos los escáneres probados, ≥96% en la mayoría. La tasa de detección de un escáner híbrido cayó de ~99% a ~10%.Por separado, Gecko Security (VentureBeat) distribuyó un payload en un archivo *.test.ts. Todos los escáneres públicos lo omitieron — pero Jest y Vitest auto-descubren y ejecutan esos archivos en el siguiente npm test.
SecureAI-Scan maneja cada técnica publicada. Cada fila a continuación tiene una prueba correspondiente en test/deobfuscate.test.js o un fixture en test-fixtures/vulnerable/skills/:
| Técnica (nombre del paper) | Ejemplo | Cómo se maneja |
|---|---|---|
| Inserción de ancho cero | Ignore all previous… | El texto se compara contra variantes sin caracteres invisibles |
| Glifos confundibles | сurl (с cirílico) | Plegado de homoglifos a ASCII antes de la comparación |
| Cambio de mayúsculas | EvAl | Todos los patrones de contenido distinguen mayúsculas de minúsculas |
| Empalme de variables | 'cu' + 'rl' | Literales de cadena adyacentes se unen antes de la comparación |
| División por líneas | ev⏎al(x) | Saltos de línea dentro de una palabra se unen como variante adicional |
| Extracción por archivo | payload en un helper importado | Se escanea todo el paquete, no solo SKILL.md |
| Reubicación de directorio | scripts/x.sh → build/x.sh | Sin lista de exclusión de directorios dentro de un paquete |
| Renombrado de extensión | x.sh → x.txt | Los archivos se escanean por contenido, no por extensión |
| Relleno de tamaño | rellenar más allá del límite del escáner | Los archivos sobredimensionados se leen por cabecera, nunca se omiten |
| Empaquetado SFS | payload en .git/skillpack.dat | Cualquier archivo no git bajo el .git/ de un paquete se marca como proven (SKL004) |
| Preparación de archivos de prueba | payload en *.test.ts | El escaneo de paquetes deliberadamente no degrada las rutas de prueba (SKL005) |
La ofuscación suele ser un riesgo de precisión — más coincidencias, más ruido. Aquí la lógica está invertida: una coincidencia que aparece solo tras la ofuscación se promueve a proven, no se degrada. La documentación ordinaria no contiene un conector de ancho cero dentro de "ignore previous instructions", ni una с cirílica dentro de curl. El ocultamiento es en sí mismo evidencia afirmativa de intención.
La comparación se hace contra el conjunto de coincidencias brutas, no meramente "si el texto bruto coincidió en absoluto" — de lo contrario, un atacante podría enmascarar la señal dejando una frase inocua en claro.
Las dos nuevas reglas de paquetes se activan solo con conjunciones, nunca con una palabra clave:
tar -x junto a un activo binario no relacionado no es suficiente. Los archivos reales (gzip/zip/png/pdf/wasm — verificados por bytes mágicos, no por extensión) nunca son "opacos" en primer lugar, sin importar cómo estén comprimidos.~/.aws/credentials, no la palabra "token") o enumeración masiva de variables de entorno (os.environ.items(), no os.environ["API_KEY"]) — y egreso a un host no local codificado, dentro de 25 líneas entre sí en el mismo archivo, o una descarga remota que se ejecuta tras ser reasignada mediante uno o más renombrados. Un helper de publicación que lee ~/.npmrc en una función y llama a un registro cuarenta líneas después permanece limpio, y leer una variable de entorno nombrada para una llamada API nunca se marca — ambas formas están fijadas como fixtures seguros.Validado contra dos corpus del mundo real, no solo fixtures escritos por nosotros: 0 hallazgos en los 18 paquetes de skills reales en anthropics/skills y los 14 en vercel/ai, y 6/6 correctos en el corpus de evaluación etiquetado de cisco-ai-defense/skill-scanner (20 skills, cada una con un veredicto _expected.json) con cero falsos positivos en cualquier cosa etiquetada como segura. Ver Pruebas y benchmarking.
Limitación honesta: la conclusión del paper es que la detonación en tiempo de ejecución supera al análisis estático, y eso es correcto. Un adversario adaptativo que conozca estas reglas puede escribir una transformación que no cubran. Lo que cambia aquí es el coste de la evasión — las técnicas publicadas y actualmente en circulación ya no funcionan, y la ofuscación necesaria para derrotarlas ahora eleva por sí misma la gravedad del hallazgo. El escaneo estático es un filtro, no un límite de seguridad. Trata una skill no confiable como código no confiable, sin importar lo que diga cualquier escáner.
prepublishOnly.Este es un proyecto de mantenedor único sin SLA contractual ni certificación independiente. Los controles anteriores reducen el riesgo; no convierten un escaneo estático en prueba de seguridad.
Los falsos positivos matan a los escáneres. El motor de reglas de SecureAI-Scan sigue tres reglas estrictas:
test-fixtures/safe/ contiene los patrones que solían causar falsos positivos (payloads de PII redactados, clientes de Google Maps, claves API de variables de entorno junto a clientes LLM, registro de respuestas ordinario, campos de metadatos OAuth, chunks de respuestas en streaming, texto de prompts de ficción/narrativa). Cualquier hallazgo allí hace fallar la suite.Tres capas, porque una sola no basta para confiar en las afirmaciones de un escáner — la precisión y el recall son modos de fallo diferentes, y ambos se verifican.
1. Corpus de fixtures — precisión + recall, se ejecuta en cada build.```bash npm test
[`test-fixtures/vulnerable/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/vulnerable) y [`test-fixtures/safe/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/safe) se escanean juntos: cada fixture vulnerable debe disparar su regla esperada con evidencia `proven`/`likely` (recall), cada fixture seguro debe producir **cero** hallazgos `proven`/`likely` (precisión). Rápido y determinista — pero solo demuestra que el escáner se comporta correctamente en código escrito específicamente para probarlo.
**2. Benchmark de regresión con código real — contra repositorios públicos que no escribimos nosotros.**```bash
npm run regression # scan the full curated repo set
npm run regression -- --fresh # re-clone everything first
npm run regression -- openai-node # scan just one repo by name
npm run regression -- --update-baseline # accept the current findings
scripts/regression-scan.js clona un conjunto curado y diverso de repositorios públicos reales (OpenAI/Anthropic/Vercel AI SDKs, los servidores MCP oficiales y el SDK de TypeScript, LlamaIndex, además de anthropics/skills y cisco-ai-defense/skill-scanner para cobertura de paquetes de habilidades — abarcando TS y Python, código de ejemplo de consumidores de SDK y código fuente de autores de SDK) y escanea cada uno con el CLI compilado.
Sale con código distinto de cero ante cualquier hallazgo proven/likely que no esté ya en test/regression-baseline.json — un registro revisado manualmente de hallazgos ya contrastados con su línea de origen. Las huellas son repo|rule|file, no números de línea, por lo que el cambio ordinario en los repositorios ascendentes no genera ruido. Una huella nueva es una afirmación que el escáner debe justificar: si no es un problema genuino, es un error de regla, corregido en la causa raíz y fijado como un nuevo fixture de test-fixtures/safe/. Incluir en la línea base un hallazgo que no has leído anula todo el mecanismo.
La cobertura de paquetes de habilidades tiene su propia línea porque el corpus evals/ de cisco-ai-defense/skill-scanner está etiquetado — cada uno de sus 20 fixtures incluye un veredicto _expected.json y se encuentra bajo un directorio literalmente llamado malicious/ o safe/, por lo que funciona también como comprobación de recall, no solo de precisión: 6/6 fixtures maliciosos dentro del alcance disparan, 0 hallazgos en cualquier cosa etiquetada como safe, y 0 hallazgos en los 18 paquetes reales de anthropics/skills y en los 14 de vercel/ai. (Las categorías restantes de Cisco — inyección SQL, path traversal, agotamiento de recursos, eval() genérico de un argumento de función, un payload deliberadamente dividido en cuatro archivos — están fuera del alcance documentado de LLM/MCP/RAG o más allá del análisis de conjunción dentro del mismo archivo; consulta la entrada del changelog 0.6.0 para el razonamiento específico de cada una.)
Antes/después histórico de la ejecución que impulsó las correcciones originales de precisión (hallazgos en el nivel de evidencia predeterminado, sin --paranoid):
| Repo | Antes | Después | Qué estaba mal |
|---|---|---|---|
| vercel/ai | 773 | 1 | Los directorios examples/, tests/ de nivel superior y los de estilo ecosystem-tests/ con guiones no se reconocían como rutas de menor confianza; chunks (una variable común de respuesta en streaming) se trataba como evidencia inequívoca de RAG |
| openai/openai-node | 47 | 0 | La misma brecha de detección de rutas, aplicada a los propios examples//ecosystem-tests/ del SDK |
| anthropics/anthropic-sdk-typescript | 2 | 0 | La misma brecha de detección de rutas en un directorio tests/ de nivel superior |
| modelcontextprotocol/typescript-sdk | 3 | 0 | Campos de metadatos OAuth de estilo token_endpoint/tokenType marcados como secretos filtrados |
| run-llama/llama_index | 18 | 15 | Una comprobación de Python marcaba cualquier campo description= que contuviera "system prompt" como envenenamiento proven de herramientas MCP, independientemente del contexto. Los 15 restantes son aciertos VEC001 en las definiciones genéricas de retriever de la propia biblioteca — escanear el código fuente del propio SDK de base de datos vectorial, no código de aplicación, por lo que no puede existir un filtro que comprobar; un límite inherente y honesto, no un error |
Ejecución actual (2026-08-06) — la evidencia versionada está registrada en docs/benchmarks/v0.9.0.json:
| Repo | Hallazgos | Reglas | Estado |
|---|---|---|---|
| openai-node, anthropic-sdk-typescript, anthropic-sdk-python, modelcontextprotocol/typescript-sdk, modelcontextprotocol/servers | 0 | — | limpio |
| anthropics/skills (18 paquetes de habilidades reales) | 0 | — | limpio — comprobación de precisión pura para SKL001–005 |
| vercel/ai (5.691 archivos) | 0 | — | era 40 (AI001, AI003, AI005, AI010, MCP002) antes del triaje — cada uno revisado manualmente contra el origen y confirmado como falso positivo, rastreado a 3 errores independientes de causa raíz (ver más abajo), corregido y reconfirmado limpio en un re-escaneo completo |
| run-llama/llama_index | 46 | VEC001 | límite inherente, no un error — las definiciones genéricas de retriever de la propia biblioteca, donde no puede existir un filtro de tenant que encontrar |
| cisco-ai-defense/skill-scanner | 7 | SKL001, SKL002, SKL005 | todos en fixtures etiquetados como malicious/ — 6/6 dentro del alcance, 0 en cualquier cosa etiquetada como safe/ |
El triaje de vercel/ai encontró tres errores reales con causa raíz — ninguno específico de las reglas de habilidades v0.6.0, todos en lógica compartida utilizada en muchas reglas:
resolveLlmSink trataba cualquier llamada resuelta a un módulo de SDK de LLM como una invocación de modelo, independientemente del nombre del método — marcando isToolUIPart (una guarda de tipo que el paquete ai exporta justo junto a generateText) como una llamada de LLM. Esto solo causó 3 de los 5 grupos de hallazgos (AI001, AI003, AI010).DANGEROUS_CALLEES en AI005 incluye "query" para sinks de estilo inyección SQL, pero "query" también es un verbo legítimo de invocación de LLM/agente — claudeSdk.query({ prompt, options }), la llamada de modelo propia del Claude Agent SDK, se marcaba como "salida de LLM pasada a un sink peligroso" únicamente por el nombre de método compartido.REQUEST_SOURCES (duplicado idénticamente en MCP002, MCP010, VEC003) coincidía con un "params." desnudo — cualquier parámetro de función convencionalmente llamado params, no necesariamente datos de solicitud HTTP. Un validador de esquema de URL (assertOpenLinkParams(params: unknown)) se marcaba como "URL de servidor MCP desde entrada de usuario".Los tres corregidos en la causa raíz (no en el sitio de llamada específico) y fijados como fixtures permanentes en test-fixtures/. Detalles completos en CHANGELOG.md.
3. Validación vulnerable-vs-parcheada — demuestra recall, no solo precisión.
Las dos capas anteriores solo comprueban que el escáner permanezca en silencio ante código seguro. Las comprobaciones de asesoramiento de DEP003 se validan en la dirección opuesta: fija un paquete a una versión documentada como vulnerable y confirma que se marca, luego fíjalo a la versión parcheada y confirma que no se marca.```bash
node --test test/dependency-guard.test.js
covers: `[email protected]` (CVE-2025-6514, vulnerable) marcado / `[email protected]` (parcheado) limpio; `[email protected]` (antes del backdoor) limpio / `[email protected]` (después — no existe un parche legítimo para un paquete malicioso) sigue marcado; `llama-cpp-python==0.2.71` (CVE-2024-34359, del conjunto generado por OSV) marcado / `==0.2.72` (parcheado) limpio, incluso bajo la normalización de nombres de PyPI (`llama_cpp_python`); y especificadores sin fijar de versión estilo `langchain>=0.1.0` que producen **cero** hallazgos en el informe por defecto. Construir esta prueba detectó una brecha real: `DEP003` solía coincidir con avisos solo por nombre de paquete, sin comparar realmente la versión declarada contra el rango afectado del aviso — corregido en [`src/scanner/semver.ts`](https://github.com/akanthed/secureai-scan/blob/main/src/scanner/semver.ts).
La ambigüedad se resuelve de forma diferente según el tipo de aviso, deliberadamente. Un paquete **malicioso** se dispara incluso cuando la versión declarada no puede resolverse — instalar un backdoor es irrecuperable, por lo que falla hacia el marcado. Un **CVE** se dispara en `proven` solo cuando la versión declarada es una fijación exacta demostrablemente dentro del rango afectado; sin fijar pero posiblemente afectado baja a `heuristic` (solo con `--paranoid`). Aplicar la regla de tipo malicioso a una instantánea de CVE de 162 entradas pondría un hallazgo crítico en cada repositorio que declare `langchain>=0.1.0` — ruido inaplicable a escala.
## Roadmap
Consulta [`ROADMAP.md`](https://github.com/akanthed/secureai-scan/blob/main/ROADMAP.md) para ver lo que se ha publicado y lo que está planificado. Ambos motores de lenguaje están basados en AST: ts-morph para TypeScript/JavaScript y Tree-sitter para Python. Las importaciones, llamadas, asignaciones, decoradores, ámbitos, argumentos de palabra clave, campos de diccionario y cadenas de Python son nodos de sintaxis; el código objetivo nunca se importa ni se ejecuta, y no se requiere ningún intérprete de Python. La brecha restante de Python es la profundidad de flujo de datos (taint) limitada entre funciones/archivos, no el análisis sintáctico. El rendimiento del escaneo y las limitaciones conocidas están documentados en [`docs/Performance.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Performance.md).
## Contribuciones
Las contribuciones son bienvenidas — consulta [`CONTRIBUTING.md`](https://github.com/akanthed/secureai-scan/blob/main/CONTRIBUTING.md) para el flujo de trabajo, y [`docs/WritingRules.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/WritingRules.md) / [`docs/RuleDevelopment.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/RuleDevelopment.md) para saber cómo añadir una regla de detección que cumpla con el estándar de precisión anterior. Cada regla nueva necesita un fixture tanto en [`test-fixtures/vulnerable/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/vulnerable) como en [`test-fixtures/safe/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/safe), una entrada en `src/scanner/catalog.ts` y un caso en `test/corpus.test.js` — `npm test` exige los tres.
## Licencia
MIT © Akshay Kanthed
| LLM01 · MCP03 |
| MCP009 | Una descripción de herramienta que desvía llamadas a una herramienta diferente (sombreado) | LLM01 · MCP03 |
| MCP010 | Comando/argumentos del servidor MCP stdio construidos a partir de la entrada del usuario (RCE) | LLM04 · MCP05 |
| SKL001 | Unicode invisible/bidi en cualquier lugar de un paquete de habilidades de agente | LLM01 |
| SKL002 | Fraseo de inyección dirigido por el agente en la descripción o el cuerpo de una habilidad (coincidencia mediante ofuscación) | LLM01 |
| SKL003 | El contenido de una habilidad dirige cuándo/cómo se usa una habilidad diferente (sombreado) | LLM01 |
| SKL004 | Carga útil por etapas/autoextraíble: blob opaco + instrucciones para decodificarlo y ejecutarlo | LLM04 · MCP04 |
| SKL005 | Lectura de credenciales + salida externa codificada en un archivo complementario del paquete | LLM02 · MCP04 |
| SKL006 | Ejecución de comandos en tiempo de carga mediante la sintaxis de inyección de contexto dinámico de Claude Code (!`cmd`/```!), antes de cualquier puerta de permisos de herramientas | LLM04 · MCP05 |
| SKL007 | Concesión de Bash sin ámbito en el frontmatter de allowed-tools de una habilidad | LLM03 |
| SKL008 | La habilidad obtiene instrucciones de una URL externa y dirige al agente a seguirlas ("Circus of Skills") | LLM04 |
| SKL009 | La habilidad persiste una puerta trasera escribiendo en otro archivo de contexto (MEMORY.md/SOUL.md/AGENTS.md/CLAUDE.md) | LLM05 |
| SKL010 | Etiqueta insegura de deserialización YAML/JSON en el frontmatter de una habilidad o en un archivo de configuración incluido | LLM04 |
| VEC001 | Búsqueda vectorial sin filtro de inquilino/usuario | LLM09 |
| VEC002 | Límite de búsqueda sin límites o controlado por el usuario | LLM06 |
| VEC003 | Contenido del usuario ingerido en un almacén vectorial compartido | LLM05 |
| VEC004 | Ingestión sin etiquetado de inquilino/espacio de nombres | LLM09 |
| DEP001 | Nombre de dependencia no encontrado en el registro (opt-in --check-dependencies) | LLM04 |
| DEP002 | Nombre de dependencia a una edición de distancia de un paquete popular (opt-in) | LLM04 |
| DEP003 | Dependencia con una versión maliciosa documentada o CVE crítico — verificado sin conexión en cada escaneo, consciente del rango de versiones (postmark-mcp, mcp-remote CVE-2025-6514, …) | LLM04 · MCP04 |
| LLC001 | Secreto codificado en un config.yaml del proxy LiteLLM | LLM02 |
| LLC002 | api_base del proxy LiteLLM accesible sobre HTTP en texto plano | LLM04 |
| LLC003 | La configuración del proxy LiteLLM no tiene sección guardrails: (heurístico, solo --paranoid) | LLM03 |