
SecureAI-Scan v0.4.0
SecureAI-Scan é uma ferramenta CLI que varre bases de código TypeScript e JavaScript em busca de problemas de segurança específicos de aplicativos alimentados por IA — injeção de prompt, abuso de ferramenta MCP, envenenamento de dados RAG, violações de confiança de agentes e mais.
SecureAI-Scan
CLI offline que analisa TypeScript, JavaScript e Python em busca de riscos de LLM, MCP, Agent Skill e RAG — evidência de fluxo de dados resolvida por importação, zero falsos positivos por padrão, mapeada para OWASP LLM/ASI/MCP Top 10.
A maioria dos scanners neste espaço faz correspondência de padrões com uma palavra-chave e chama isso de descoberta. O SecureAI-Scan rastreia o caminho real de origem → fluxo → destino através de código real, resolvido por importação — e uma varredura padrão mostra apenas o que ele pode comprovar. Sem conta, sem upload para a nuvem, nada sai da sua máquina.
Cobre o OWASP Top 10 para Aplicações LLM 2026 oficial, o Top 10 para Aplicações Agênticas (2026) e o MCP Top 10 desde a semana de lançamento.
Comece em 30 segundos```bash
npx --yes [email protected] scan .
Sem conta, upload para a nuvem, interpretador Python ou configuração necessários. TypeScript, JavaScript, Python, configs MCP e pacotes de Agent Skill são detetados automaticamente.
**Candidato a lançamento `0.9.0` medido:** 136/136 testes · 88,08% de cobertura de declarações · 12.676 ficheiros em 9 repositórios públicos · 0 novas impressões digitais de nível predefinido face à linha de base revista. [Evidência](https://github.com/akanthed/secureai-scan/blob/main/docs/benchmarks/v0.9.0.json) · [metodologia e limites](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.
Isto é para você? O SecureAI-Scan é deliberadamente escopado para riscos de LLM, MCP e RAG/agentes — injeção de prompt, envenenamento de ferramentas, tratamento inseguro de saída, controle de acesso a vector stores, envenenamento de habilidades de agentes. Não é um scanner SAST geral ou de segredos, e não tenta ser um; um pacote conhecidamente malicioso sem payload em formato LLM (por exemplo, um endereço de exfiltração hardcoded em uma chamada de API de e-mail) é capturado pela lista de avisos offline (DEP003), não por uma regra de padrão. Se seu código fala com um LLM, um servidor MCP, um vector store ou envia Agent Skills, isto foi feito para você.
Novo: varredura estática de configuração para LiteLLM Proxy (config.yaml) — segredos hardcoded, endpoints de provedor em texto puro, guardrails ausentes. Veja Regras (LLC001–LLC003).
Conteúdo
- Por que este scanner é diferente
- Como ele se compara
- Comece em 30 segundos
- Veja funcionando
- Comandos
- GitHub Action
- Hook de pre-commit
- Regras
- Arquitetura
- Servidor MCP (use a partir do Claude)
- Claude Skill
- Garantia de confiança e release
- O contrato de precisão
- Testes e benchmarking
- Roadmap
- Contribuindo
Por que este scanner é diferente
- Níveis de evidência, não ruído. Cada achado é
provado(dataflow rastreado ou fato de configuração analisado),provável(sink resolvido, um salto heurístico) ouheurístico. Uma varredura padrão mostra apenas provado + provável. Heurísticas são opcionais via--paranoid. - Detecção resolvida por importação. Uma chamada só é uma "chamada de LLM" se resolver para uma importação real de SDK (
openai,@anthropic-ai/sdk,ai,@google/genai, LangChain, Bedrock, …). Seu cliente do Google Maps nunca mais será sinalizado como LLM. - Limitado por precisão e avaliado contra repositórios reais. A suíte de testes afirma que cada fixture vulnerável dispara e que cada fixture segura permanece limpa — um falso positivo no corpus seguro quebra o build. Além disso,
npm run regressionvarre repositórios públicos reais (OpenAI/Anthropic/Vercel AI SDKs, servidores MCP oficiais, LlamaIndex) contra uma baseline commitada e revisada manualmente e falha em qualquer novo achadoprovado/provável. Veja Testes e benchmarking para os números reais de antes/depois, ou O que encontramos varrendo repositórios reais para a história por trás deles — uma taxa de captura de 6/6 em um corpus rotulado de skills maliciosas, e por que não estamos chamando llama_index de "vulnerável" por um achado honesto em nível de biblioteca. Write-up da discussão → - SARIF para code scanning do GitHub.
--output report.sarifcoloca achados inline em pull requests e na aba Security. - AI-BOM.
secureai-scan bom .constrói um inventário derivado de sintaxe de SDKs, IDs de modelo, vector stores, frameworks de agentes e servidores MCP, mapeado para as necessidades de documentação do OWASP LLM Top 10 / EU AI Act. - Varredura de configuração MCP. Analisa
.mcp.json,claude_desktop_config.json,.cursor/mcp.json: servidoresnpx -ysem pin, segredos inline, transportes HTTP em texto puro. - Detecção de envenenamento de ferramentas MCP. Captura o padrão por trás do rug-pull do WhatsApp MCP e do backdoor do postmark-mcp — Unicode invisível, frases de injeção direcionadas a agentes e shadowing entre ferramentas em nomes/descrições, estaticamente, antes de você sequer executar o servidor.
- Detecção de injeção de comando MCP. Sinaliza
command/argsdo transporte stdio MCP construídos a partir de dados de requisição — o padrão por trás da divulgação de RCE no MCP STDIO de 2026. - Detecção de envenenamento de Agent Skills. As mesmas verificações de Unicode invisível, frases de injeção e shadowing aplicadas a arquivos
SKILL.md— Agent Skills são carregados integralmente no contexto, então uma skill envenenada é uma descrição de ferramenta envenenada por outro nome. - Varredura de skills resistente a evasão. Bundles de skills são varridos como diretórios, não apenas seu
SKILL.md, e cada verificação de conteúdo roda contra variantes deofuscadas do texto. Isso visa as técnicas publicadas — homóglifos, divisão por zero-width, payloads armazenados em.git/oubuild/, exfiltração escondida em um arquivo*.test.ts— que contornaram >90% dos nove scanners pesquisados em Cloak and Detonate (arXiv:2607.02357). Veja Resistência a evasão. - Avisos de pacotes conhecidamente vulneráveis e maliciosos, cientes de versão. Verifica cada dependência e cada pacote lançado via MCP contra um snapshot de avisos embutido — uma lista curada manualmente de backdoors documentados in-the-wild, além de avisos OSV HIGH/CRITICAL para uma watchlist de pacotes LLM/MCP/RAG, regenerada por
scripts/sync-advisories.js. Roda offline em cada varredura, sem necessidade de flag. Um CVE só dispara quando sua versão fixada está provadamente dentro do intervalo afetado; um pacote documentado como malicioso dispara mesmo em um intervalo ambíguo, porque instalar um backdoor é irrecuperável. - Local-first. Nada sai da sua máquina.
Como ele se compara
O SecureAI-Scan não é um substituto para uma ferramenta SAST geral ou um scanner de contêiner/IaC — execute-o junto com uma, não no lugar de uma. Ele é feito sob medida para a superfície de ataque LLM/MCP/RAG e enfatiza evidência de dataflow em vez de achados planos por palavra-chave.
| SecureAI-Scan | Semgrep (regras OSS) | Trivy | GitHub Advanced Security | |
|---|---|---|---|---|
| Injeção de prompt (source→sink rastreado) | ✅ dataflow resolvido por importação | ⚠️ apenas regras de padrão, mantidas pela comunidade | ❌ | ⚠️ CodeQL pode, mas sem ruleset específico de IA |
| Envenenamento de ferramentas MCP / risco de configuração | ✅ MCP007–010, scanner de configuração | ❌ | ❌ | ❌ |
Envenenamento de Agent Skills (SKILL.md) | ✅ resistente a evasão, ciente de bundle | ❌ | ❌ | ❌ |
| Configuração incorreta de RAG / vector-store | ✅ VEC001–004 | ❌ | ❌ | ❌ |
| Avisos de pacotes de IA conhecidamente maliciosos | ✅ DEP003, offline, ciente de versão | ❌ | ⚠️ feed CVE geral, não específico de IA | ⚠️ Dependabot, feed CVE geral |
| SAST geral (SQLi, XSS, path traversal) | ❌ fora do escopo por design | ✅ | ❌ | ✅ |
| Varredura de contêiner / IaC | ❌ | ❌ | ✅ | ⚠️ via CodeQL/Actions |
| Níveis de evidência (provado/provável/heurístico) | ✅ | ❌ achados são planos | ❌ | ⚠️ CodeQL tem alguns, não ajustados para IA |
| Saída SARIF (code scanning do GitHub) | ✅ | ✅ | ✅ | nativo |
| Roda offline, sem conta | ✅ | ✅ (regras OSS) | ✅ | ❌ requer GitHub |
Se você já roda Semgrep ou GHAS, mantenha-os — adicione o SecureAI-Scan para a superfície de risco que eles não modelam de forma alguma.
Prefere fazer perguntas primeiro? Experimente o SecureAI-Scan AI Security Advisor no ChatGPT gratuito.
Prestes a executar um servidor MCP que encontrou no GitHub ou Twitter? Cole a descrição da ferramenta no MCP X-Ray primeiro — verifica Unicode oculto, instruções injetadas e pacotes conhecidamente maliciosos no seu navegador, sem instalação.
Veja funcionando
secureai-scan scan . de ponta a ponta, saída real contra um arquivo real (pequeno, deliberadamente vulnerável) — fonte:
Formatos de ataque que o scanner rastreia de ponta a ponta:
| Dataflow de envenenamento de ferramentas MCP | Dataflow de injeção de contexto RAG |
|---|---|
![]() | ![]() |
Comandos
O que você precisa 95% das vezes:```bash secureai-scan scan .
Tudo o resto está lá quando precisar. `secureai-scan scan . --help` mostra tudo isto no terminal, agrupado da mesma forma:
**Uso diário**
| Flag | O que faz |
|------|---------------|
| *(nenhuma)* | resultados `proven` + `likely` — o padrão, sem necessidade de flags |
| `--paranoid` | inclui também resultados de nível `heuristic` |
| `-s, --severity <level>` | mostra apenas resultados em/abaixo de `low`\|`medium`\|`high`\|`critical` |
| `--output <file>` | escreve um relatório completo — `.sarif` (code scanning do GitHub), `.json`, `.md` ou `.html` |
**Âmbito das regras que são executadas**
| Flag | O que faz |
|------|---------------|
| `-r, --rules <list>` | executa apenas estes IDs de regras, ex.: `AI001,MCP007` |
| `--only-ai` / `--only-mcp` / `--only-vec` / `--only-skl` | executa apenas uma categoria de regras |
| `--check-dependencies` | verifica também `package.json`/`requirements.txt` contra o registo npm/PyPI para erros de digitação e pacotes alucinados (`DEP001`/`DEP002`). Ativado automaticamente se selecionar essas regras diretamente via `-r` — nunca precisa de se lembrar de passar ambas. Não é necessário para `DEP003` (pacotes conhecidos como maliciosos), que é sempre executado offline |
**CI / fluxo de trabalho**
| Flag | O que faz |
|------|---------------|
| `--fail-on <severity>` | sai com código `1` se existirem resultados em/abaixo desta gravidade |
| `--baseline <file>` | monitoriza apenas problemas novos/alterados em relação a uma baseline guardada |
| `--policy <file>` | carrega limites, caminhos ignorados e regras bloqueadas de um `.secureai-policy.json` (deteção automática se presente — `secureai-scan init` cria um) |
**Avançado**
| Flag | O que faz |
|------|---------------|
| `--min-confidence <0-1>` | mais granular que `--paranoid`: oculta resultados abaixo de uma pontuação de confiança exata (`0.9` proven / `0.65` likely / `0.35` heuristic) |
| `--limit <n>` | número máximo de grupos de regras mostrados no terminal (padrão `10`) — o detalhe completo vai sempre para `--output` |
| `--debug` | imprime cada ficheiro analisado e quais regras foram executadas |
**Analise antes de instalar — sem clone, sem configuração:**```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 e mcp buscam o alvo e o analisam, depois eliminam a cópia obtida (--keep para inspecioná-la em vez disso). Nada do que é obtido é alguma vez executado: um alvo npm é descarregado com npm pack — apenas o tarball, sem install, sem scripts de ciclo de vida — e um alvo git é um simples git clone --depth 1. Este é o momento que mais importa: antes de uma skill chegar a ~/.claude/skills/ ou de um servidor chegar a .mcp.json, não depois.
Outros 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
Suprimir uma descoberta revisada no código:```ts
// secureai-ignore AI001: reviewed, input sanitized via allowlist
GitHub Action```yaml
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
As descobertas aparecem como anotações inline no PR e na aba de Segurança do repositório. (`secureai-scan init` gera um workflow equivalente usando a CLI diretamente.)
A varredura está limpa? Adicione o badge ao seu próprio README:```md
[](https://github.com/akanthed/SecureAI-Scan)
Pre-commit hook
Prefere capturar descobertas antes de serem enviadas? Adicione este repositório como fonte de hook do pre-commit em vez de, ou juntamente com, a GitHub Action:```yaml repos:
- repo: https://github.com/akanthed/SecureAI-Scan
rev: v0.10.0
hooks:
- id: secureai-scan
O hook verifica todo o projeto a cada commit (não apenas os arquivos alterados — um rastreamento de fluxo de dados no arquivo A pode depender do arquivo B, o que uma verificação parcial deixaria passar) e bloqueia o commit em descobertas de severidade `high`+ por padrão. Substitua o limite na sua própria configuração:```yaml
- id: secureai-scan
args: ["--fail-on", "critical"]
Regras
42 regras, mapeadas para o OWASP Top 10 oficial para Aplicações de LLM (2026) — além, quando aplicável, do OWASP Top 10 para Aplicações de Agentes (2026, ASI), do OWASP MCP Top 10 (2025) e de um artigo da Lei de IA da UE. Consulte a cobertura e limites da versão 2026; threat-model renderiza a matriz para cada projeto verificado.
| Regra | O que ela comprova | OWASP |
|---|---|---|
| AI001 | Entrada do usuário flui para um prompt de sistema/desenvolvedor (fonte rastreada → sumidouro, inclusive entre limites de arquivo/função) | LLM01 |
| AI002 | Conteúdo do prompt ou segredos gravados em logs (em arquivos que usam um SDK de LLM) | LLM02 |
| AI003 | Chamada de LLM em um manipulador de requisição sem verificação de autenticação antes dela | LLM06 |
| AI004 | Objeto inteiro de usuário/sessão serializado em um prompt (a seleção de campos não é sinalizada) | LLM02 |
| AI005 | Saída do LLM alcança sumidouros de eval/exec/SQL/HTML | LLM10 |
| AI006 | Ferramentas de alto impacto (delete, pay, deploy, …) expostas sem uma etapa de aprovação | LLM03 |
| AI007 | Conteúdo de RAG recuperado interpolado em prompts privilegiados | LLM01 |
| AI008 | Segredos incorporados no texto do prompt de sistema | LLM08 |
| AI009 | Entrada de usuário sem limites / limites de token ausentes | LLM06 |
| AI010 | Conteúdo externo buscado flui para prompts | LLM01 |
| AI011 | Saída do agente elevada ao papel de sistema em chamadas posteriores | LLM03 |
| AI012 | Saída do LLM analisada sem validação de esquema | LLM10 |
| MCP001 | Metadados de ferramenta MCP alcançam o prompt de sistema sem validação | LLM01 |
| MCP002 | URL do servidor MCP construída a partir de entrada do usuário | LLM04 |
| MCP003 | Resultados de ferramenta MCP elevados ao papel de sistema | LLM10 |
| MCP004 | Servidor MCP iniciado como um pacote npx -y sem fixação de versão | LLM04 |
| MCP005 | Segredo embutido em uma configuração MCP versionada | LLM02 |
| MCP006 | Servidor MCP sobre HTTP sem criptografia | LLM04 |
| MCP007 | Unicode invisível/bidi oculto em nomes ou descrições de ferramentas MCP | LLM01 · MCP03 |
| MCP008 | Frases de injeção direcionadas a agentes em descrições de ferramentas MCP | LLM01 · MCP03 |
| MCP009 | Uma descrição de ferramenta que direciona chamadas para uma ferramenta diferente (sombreamento) | LLM01 · MCP03 |
| MCP010 | Comando/argumentos do servidor MCP stdio construídos a partir de entrada do usuário (RCE) | LLM04 · MCP05 |
| SKL001 | Unicode invisível/bidi em qualquer lugar em um pacote de Skill de Agente | LLM01 |
| SKL002 | Fraseado de injeção direcionado a agentes na descrição ou no corpo de uma skill (correspondido por meio de ofuscação) | LLM01 |
| SKL003 | O conteúdo de uma skill direciona quando/como uma skill diferente é usada (sombreamento) | LLM01 |
| SKL004 | Payload encenado/autoextraível: blob opaco + instruções para decodificá-lo e executá-lo | LLM04 · MCP04 |
| SKL005 | Leitura de credenciais + egresso externo codificado em um arquivo complementar do pacote | LLM02 · MCP04 |
| SKL006 | Execução de comando em tempo de carregamento via sintaxe de injeção de contexto dinâmico do Claude Code (!`cmd`/```!), antes de qualquer etapa de permissão de ferramenta | LLM04 · MCP05 |
| SKL007 | Concessão de Bash sem escopo no frontmatter allowed-tools de uma skill | LLM03 |
| SKL008 | Skill busca instruções de uma URL externa e direciona o agente a segui-las ("Circus of Skills") | LLM04 |
| SKL009 | Skill persiste uma backdoor gravando em outro arquivo de contexto (MEMORY.md/SOUL.md/AGENTS.md/CLAUDE.md) | LLM05 |
| SKL010 | Tag insegura de desserialização YAML/JSON no frontmatter de uma skill ou em um arquivo de configuração empacotado | LLM04 |
| VEC001 | Busca vetorial sem filtro de locatário/usuário | LLM09 |
| VEC002 | Limite de busca sem limites ou controlado pelo usuário | LLM06 |
| VEC003 | Conteúdo do usuário ingerido em um armazenamento vetorial compartilhado | LLM05 |
| VEC004 | Ingestão sem marcação de locatário/namespace | LLM09 |
| DEP001 | Nome de dependência não encontrado no registro (opt-in --check-dependencies) | LLM04 |
| DEP002 | Nome de dependência a uma edição de distância de um pacote popular (opt-in) | LLM04 |
| DEP003 | Dependência com lançamento malicioso documentado ou CVE crítico — verificado offline em cada varredura, ciente de faixa de versão (postmark-mcp, mcp-remote CVE-2025-6514, …) | LLM04 · MCP04 |
| LLC001 | Segredo codificado em um config.yaml do proxy LiteLLM | LLM02 |
| LLC002 | api_base do proxy LiteLLM acessível sobre HTTP sem criptografia | LLM04 |
| LLC003 | Configuração do proxy LiteLLM sem seção guardrails: (heurística, somente --paranoid) | LLM03 |
secureai-scan explain <RULE_ID> fornece o passo a passo da exploração e um exemplo de código antes/depois para qualquer regra.
Arquitetura
Três superfícies de varredura independentes alimentam uma única lista de achados mesclada e 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)
Every AST rule only calls a function an "LLM call" if it resolves through real imports to a known SDK — never by name-matching alone. See [`docs/Architecture.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Architecture.md) for the full breakdown of each surface, and [`docs/DetectionEngine.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/DetectionEngine.md) for how the evidence-tier contract works.
## Servidor MCP (use-o a partir do Claude)
O pacote inclui um servidor MCP que expõe `scan_repository`, `explain_rule`, `generate_bom` e `scan_untrusted_target` (busca e analisa uma skill ou servidor MCP antes de o Claude recomendar a instalação — o mesmo comportamento de busca-sem-execução dos comandos CLI `skill`/`mcp`):```json
{
"mcpServers": {
"secureai-scan": {
"command": "node",
"args": ["/path/to/secureai-scan/mcp-server/index.js"]
}
}
}
Claude Skill
Para utilizadores de Claude Code / Claude.ai, o skills/secureai-scan/SKILL.md ensina o Claude quando executar uma análise (ao rever código de IA/LLM, ou ao verificar um servidor MCP/Skill de Agente antes de o instalar) e como ler os resultados — sem necessidade de um processo separado, ao contrário do servidor MCP acima. Copie o diretório skills/secureai-scan/ para o seu .claude/skills/ para o utilizar.
Resistência a evasão
Em julho de 2026, duas investigações públicas demonstraram que os scanners de Skills de Agente são trivialmente contornados.
Cloak and Detonate (Ji, Xu, Li, Gao, Wei, Wang & Cheung — HKUST, arXiv:2607.02357) realizou um estudo adversarial contra nove scanners de skills publicados, utilizando 1.613 skills maliciosas recolhidas em ambiente real. Duas famílias de ataques, ambas preservando exatamente o payload:
- Reify — reescreve os bytes que uma regra corresponde, enquanto o agente ainda remonta o original em tempo de execução. >80% de evasão na maioria dos scanners estáticos.
- Disperse / empacotamento SFS — move o payload para fora do âmbito da regra: para um diretório que o scanner ignora, atrás de uma extensão renomeada, ou arquivado e codificado em
.git/, com umSKILL.mdde cobertura benigno que o reconstrói na primeira execução. ≥90% de evasão em todos os scanners testados, ≥96% na maioria. A taxa de deteção de um scanner híbrido caiu de ~99% para ~10%.
Separadamente, a Gecko Security (VentureBeat) distribuiu um payload num ficheiro *.test.ts. Todos os scanners públicos o ignoraram — mas o Jest e o Vitest detetam e executam automaticamente esses ficheiros no próximo npm test.
O SecureAI-Scan trata cada técnica publicada. Cada linha abaixo tem um teste correspondente em test/deobfuscate.test.js ou uma fixture em test-fixtures/vulnerable/skills/:
| Técnica (nome do artigo) | Exemplo | Como é tratada |
|---|---|---|
| Inserção de largura zero | Ignore all previous… | O texto é comparado com variantes sem caracteres invisíveis |
| Glifos confundíveis | сurl (с cirílico) | Dobragem de homóglifos para ASCII antes da correspondência |
| Inversão de maiúsculas | EvAl | Todos os padrões de conteúdo são insensíveis a maiúsculas/minúsculas |
| Divisão de variáveis | 'cu' + 'rl' | Literais de string adjacentes são unidos antes da correspondência |
| Divisão por linha | ev⏎al(x) | Quebras de linha intra-palavra unidas como variante extra |
| Extração por ficheiro | payload num helper importado | Todo o pacote é analisado, não apenas SKILL.md |
| Relocalização de diretório | scripts/x.sh → build/x.sh | Sem lista de exclusão de diretórios dentro de um pacote |
| Renomeação de extensão | x.sh → x.txt | Os ficheiros são analisados por conteúdo, não por extensão |
| Preenchimento de tamanho | preencher além do limite do scanner | Ficheiros sobredimensionados são lidos no início, nunca ignorados |
| Empacotamento SFS | payload em .git/skillpack.dat | Qualquer ficheiro não-git sob o .git/ de um pacote é provado (SKL004) |
| Encenação em ficheiros de teste | payload em *.test.ts | A análise de pacotes deliberadamente não rebaixa caminhos de teste (SKL005) |
Isto não enfraquece o contrato de precisão
A desofuscação é normalmente um risco de precisão — mais correspondências, mais ruído. Aqui a lógica é invertida: uma correspondência que só aparece após a desofuscação é promovida a provado, não rebaixada. Documentação comum não contém um unidor de largura zero dentro de "ignore previous instructions", nem um с cirílico dentro de curl. A ocultação é, por si só, evidência afirmativa de intenção.
A comparação é feita contra o conjunto de correspondências brutas, não apenas "a correspondência bruta existiu ou não" — caso contrário, um atacante poderia mascarar o sinal deixando uma frase inócua em texto claro.
As duas novas regras de pacote disparam apenas em conjunções, nunca numa palavra-chave isolada:
- SKL004 requer um blob opaco e uma diretiva de desempacotamento que referencie esse blob pelo nome — um README que menciona
tar -xao lado de um ativo binário não relacionado não é suficiente. Arquivos reais (gzip/zip/png/pdf/wasm — verificados por magic bytes, não por extensão) nunca são "opacos" em primeiro lugar, independentemente de como são comprimidos. - SKL005 requer um sinal concreto de credencial — um caminho (
~/.aws/credentials, não a palavra "token") ou enumeração em massa de variáveis de ambiente (os.environ.items(), nãoos.environ["API_KEY"]) — e egresso para um host não-local codificado, dentro de 25 linhas um do outro no mesmo ficheiro, ou uma obtenção remota que é executada após ser reatribuída através de uma ou mais renomeações. Um helper de publicação que lê~/.npmrcnuma função e chama um registo quarenta linhas depois permanece limpo, e ler uma variável de ambiente nomeada para uma chamada de API nunca é sinalizado — ambas as formas estão fixadas como fixtures seguras.
Validado contra dois corpora do mundo real, não apenas fixtures que escrevemos nós próprios: 0 descobertas em todos os 18 pacotes de skills reais em anthropics/skills e todos os 14 em vercel/ai, e 6/6 corretos no corpus de avaliação rotulado de cisco-ai-defense/skill-scanner (20 skills, cada uma com um veredito _expected.json) com zero falsos positivos em qualquer coisa rotulada como segura. Consulte Testes e benchmarking.
O que isto não é
Limitação honesta: a conclusão do artigo é que a detonação em tempo de execução supera a análise estática, e isso está correto. Um adversário adaptativo que conhece estas regras pode escrever uma transformação que elas não cobrem. O que muda aqui é o custo da evasão — as técnicas publicadas e atualmente em circulação deixam de funcionar, e a ofuscação necessária para as derrotar agora eleva ela própria a gravidade da descoberta. A análise estática é um filtro, não uma fronteira de segurança. Trate uma skill não confiável como código não confiável, independentemente do que qualquer scanner diga.
Garantia de confiança e lançamento
- O CI é executado em Linux, Windows e macOS em todas as versões suportadas do Node.
- CodeQL, auditoria de dependências de produção, OpenSSF Scorecard, Dependabot e a auto-análise bloqueante deste próprio scanner fornecem verificações independentes.
- Cada publicação manual no npm invoca testes, limites mínimos de cobertura, o portão de regressão do repositório real revisto e inspeção do tarball através de
prepublishOnly. - O GitHub Actions não recebe palavra-passe ou token do npm e não pode publicar o pacote.
- Garantia de lançamento, governação de mantenedor único, comunicação de segurança e evidência de benchmark versionada são públicas.
Este é um projeto de mantenedor único, sem SLA contratual ou certificação independente. Os controlos acima reduzem o risco; não transformam uma análise estática em prova de segurança.
O contrato de precisão
Falsos positivos matam scanners. O motor de regras do SecureAI-Scan segue três regras rígidas:
- Os sinks são resolvidos através de imports. Se um identificador resolve para um módulo que não é um SDK de LLM, definitivamente não é uma chamada de LLM — independentemente do seu nome.
- A evidência é rotulada, nunca misturada. Um fluxo de dados rastreado e uma correspondência por proximidade de palavras não são a mesma coisa, por isso nunca partilham um nível.
- O corpus seguro bloqueia todos os lançamentos.
test-fixtures/safe/contém os padrões que costumavam causar falsos positivos (payloads de PII redigidos, clientes do Google Maps, chaves de API em variáveis de ambiente junto a clientes de LLM, registo de respostas comum, campos de metadados OAuth,chunksde respostas em streaming, texto de prompt de ficção/narrativa). Qualquer descoberta aí falha a suíte.
Testes e benchmarking
Três camadas, porque uma só não é suficiente para confiar nas afirmações de um scanner — precisão e recall são modos de falha diferentes, e ambos são verificados.
1. Corpus de fixtures — precisão + recall, executado em cada build.```bash npm test
[`test-fixtures/vulnerable/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/vulnerable) e [`test-fixtures/safe/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/safe) são analisados em conjunto: cada fixture vulnerável deve acionar a regra esperada com evidência `proven`/`likely` (recall), cada fixture seguro deve produzir **zero** achados `proven`/`likely` (precisão). Rápido e determinístico — mas apenas prova que o scanner se comporta em código escrito especificamente para testá-lo.
**2. Benchmark de regressão no mundo real — contra repositórios públicos que não escrevemos.**```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 um conjunto curado e diversificado de repositórios públicos reais (OpenAI/Anthropic/Vercel AI SDKs, os servidores MCP oficiais e o TypeScript SDK, LlamaIndex, além de anthropics/skills e cisco-ai-defense/skill-scanner para cobertura de pacotes de skills — abrangendo TS e Python, código de exemplo de consumidores de SDK e código-fonte de autores de SDK) e escaneia cada um com o CLI compilado.
Ele sai com código não-zero em qualquer achado proven/likely que já não esteja em test/regression-baseline.json — um registro revisado manualmente de achados já lidos em relação à sua linha de origem. As impressões digitais são repo|rule|file, não números de linha, então mudanças comuns a montante não geram ruído. Uma nova impressão digital é uma afirmação que o scanner precisa justificar: se não for um problema genuíno, é um bug de regra, corrigido na causa raiz e travado como um novo fixture test-fixtures/safe/. Incluir na baseline um achado que você não leu anula todo o mecanismo.
A cobertura de pacotes de skills tem sua própria linha porque o corpus evals/ do cisco-ai-defense/skill-scanner é rotulado — cada um de seus 20 fixtures vem com um veredito _expected.json e fica sob um diretório literalmente chamado malicious/ ou safe/, então ele funciona também como uma verificação de recall, não apenas de precisão: 6/6 fixtures maliciosos no escopo disparam, 0 achados em qualquer coisa rotulada como safe, e 0 achados em todos os 18 pacotes reais em anthropics/skills e todos os 14 em vercel/ai. (As categorias restantes da Cisco — injeção de SQL, path traversal, exaustão de recursos, eval() genérico de um argumento de função, um payload deliberadamente dividido entre quatro arquivos — estão fora do escopo documentado de LLM/MCP/RAG ou além da análise de conjunção em arquivo único; veja a entrada do changelog 0.6.0 para o raciocínio específico de cada uma.)
Antes/depois histórico da execução que motivou as correções originais de precisão (achados no nível de evidência padrão, sem --paranoid):
| Repo | Antes | Depois | O que estava errado |
|---|---|---|---|
| vercel/ai | 773 | 1 | examples/, tests/ de nível superior e diretórios com hífen no estilo ecosystem-tests/ não eram reconhecidos como caminhos de menor confiança; chunks (uma variável comum de resposta em streaming) era tratado como evidência inequívoca de RAG |
| openai/openai-node | 47 | 0 | Mesma lacuna de detecção de caminho, aplicada aos próprios examples//ecosystem-tests/ do SDK |
| anthropics/anthropic-sdk-typescript | 2 | 0 | Mesma lacuna de detecção de caminho em um diretório tests/ de nível superior |
| modelcontextprotocol/typescript-sdk | 3 | 0 | Campos de metadados OAuth no estilo token_endpoint/tokenType sinalizados como segredos vazados |
| run-llama/llama_index | 18 | 15 | Uma verificação em Python sinalizava qualquer campo description= contendo "system prompt" como envenenamento proven de ferramenta MCP, independentemente do contexto. Os 15 restantes são hits VEC001 nas definições genéricas de retriever da própria biblioteca — escanear o código-fonte de um SDK de banco vetorial, não código de aplicação, então um filtro não pode existir para ser verificado; um limite honesto e inerente, não um bug |
Execução atual (2026-08-06) — evidência versionada está registrada em docs/benchmarks/v0.9.0.json:
| Repo | Achados | Regras | Status |
|---|---|---|---|
| openai-node, anthropic-sdk-typescript, anthropic-sdk-python, modelcontextprotocol/typescript-sdk, modelcontextprotocol/servers | 0 | — | limpo |
| anthropics/skills (18 pacotes de skills reais) | 0 | — | limpo — verificação pura de precisão para SKL001–005 |
| vercel/ai (5.691 arquivos) | 0 | — | era 40 (AI001, AI003, AI005, AI010, MCP002) antes da triagem — cada um revisado manualmente contra a fonte e confirmado como falso positivo, rastreado até 3 bugs independentes de causa raiz (veja abaixo), corrigido e re-confirmado limpo em um re-escaneamento completo |
| run-llama/llama_index | 46 | VEC001 | limite inerente, não um bug — as definições genéricas de retriever da própria biblioteca, onde nenhum filtro de tenant pode existir para ser encontrado |
| cisco-ai-defense/skill-scanner | 7 | SKL001, SKL002, SKL005 | todos em fixtures rotulados como malicious/ — 6/6 no escopo, 0 em qualquer coisa rotulada como safe/ |
A triagem do vercel/ai encontrou três bugs reais com causa raiz identificada — nenhum específico das regras de skills da v0.6.0, todos em lógica compartilhada usada em muitas regras:
resolveLlmSinktratava qualquer chamada resolvida para um módulo de SDK de LLM como uma invocação de modelo, independentemente do nome do método — sinalizandoisToolUIPart(um type guard que o pacoteaiexporta ao lado degenerateText) como uma chamada de LLM. Isso sozinho causou 3 dos 5 grupos de achados (AI001, AI003, AI010).DANGEROUS_CALLEESem AI005 inclui"query"para sinks no estilo injeção de SQL, mas"query"também é um verbo legítimo de invocação de LLM/agente —claudeSdk.query({ prompt, options }), a chamada de modelo do próprio Claude Agent SDK, foi sinalizado como "saída de LLM passada para um sink perigoso" puramente por causa do nome de método compartilhado.REQUEST_SOURCES(duplicado de forma idêntica em MCP002, MCP010, VEC003) correspondia a um"params."simples — qualquer parâmetro de função convencionalmente chamadoparams, não necessariamente dados de requisição HTTP. Um validador de esquema de URL (assertOpenLinkParams(params: unknown)) foi sinalizado como "URL de servidor MCP de entrada do usuário".
Todos os três corrigidos na causa raiz (não no call site específico) e fixados como fixtures permanentes em test-fixtures/. Detalhes completos em CHANGELOG.md.
3. Validação vulnerável-vs-corrigido — prova recall, não apenas precisão.
As duas camadas acima apenas verificam que o scanner permanece silencioso em código seguro. As verificações de advisory do DEP003 são validadas no sentido oposto: fixe um pacote em uma versão documentada como vulnerável e confirme que ele é sinalizado, depois fixe-o na versão corrigida e confirme que não é.```bash
node --test test/dependency-guard.test.js
covers: `[email protected]` (CVE-2025-6514, vulnerável) sinalizado / `[email protected]` (corrigido) limpo; `[email protected]` (antes do backdoor) limpo / `[email protected]` (depois — não existe patch legítimo para um pacote malicioso) ainda sinalizado; `llama-cpp-python==0.2.71` (CVE-2024-34359, do conjunto gerado por OSV) sinalizado / `==0.2.72` (corrigido) limpo, inclusive sob a normalização de nomes do PyPI (`llama_cpp_python`); e especificadores não fixados do tipo `langchain>=0.1.0` produzindo **zero** achados no relatório padrão. Construir este teste revelou uma lacuna real: o `DEP003` costumava corresponder avisos apenas pelo nome do pacote, sem nunca comparar de fato a versão declarada com o intervalo afetado do aviso — corrigido em [`src/scanner/semver.ts`](https://github.com/akanthed/secureai-scan/blob/main/src/scanner/semver.ts).
A ambiguidade é resolvida de forma diferente por tipo de aviso, deliberadamente. Um pacote **malicioso** dispara mesmo quando a versão declarada não pode ser resolvida — instalar um backdoor é irreversível, então ele falha em direção à sinalização. Uma **CVE** dispara em `proven` apenas quando a versão declarada é um pin exato comprovadamente dentro do intervalo afetado; versões não fixadas mas possivelmente afetadas caem para `heuristic` (somente com `--paranoid`). Aplicar a regra do tipo malicioso a um snapshot de CVE com 162 entradas colocaria um achado crítico em todo repositório que declara `langchain>=0.1.0` — ruído inacionável em escala.
## Roadmap
Consulte [`ROADMAP.md`](https://github.com/akanthed/secureai-scan/blob/main/ROADMAP.md) para saber o que foi lançado e o que está planejado. Ambos os mecanismos de linguagem são baseados em AST: ts-morph para TypeScript/JavaScript e Tree-sitter para Python. Imports, chamadas, atribuições, decoradores, escopos, argumentos nomeados, campos de dicionário e strings em Python são nós de sintaxe; o código-alvo nunca é importado ou executado, e nenhum interpretador Python é necessário. A lacuna restante do Python é a profundidade limitada de taint entre funções/arquivos, não a análise. O desempenho da varredura e os limites conhecidos estão documentados em [`docs/Performance.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Performance.md).
## Contribuindo
Contribuições são bem-vindas — consulte [`CONTRIBUTING.md`](https://github.com/akanthed/secureai-scan/blob/main/CONTRIBUTING.md) para o fluxo de trabalho, e [`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 como adicionar uma regra de detecção que atenda ao padrão de precisão acima. Cada nova regra precisa de um fixture tanto em [`test-fixtures/vulnerable/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/vulnerable) quanto em [`test-fixtures/safe/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/safe), uma entrada em `src/scanner/catalog.ts` e um caso em `test/corpus.test.js` — `npm test` aplica os três.
## Licença
MIT © Akshay Kanthed

