skill-vet — skill de agente
Verifique um pacote de skill de agente antes de instalá-lo, com um comando. Use-o sempre que uma tarefa envolver uma skill de fora desta máquina — um download do marketplace, um repositório que alguém linkou, um pacote que um cliente enviou — antes que o pacote seja instalado em qualquer lugar onde um agente o carregará.
O comando único
skill-vet vet <pasta-do-pacote-de-skill> [--json] [--strict]
(Instalado via pipx install skill-vet ou pip install skill-vet. A partir de um checkout da fonte, o mesmo comando é
python3 src/skill_vet.py vet <pasta-do-pacote-de-skill> ….)
- O argumento é a pasta raiz do pacote (aquela que contém SKILL.md). Um pacote por execução.
- Códigos de saída: 0 veredito CLEAN ou REVIEW, 1
veredito BLOCKED (ou REVIEW com
--strict), 2 erro de entrada (pasta ausente, não é uma pasta, argumentos inválidos). No modo--json, todo erro — incluindo erros de argumento — é um objeto JSON com uma chaveerror. Nunca um traceback. - Somente leitura, e não executa nada. O Vet lê o pacote como texto estático: nunca importa, executa, chama shell ou avalia nada no pacote. Sua própria fonte não importa módulos de rede ou subprocesso. Seguro para apontar para um pacote no qual você ativamente desconfia.
O modelo de perigo (o produto completo)
Um pacote é PERIGOSO apenas se fizer uma de três coisas — a regra fixa do proprietário (2026-10-30), aplicada exatamente:
- ROUBA — lê armazenamentos de credenciais, perfis de navegador, chaves SSH ou segredos de ambiente E envia ou copia os resultados para fora.
- ESPIA — captura tela, teclado, microfone, câmera ou histórico do navegador E persiste ou envia a captura, sem que a documentação divulgue a captura.
- ENVIA EM SEGREDO — chamadas de rede cujo payload ou destino está oculto: um payload codificado para um endpoint não divulgado, conteúdo de arquivo local enviado para um endpoint não divulgado, um envio combinado com gravações fora do pacote, ou qualquer envio de um pacote cuja documentação afirma que funciona offline.
Um padrão isolado nunca é um veredito de perigo. Cada teste precisa de sua combinação de evidências no mesmo arquivo, e a descoberta cita ambas as metades com arquivo e linha. Qualquer coisa abaixo de uma combinação é um aviso com a evidência declarada. As combinações:
| Evidência em um arquivo | Veredito |
|---|---|
| leitura de segredo + envio, ou leitura de segredo + cópia/movimento para um caminho externo | DANGER_STEALING (BLOCKED) |
| captura + (envio ou persistência), tipo de captura não divulgado em SKILL.md / README.md | DANGER_SPYING (BLOCKED) |
| envio + payload codificado, endpoint não divulgado ou não literal | DANGER_HIDDEN_SENDING (BLOCKED) |
| envio + leitura de arquivos fora do pacote, endpoint não divulgado | DANGER_HIDDEN_SENDING (BLOCKED) |
| envio + gravação fora do pacote, endpoint não divulgado | DANGER_HIDDEN_SENDING (BLOCKED) |
| envio + documentação afirma offline / apenas local | DANGER_HIDDEN_SENDING (BLOCKED) |
| leitura de segredo, sem egresso no arquivo | SECRET_ACCESS (WARN) |
| envio para um endpoint que a documentação nunca menciona, sem combinação de ocultação | UNDISCLOSED_NETWORK (WARN) |
| envio sem URL literal para verificar | DYNAMIC_ENDPOINT (WARN) |
| payload codificado para um endpoint DIVULGADO | ENCODED_PAYLOAD (WARN) |
| captura + persistência/envio, captura divulgada na documentação | DISCLOSED_CAPTURE (WARN) |
| captura, sem persistência ou envio | CAPTURE_CAPABILITY (WARN) |
Julgamentos, declarados abertamente:
- Roubo não precisa de ocultação: enviar uma credencial para um endpoint documentado ainda é roubo — a documentação admitir isso não o torna seguro. A divulgação apenas separa espionagem e envio oculto de suas contrapartes honestas.
- Um envio já julgado como a metade de egresso de uma descoberta de ROUBO no mesmo arquivo não é contado uma segunda vez como envio oculto.
- A "divulgação" de captura é verificada por tipo contra palavras simples em SKILL.md / README.md (screenshot, keystroke, microphone, webcam, browser history, …). Um pacote que captura e diz isso é uma capacidade a ser aceita conscientemente, não um espião.
- Os ingredientes de egresso são coletados por arquivo em todas as suas linhas, incluindo comentários (conservador: a evidência de capacidade não é descartada porque está perto de um comentário). As verificações de higiene (shell=True, eval, download-exec) pulam comentários de linha completa — um comentário mencionando um padrão é documentação, não código.
A tabela de regras
| Regra | Gravidade | Base |
|---|---|---|
MISSING_SKILL_MD | ERROR | Nenhum SKILL.md na raiz do pacote (um nome com caixa diferente gera SKILL_MD_CASE, WARN, e é usado). |
FRONTMATTER_MISSING | ERROR | SKILL.md não tem bloco ---. |
FRONTMATTER_PARSE | ERROR | Uma linha de frontmatter fora do subconjunto suportado (veja abaixo); a linha é citada. |
REQUIRED_FIELD | ERROR | name ou description ausente ou vazio. |
NAME_DIR_MISMATCH | WARN | Nome do frontmatter normalizado ≠ nome da pasta. |
NAME_FORMAT | WARN | O nome não é um slug kebab-case minúsculo. |
BROAD_TOOL_GRANT | WARN | Uma entrada allowed-tools que é *, um curinga Tool(*) ou um Bash simples. Formas com escopo como Bash(npm run *) não são sinalizadas. |
BROKEN_REFERENCE | ERROR | Um arquivo local referenciado de SKILL.md (link markdown ou caminho entre crases) ou de um script (literal de caminho) não existe. A resolução tenta a pasta do arquivo de referência, depois a raiz do pacote. |
REFERENCE_OUTSIDE_PACK | WARN | Uma referência resolvendo fora da pasta do pacote. |
LARGE_FILE | WARN | Um arquivo acima de 1 MiB (1.048.576 bytes). |
BUNDLED_BINARY | WARN | Um arquivo binário (extensão binária conhecida ou um byte NUL nos primeiros 8 KiB). Binários não podem ser verificados estaticamente. |
SYMLINK_OUT | INFO | Um link simbólico cujo destino sai do pacote; o destino não é escaneado. |
DANGER_STEALING | ERROR, danger | A tabela de combinação acima. |
DANGER_SPYING | ERROR, danger | A tabela de combinação acima. Tipos de captura: tela, teclado, áudio, câmera, histórico (uma leitura de armazenamento de histórico mais uma leitura de banco de dados em um arquivo). |
DANGER_HIDDEN_SENDING | ERROR, danger | A tabela de combinação acima. |
SECRET_ACCESS | WARN | Uma leitura de armazenamento de credenciais / segredo de ambiente sem egresso no mesmo arquivo. Fontes de segredo: .ssh / arquivos de chave, .aws, .gnupg, .netrc, arquivos de credenciais armazenadas, armazenamentos de navegador e pastas de perfil, arquivos .env, acesso de ambiente nomeando um segredo (TOKEN / SECRET / PASSWORD / API_KEY / …), leituras de ambiente inteiro. |
UNDISCLOSED_NETWORK | WARN | Uma chamada de rede cujo host do endpoint não aparece nem em SKILL.md nem em README.md. Um host conta como divulgado quando ele — ou um domínio pai dele — aparece como uma URL na documentação. |
DYNAMIC_ENDPOINT | WARN | Uma chamada de rede sem URL literal em nenhum lugar do arquivo. |
ENCODED_PAYLOAD | WARN | A saída de um codificador atinge um envio para um endpoint divulgado. "Atinge" é julgado por: a chamada do codificador dentro do segmento de chamada do envio, ou uma variável atribuída de um codificador usada no segmento. |
DISCLOSED_CAPTURE | WARN | Captura + persistência/envio, divulgado por tipo. |
CAPTURE_CAPABILITY | WARN | Uma API de captura sem persistência ou envio no arquivo. |
HYGIENE_SHELL_TRUE | WARN | shell=True / shell: true em uma linha de código. |
HYGIENE_DYNAMIC_EXEC | WARN | eval( / exec( / os.system( / os.popen( / __import__( em uma linha de código. |
HYGIENE_DOWNLOAD_EXEC | WARN | Um download canalizado para um shell (`curl … |
HYGIENE_OBFUSCATION | WARN | Um blob base64 (120+ caracteres, maiúsculas e minúsculas misturadas com um dígito ou +/), um blob hex (120+ caracteres) ou uma sequência de 8+ escapes \xNN, em um arquivo de texto. Uma descoberta por tipo por arquivo. |
CONCEALED_UNICODE | WARN | Caracteres invisíveis (largura zero, juntador de palavras, hífen suave, controles bidirecionais, caracteres de tag Unicode) em um arquivo de texto; contagem, primeira linha e nomes de caracteres citados. Um BOM inicial é uma assinatura, não ocultação, e é ignorado. |
CONCEALED_BLANK_RUN | WARN | 50+ linhas em branco consecutivas em um arquivo markdown com conteúdo após elas. |
HIDDEN_COMMENT | WARN | Um comentário HTML de 120+ caracteres em um arquivo markdown. |
WRITES_OUTSIDE_PACK | WARN | Um script grava em um caminho absoluto, home ou do sistema (/tmp, /etc, ~/, $HOME, …). |
INSTALL_HOOK | WARN | Um arquivo que é executado no momento da instalação: setup.py, install.* / setup.sh, um arquivo .pth, ou um package.json com um hook preinstall/install/postinstall. |
Vereditos: BLOCKED = pelo menos uma descoberta de perigo
→ saída 1. REVIEW = nenhum perigo, mas pelo menos um
ERROR ou WARN → saída 0 (1 com --strict).
CLEAN = nenhum ERROR ou WARN (INFO pode permanecer) →
saída 0. REVIEW sai 0 de propósito: REVIEW significa "um
humano deve ler isso", e scripts que bloqueiam instalações
devem falhar em perigo, não em avisos — --strict existe
para quem discorda.
O subconjunto de frontmatter
O analisador lê a forma de frontmatter que os pacotes de skill
realmente enviam: escalares chave: valor (aspas removidas),
chave: [a, b] listas inline, chave: seguido por - item
listas em bloco e linhas de continuação indentadas dobradas
no escalar anterior. Ele é deliberadamente não um
analisador YAML completo. Uma linha fora do subconjunto é um
erro FRONTMATTER_PARSE com a linha citada — o vet
relata o que não pode ler em vez de adivinhar.
Escopo da varredura
- Cada arquivo no pacote é inventariado com seu tamanho.
Diretórios
.gite__pycache__são pulados. - A varredura de texto cobre scripts (por extensão ou shebang), markdown e outros arquivos de texto, até 2 MiB por arquivo. Arquivos binários e alvos de link simbólico fora do pacote são inventariados, nunca escaneados.
- Referências são coletadas de SKILL.md e de arquivos de script. Referências dentro de outros arquivos markdown (um references/*.md linkando adiante) não são seguidas no v1.0.0.
- A divulgação é julgada contra o SKILL.md e README.md da raiz do pacote apenas — os dois documentos que uma pessoa instalando o pacote deve ter lido.
Esquema JSON (--json)
Um objeto:
tool,version,commandpack:dir(como passado),dirname,name(nome do frontmatter ou null),skill_filefiles[]:path,bytes,kind(script|markdown|text|binary|other),binary,scanned,symlink_outfindings[]:rule,severity(ERROR|WARN|INFO),danger(bool),file(ou null),line(ou null),message,fixsummary:findings,errors,warnings,info,danger— contagens que recontam exatamente contrafindingsverdict(CLEAN|REVIEW|BLOCKED),verdict_rule,limits
As descobertas são ordenadas deterministicamente: perigo primeiro, depois gravidade, arquivo, linha, regra — o mesmo pacote sempre produz o mesmo relatório.
Não-objetivos deliberados (honestidade)
- Nunca executa nada. Nenhum script é executado, importado, compilado ou analisado por shell; nenhum payload é decodificado (um blob codificado é relatado, não aberto).
- Padrões estáticos são evidência, não prova. Um pacote que monta seu comportamento em tempo de execução a partir de peças inofensivas, divide uma combinação entre dois arquivos (o vet julga combinações por arquivo) ou escreve sua engenharia social em frases simples e honestas pode passar. Um veredito CLEAN significa que nenhum padrão disparou — o README diz isso, e todo relatório também.
- Binários estão fora de alcance. Um binário empacotado é sinalizado precisamente porque o vet não pode dizer nada sobre ele.
- Sem telemetria, sem rede, sem gravações. Apenas stdlib; a fonte não contém código de rede ou subprocesso (um teste analisa suas importações e prova isso).
- Tabelas de padrões são uma foto instantânea. Novas bibliotecas de captura, novos armazenamentos de credenciais e novas APIs de cliente aparecem constantemente; as tabelas na fonte são datadas pela versão, não tratadas como completas.