CommunityProgramación y desarrollogithub.com

skill-vet — evaluación de skills para agentes

Evalúa un skill pack de agente antes de instalarlo: detección de robo, espionaje, envío oculto, con evidencia de archivo y línea. Estático, solo lectura, Python puro.

¿Qué es skill-vet — evaluación de skills para agentes?

skill-vet es una herramienta de seguridad estática diseñada para analizar skill packs de agentes de IA (como Claude Code, Cursor o Codex) antes de instalarlos, garantizando que no contengan comportamientos maliciosos. Es capaz de detectar tres categorías principales de peligro: robo de credenciales (STEALING), captura de pantalla/teclado/micro/cámara sin declaración (SPYING), y envío oculto de datos (HIDDEN SENDING), todo ello mediante análisis de patrones combinados en un mismo archivo, citando línea y archivo. La herramienta es completamente de solo lectura: no ejecuta, importa ni evalúa ningún código del pack; su funcionamiento se basa en Python puro, sin dependencias de red ni subprocesos. Soporta las plataformas más comunes de agentes y emite un veredicto (CLEAN, REVIEW o BLOCKED) con una salida JSON detallada para integración en pipelines de CI/CD. Además, incluye reglas de higiene (shell=True, eval, descarga+ejecución, ofuscación) y verificación de metadatos del skill (frontmatter, referencias, tamaño de archivos).

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/jasveerai88/skill-vet/tree/HEAD

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

skill-vet — skill para agentes

Evalúa un skill pack de agente antes de instalarlo, con un solo comando. Úsalo siempre que una tarea involucre un skill de fuera de esta máquina — una descarga del marketplace, un repositorio que alguien enlazó, un pack que un cliente envió — antes de que el pack se instale en cualquier lugar donde un agente lo cargue.

El comando único

skill-vet vet <carpeta-del-skill-pack> [--json] [--strict]

(Instalado mediante pipx install skill-vet o pip install skill-vet. Desde un checkout de la fuente, el mismo comando es python3 src/skill_vet.py vet <carpeta-del-skill-pack> ….)

  • El argumento es la carpeta raíz del pack (la que contiene SKILL.md). Un pack por ejecución.
  • Códigos de salida: 0 veredicto CLEAN o REVIEW, 1 veredicto BLOCKED (o REVIEW con --strict), 2 error de entrada (carpeta faltante, no es una carpeta, argumentos incorrectos). En modo --json, cada error — incluyendo errores de argumento — es un objeto JSON con una clave error. Nunca un traceback.
  • Solo lectura y no ejecuta nada. Vet lee el pack como texto estático: nunca importa, ejecuta, llama a subprocesos ni evalúa nada del pack. Su propio código fuente no importa ningún módulo de red o subprocesos. Es seguro apuntarlo a un pack que desconfíes activamente.

El modelo de peligro (el producto completo)

Un pack es PELIGROSO solo si hace una de tres cosas — la regla permanente del mantenedor (2026-10-30), aplicada exactamente:

  1. ROBA — lee almacenes de credenciales, perfiles de navegador, claves SSH o secretos de entorno Y envía o copia los resultados fuera.
  2. ESPÍA — captura pantalla, teclado, micrófono, cámara o historial del navegador Y persiste o envía la captura, sin que la documentación la revele.
  3. ENVÍA EN SECRETO — llamadas de red cuyo payload o destino están ocultos: un payload codificado a un endpoint no revelado, contenido de archivos locales enviados a un endpoint no revelado, un envío combinado con escrituras fuera del pack, o cualquier envío desde un pack cuya documentación afirma que funciona sin conexión.

Un patrón aislado nunca es un veredicto de peligro. Cada prueba necesita su combinación de evidencia en el mismo archivo, y el hallazgo cita ambas mitades con archivo y línea. Cualquier cosa que no sea una combinación es una advertencia con la evidencia enunciada. Las combinaciones:

Evidencia en un archivoVeredicto
lectura de secreto + envío, o lectura de secreto + copia/mover a una ruta externaDANGER_STEALING (BLOQUEADO)
captura + (envío o persistencia), tipo de captura no revelado en SKILL.md / README.mdDANGER_SPYING (BLOQUEADO)
envío + payload codificado, endpoint no revelado o no literalDANGER_HIDDEN_SENDING (BLOQUEADO)
envío + lectura de archivos fuera del pack, endpoint no reveladoDANGER_HIDDEN_SENDING (BLOQUEADO)
envío + escritura fuera del pack, endpoint no reveladoDANGER_HIDDEN_SENDING (BLOQUEADO)
envío + documentación afirma offline / solo localDANGER_HIDDEN_SENDING (BLOQUEADO)
lectura de secreto, sin egress en el archivoSECRET_ACCESS (AVISO)
envío a un endpoint que la documentación nunca menciona, sin combinación de ocultamientoUNDISCLOSED_NETWORK (AVISO)
envío sin URL literal para verificarDYNAMIC_ENDPOINT (AVISO)
payload codificado a un endpoint REVELADOENCODED_PAYLOAD (AVISO)
captura + persistencia/envío, captura revelada en la documentaciónDISCLOSED_CAPTURE (AVISO)
captura, sin persistencia ni envíoCAPTURE_CAPABILITY (AVISO)

Decisiones de criterio, enunciadas abiertamente:

  • Robar no necesita ocultamiento: enviar una credencial a un endpoint documentado sigue siendo robo — que la documentación lo admita no lo hace seguro. La revelación solo separa el espionaje y el envío oculto de sus contrapartes honestas.
  • Un envío ya juzgado como la mitad de egress de un hallazgo de ROBO en el mismo archivo no se cuenta una segunda vez como envío oculto.
  • La "revelación" de captura se verifica por tipo contra palabras simples en SKILL.md / README.md (captura de pantalla, pulsación de tecla, micrófono, cámara web, historial del navegador, …). Un pack que captura y lo dice es una capacidad que se acepta con conocimiento, no un espía.
  • Los ingredientes de egress se recopilan por archivo en todas sus líneas, incluidos comentarios (conservador: la evidencia de capacidad no se descarta porque esté cerca de un comentario). Las comprobaciones de higiene (shell=True, eval, descarga+ejecución) omiten líneas de comentario completas — un comentario que menciona un patrón es documentación, no código.

La tabla de reglas

ReglaGravedadBase
MISSING_SKILL_MDERRORNo hay SKILL.md en la raíz del pack (un nombre con mayúsculas distintas genera SKILL_MD_CASE, AVISO, y se usa).
FRONTMATTER_MISSINGERRORSKILL.md no tiene bloque ---.
FRONTMATTER_PARSEERRORUna línea de frontmatter fuera del subconjunto soportado (ver abajo); se cita la línea.
REQUIRED_FIELDERRORname o description faltante o vacío.
NAME_DIR_MISMATCHAVISOEl nombre normalizado del frontmatter ≠ nombre de la carpeta.
NAME_FORMATAVISOEl nombre no es un slug en kebab-case minúsculas.
BROAD_TOOL_GRANTAVISOUna entrada de allowed-tools que es *, un comodín Tool(*), o un Bash simple. Formas con ámbito como Bash(npm run *) no se marcan.
BROKEN_REFERENCEERRORUn archivo local referenciado desde SKILL.md (enlace markdown o ruta entre backticks) o desde un script (ruta literal) no existe. La resolución prueba la carpeta del archivo referenciador, luego la raíz del pack.
REFERENCE_OUTSIDE_PACKAVISOUna referencia que se resuelve fuera de la carpeta del pack.
LARGE_FILEAVISOUn archivo de más de 1 MiB (1 048 576 bytes).
BUNDLED_BINARYAVISOUn archivo binario (extensión binaria conocida, o un byte NUL en los primeros 8 KiB). Los binarios no se pueden evaluar estáticamente.
SYMLINK_OUTINFOUn enlace simbólico cuyo objetivo sale del pack; el objetivo no se escanea.
DANGER_STEALINGERROR, peligroLa tabla de combinaciones anterior.
DANGER_SPYINGERROR, peligroLa tabla de combinaciones anterior. Tipos de captura: pantalla, teclado, audio, cámara, historial (una lectura de almacén de historial más una lectura de base de datos en un archivo).
DANGER_HIDDEN_SENDINGERROR, peligroLa tabla de combinaciones anterior.
SECRET_ACCESSAVISOLectura de almacén de credenciales / secreto de entorno sin egress en el mismo archivo. Fuentes de secretos: archivos .ssh / de clave, .aws, .gnupg, .netrc, archivos de credenciales almacenadas, almacenes y carpetas de perfil del navegador, archivos .env, acceso a entorno que nombre un secreto (TOKEN / SECRET / PASSWORD / API_KEY / …), lecturas de entorno completo.
UNDISCLOSED_NETWORKAVISOUna llamada de red cuyo host de endpoint no aparece ni en SKILL.md ni en README.md. Un host cuenta como revelado cuando él — o un dominio padre — aparece como URL en la documentación.
DYNAMIC_ENDPOINTAVISOUna llamada de red sin URL literal en ningún lugar del archivo.
ENCODED_PAYLOADAVISOLa salida de un codificador llega a un envío a un endpoint revelado. "Llega" se juzga por: la llamada al codificador dentro del segmento de llamada del envío, o una variable asignada desde un codificador usado en el segmento.
DISCLOSED_CAPTUREAVISOCaptura + persistencia/envío, revelada por tipo.
CAPTURE_CAPABILITYAVISOUna API de captura sin persistencia ni envío en el archivo.
HYGIENE_SHELL_TRUEAVISOshell=True / shell: true en una línea de código.
HYGIENE_DYNAMIC_EXECAVISOeval( / exec( / os.system( / os.popen( / __import__( en una línea de código.
HYGIENE_DOWNLOAD_EXECAVISOUna descarga canalizada a un shell (`curl …
HYGIENE_OBFUSCATIONAVISOUn blob base64 (120+ caracteres, mayúsculas y minúsculas con un dígito o +/), un blob hexadecimal (120+ caracteres), o una secuencia de 8+ escapes \xNN, en un archivo de texto. Un hallazgo por tipo por archivo.
CONCEALED_UNICODEAVISOCaracteres invisibles (ancho cero, unión de palabras, guión suave, controles bidi, caracteres de etiqueta Unicode) en un archivo de texto; se citan el recuento, la primera línea y los nombres de los caracteres. Un BOM inicial es una firma, no ocultamiento, y se ignora.
CONCEALED_BLANK_RUNAVISO50+ líneas en blanco consecutivas en un archivo markdown con contenido después de ellas.
HIDDEN_COMMENTAVISOUn comentario HTML de 120+ caracteres en un archivo markdown.
WRITES_OUTSIDE_PACKAVISOUn script escribe en una ruta absoluta, del hogar o del sistema (/tmp, /etc, ~/, $HOME, …).
INSTALL_HOOKAVISOUn archivo que se ejecuta en tiempo de instalación: setup.py, install.* / setup.sh, un archivo .pth, o un package.json con un hook preinstall/install/postinstall.

Veredictos: BLOQUEADO = al menos un hallazgo de peligro → salida 1. REVISAR = sin peligro, pero al menos un ERROR o AVISO → salida 0 (1 con --strict). LIMPIO = sin ERROR ni AVISO (pueden quedar INFO) → salida 0. REVISAR sale con 0 a propósito: REVISAR significa "un humano debería leer esto", y los scripts que controlan instalaciones deberían fallar por peligro, no por advertencias — --strict existe para quienes discrepan.

El subconjunto de frontmatter

El analizador lee la forma de frontmatter que los skill packs realmente envían: escalares clave: valor (comillas eliminadas), listas en línea clave: [a, b], listas en bloque clave: seguidas de - elemento, y líneas de continuación indentadas plegadas en el escalar anterior. No es deliberadamente un analizador YAML completo. Una línea fuera del subconjunto es un error FRONTMATTER_PARSE con la línea citada — vet informa lo que no puede leer en lugar de adivinar.

Alcance del escaneo

  • Cada archivo en el pack se inventaria con su tamaño. Se omiten los directorios .git y __pycache__.
  • El escaneo de texto cubre scripts (por extensión o shebang), markdown y otros archivos de texto, hasta 2 MiB por archivo. Los archivos binarios y los objetivos de enlaces simbólicos fuera del pack se inventarian, nunca se escanean.
  • Las referencias se recopilan de SKILL.md y de archivos de script. Las referencias dentro de otros archivos markdown (un archivo references/*.md que enlaza hacia adelante) no se siguen en v1.0.0.
  • La revelación se juzga solo contra SKILL.md y README.md de la raíz del pack — los dos documentos que se espera que una persona que instale el pack haya leído.

Esquema JSON (--json)

Un objeto:

  • tool, version, command
  • pack: dir (tal como se pasó), dirname, name (nombre del frontmatter o null), skill_file
  • files[]: path, bytes, kind (script|markdown|text|binary|other), binary, scanned, symlink_out
  • findings[]: rule, severity (ERROR|WARN|INFO), danger (bool), file (o null), line (o null), message, fix
  • summary: findings, errors, warnings, info, danger — recuentos que coinciden exactamente con findings
  • verdict (CLEAN|REVIEW|BLOCKED), verdict_rule, limits

Los hallazgos se ordenan de forma determinista: primero peligro, luego gravedad, archivo, línea, regla — el mismo pack siempre produce el mismo informe.

No objetivos deliberados (honestidad)

  • Nunca ejecuta nada. Ningún script se ejecuta, importa, compila o analiza sintácticamente; ningún payload se descodifica (un blob codificado se informa, no se abre).
  • Los patrones estáticos son evidencia, no prueba. Un pack que ensambla su comportamiento en tiempo de ejecución a partir de piezas inocuas, divide una combinación entre dos archivos (vet juzga combinaciones por archivo), o escribe su ingeniería social en oraciones claras y honestas puede pasar. Un veredicto LIMPIO significa que ningún patrón se activó — el README lo dice, y también lo dice cada informe.
  • Los binarios están fuera de alcance. Un binario empaquetado se marca precisamente porque vet no puede decir nada sobre él.
  • Sin telemetría, sin red, sin escrituras. Solo stdlib; el código fuente no contiene código de red o subprocesos en absoluto (una prueba analiza sus importaciones y lo demuestra).
  • Las tablas de patrones son una instantánea. Aparecen constantemente nuevas bibliotecas de captura, nuevos almacenes de credenciales y nuevas API cliente; las tablas en el código fuente están fechadas por la versión, no se consideran completas.

Skills relacionados