
SecureAI-Scan v0.6.0
SecureAI-Scan è uno strumento CLI che analizza codebase TypeScript e JavaScript per problemi di sicurezza specifici delle app basate su AI — prompt injection, abuso di strumenti MCP, avvelenamento dei dati RAG, violazioni della fiducia degli agenti e altro.
SecureAI-Scan
CLI offline che esegue la scansione di TypeScript, JavaScript e Python per rischi LLM, MCP, Agent Skill e RAG — evidenze di dataflow risolte tramite import, zero falsi positivi di default, mappate su OWASP LLM/ASI/MCP Top 10.
La maggior parte degli scanner in questo ambito confronta una parola chiave e la definisce un riscontro. SecureAI-Scan traccia il percorso effettivo sorgente → flusso → sink attraverso codice reale, risolto tramite import — e una scansione di default mostra solo ciò che può dimostrare. Nessun account, nessun caricamento sul cloud, nulla lascia la tua macchina.
Copre l'OWASP Top 10 ufficiale per applicazioni LLM 2026, il Top 10 per applicazioni agentiche (2026) e il MCP Top 10 dalla settimana di lancio.
Inizia in 30 secondi```bash
npx --yes [email protected] scan .
Nessun account, caricamento su cloud, interprete Python o configurazione richiesti. TypeScript, JavaScript, Python, configurazioni MCP e bundle di Agent Skill vengono rilevati automaticamente.
**Release candidate `0.9.0` misurata:** 136/136 test · 88,08% di copertura delle istruzioni · 12.676 file in 9 repository pubblici · 0 nuove impronte digitali di livello predefinito rispetto alla baseline revisionata. [Evidenza](https://github.com/akanthed/secureai-scan/blob/main/docs/benchmarks/v0.9.0.json) · [metodologia e limiti](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.
È per te? SecureAI-Scan è deliberatamente limitato ai rischi LLM, MCP e RAG/agent — injection di prompt, avvelenamento degli strumenti, gestione non sicura dell'output, controllo degli accessi al vector store, avvelenamento delle skill degli agenti. Non è uno scanner SAST generico né un cercatore di segreti, e non cerca di esserlo; un pacchetto notoriamente dannoso senza payload in stile LLM (ad es. un indirizzo di esfiltrazione hardcoded in una chiamata API email) viene rilevato dall'elenco advisory offline (DEP003), non da una regola pattern. Se il tuo codebase parla con un LLM, un server MCP, un vector store o distribuisce Agent Skills, questo strumento è fatto per te.
Novità: scansione statica della configurazione per LiteLLM Proxy (config.yaml) — segreti hardcoded, endpoint provider in chiaro, guardrail mancanti. Vedi Regole (LLC001–LLC003).
Contenuti
- Perché questo scanner è diverso
- Come si confronta
- Inizia in 30 secondi
- Vedilo in azione
- Comandi
- GitHub Action
- Hook pre-commit
- Regole
- Architettura
- Server MCP (usalo da Claude)
- Claude Skill
- Garanzia di fiducia e rilascio
- Il contratto di precisione
- Test e benchmark
- Roadmap
- Contribuire
Perché questo scanner è diverso
- Livelli di evidenza, non rumore. Ogni riscontro è
proven(dataflow tracciato o fatto di configurazione parsato),likely(sink risolto, un salto euristico) oheuristic. Una scansione predefinita mostra solo proven + likely. Le euristiche sono opzionali tramite--paranoid. - Rilevamento risolto tramite import. Una chiamata è una "chiamata LLM" solo se risolve a un import SDK reale (
openai,@anthropic-ai/sdk,ai,@google/genai, LangChain, Bedrock, …). Il tuo client di Google Maps non verrà mai più segnalato come LLM. - Gated sulla precisione e benchmarkato su repository reali. La suite di test asserisce che ogni fixture vulnerabile scatti e che ogni fixture sicura resti pulita — un falso positivo sul corpus sicuro fa fallire la build. Inoltre,
npm run regressionscansiona repository pubblici reali (OpenAI/Anthropic/Vercel AI SDKs, server MCP ufficiali, LlamaIndex) contro una baseline committata e revisionata manualmente e fallisce su qualsiasi nuovo riscontroproven/likely. Vedi Test e benchmark per i numeri reali prima/dopo, oppure Cosa abbiamo trovato scansionando repository reali per la storia dietro quei numeri — un tasso di rilevamento 6/6 su un corpus di skill dannose etichettate, e perché non stiamo definendo llama_index "vulnerabile" per un riscontro onesto a livello di libreria. Write-up della discussione → - SARIF per GitHub code scanning.
--output report.sarifmette i riscontri inline sulle pull request e nella scheda Security. - AI-BOM.
secureai-scan bom .costruisce un inventario derivato dalla sintassi di SDK, ID modello, vector store, framework agent e server MCP, mappato alle esigenze documentali di OWASP LLM Top 10 / EU AI Act. - Scansione configurazione MCP. Analizza
.mcp.json,claude_desktop_config.json,.cursor/mcp.json: servernpx -ynon pinnati, segreti inline, trasporti HTTP in chiaro. - Rilevamento avvelenamento strumenti MCP. Cattura il pattern dietro il rug-pull di WhatsApp MCP e il backdoor di postmark-mcp — Unicode invisibile, frasi di injection dirette all'agente e shadowing tra strumenti in nomi/descrizioni, staticamente, prima ancora di eseguire il server.
- Rilevamento command-injection MCP. Segnala i
command/argsdel trasporto stdio MCP costruiti da dati di richiesta — il pattern dietro la divulgazione RCE MCP STDIO del 2026. - Rilevamento avvelenamento Agent Skill. Gli stessi controlli di Unicode invisibile, frasi di injection e shadowing applicati ai file
SKILL.md— le Agent Skills vengono caricate nel contesto per intero, quindi una skill avvelenata è una descrizione di strumento avvelenata con un altro nome. - Scansione skill resistente all'evasione. I bundle di skill vengono scansionati come directory, non solo il loro
SKILL.md, e ogni controllo di contenuto viene eseguito su varianti deoffuscate del testo. Questo mira alle tecniche pubblicate — omoglifi, suddivisione a larghezza zero, payload collocati in.git/obuild/, esfiltrazione nascosta in un file*.test.ts— che hanno bypassato >90% dei nove scanner esaminati in Cloak and Detonate (arXiv:2607.02357). Vedi Resistenza all'evasione. - Advisory di pacchetti notoriamente vulnerabili e dannosi, sensibili alla versione. Controlla ogni dipendenza e ogni pacchetto avviato da MCP contro uno snapshot advisory incluso — un elenco curato manualmente di backdoor documentati in-the-wild, più advisory OSV HIGH/CRITICAL per una watchlist di pacchetti LLM/MCP/RAG, rigenerato da
scripts/sync-advisories.js. Funziona offline su ogni scansione, senza bisogno di flag. Una CVE scatta solo quando la tua versione pinnata è provabilmente all'interno dell'intervallo interessato; un pacchetto documentato come dannoso scatta anche su un intervallo ambiguo, perché installare un backdoor è irreversibile. - Local-first. Nulla lascia la tua macchina.
Come si confronta
SecureAI-Scan non sostituisce uno strumento SAST generico o uno scanner container/IaC — eseguilo insieme a uno di questi, non al posto di uno. È costruito appositamente per la superficie d'attacco LLM/MCP/RAG e enfatizza l'evidenza del dataflow rispetto a riscontri piatti basati su parole chiave.
| SecureAI-Scan | Semgrep (regole OSS) | Trivy | GitHub Advanced Security | |
|---|---|---|---|---|
| Injection di prompt (source→sink tracciato) | ✅ dataflow risolto tramite import | ⚠️ solo regole pattern, mantenute dalla community | ❌ | ⚠️ CodeQL può, ma nessun ruleset specifico per AI |
| Avvelenamento strumenti MCP / rischio configurazione | ✅ MCP007–010, scanner configurazione | ❌ | ❌ | ❌ |
Avvelenamento Agent Skill (SKILL.md) | ✅ resistente all'evasione, consapevole dei bundle | ❌ | ❌ | ❌ |
| Misconfigurazione RAG / vector-store | ✅ VEC001–004 | ❌ | ❌ | ❌ |
| Advisory pacchetti AI notoriamente dannosi | ✅ DEP003, offline, sensibile alla versione | ❌ | ⚠️ feed CVE generico, non specifico per AI | ⚠️ Dependabot, feed CVE generico |
| SAST generico (SQLi, XSS, path traversal) | ❌ fuori scope per design | ✅ | ❌ | ✅ |
| Scansione container / IaC | ❌ | ❌ | ✅ | ⚠️ tramite CodeQL/Actions |
| Livelli di evidenza (proven/likely/heuristic) | ✅ | ❌ i riscontri sono piatti | ❌ | ⚠️ CodeQL ne ha alcuni, non ottimizzati per AI |
| Output SARIF (GitHub code scanning) | ✅ | ✅ | ✅ | nativo |
| Funziona offline, senza account | ✅ | ✅ (regole OSS) | ✅ | ❌ richiede GitHub |
Se usi già Semgrep o GHAS, tienili — aggiungi SecureAI-Scan per la superficie di rischio che non modellano affatto.
Preferisci fare domande prima? Prova il SecureAI-Scan AI Security Advisor su ChatGPT gratuito.
Stai per eseguire un server MCP trovato su GitHub o Twitter? Incolla prima la sua descrizione dello strumento in MCP X-Ray — lo controlla per Unicode nascosto, istruzioni iniettate e pacchetti notoriamente dannosi nel tuo browser, senza installazione.
Vedilo in azione
secureai-scan scan . end to end, output reale su un file reale (piccolo, deliberatamente vulnerabile) — sorgente:
Forme di attacco che lo scanner traccia end to end:
| Dataflow avvelenamento strumenti MCP | Dataflow injection contesto RAG |
|---|---|
![]() | ![]() |
Comandi
Quello che ti serve il 95% delle volte:```bash secureai-scan scan .
Tutto il resto è disponibile quando ti serve. `secureai-scan scan . --help` mostra tutto questo nel terminale, raggruppato allo stesso modo:
**Uso quotidiano**
| Flag | Cosa fa |
|------|---------------|
| *(nessuno)* | risultati `proven` + `likely` — l'impostazione predefinita, nessun flag necessario |
| `--paranoid` | include anche i risultati di livello `heuristic` |
| `-s, --severity <level>` | mostra solo i risultati a/oltre `low`\|`medium`\|`high`\|`critical` |
| `--output <file>` | scrive un report completo — `.sarif` (code scanning di GitHub), `.json`, `.md` o `.html` |
**Ambito delle regole da eseguire**
| Flag | Cosa fa |
|------|---------------|
| `-r, --rules <list>` | esegue solo questi ID di regola, es. `AI001,MCP007` |
| `--only-ai` / `--only-mcp` / `--only-vec` / `--only-skl` | esegue solo una categoria di regole |
| `--check-dependencies` | controlla anche `package.json`/`requirements.txt` contro il registro npm/PyPI per refusi e pacchetti allucinati (`DEP001`/`DEP002`). Si attiva automaticamente se selezioni quelle regole direttamente con `-r` — non devi mai ricordarti di passarli entrambi. Non necessario per `DEP003` (pacchetti noti come dannosi), che viene sempre eseguito offline |
**CI / workflow**
| Flag | Cosa fa |
|------|---------------|
| `--fail-on <severity>` | esce con `1` se esistono risultati a/oltre questa gravità |
| `--baseline <file>` | tiene traccia solo dei problemi nuovi/modificati rispetto a una baseline salvata |
| `--policy <file>` | carica soglie, percorsi saltati e regole bloccate da un `.secureai-policy.json` (rilevato automaticamente se presente — `secureai-scan init` ne crea uno) |
**Avanzato**
| Flag | Cosa fa |
|------|---------------|
| `--min-confidence <0-1>` | più granulare di `--paranoid`: nasconde i risultati al di sotto di un punteggio di confidenza esatto (`0.9` proven / `0.65` likely / `0.35` heuristic) |
| `--limit <n>` | numero massimo di gruppi di regole mostrati nel terminale (predefinito `10`) — i dettagli completi vanno sempre a `--output` |
| `--debug` | stampa ogni file scansionato e quali regole sono state eseguite |
**Scansiona prima di installare — nessun clone, nessuna configurazione:**```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 recuperano il target e lo scansionano, quindi eliminano la copia scaricata (--keep per ispezionarla invece). Nulla di ciò che viene recuperato viene mai eseguito: un target npm viene scaricato con npm pack — solo il tarball, nessun install, nessuno script di ciclo di vita — e un target git è un semplice git clone --depth 1. Questo è il momento che conta di più: prima che una skill finisca in ~/.claude/skills/ o un server finisca in .mcp.json, non dopo.
Altri comandi:```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
Sopprimi un risultato revisionato nel codice:```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
Findings appear as inline annotations on the PR and in the repository's Security tab. (`secureai-scan init` generates an equivalent workflow using the CLI directly.)
Scanning clean? Add the badge to your own README:```md
[](https://github.com/akanthed/SecureAI-Scan)
Pre-commit hook
Preferisci individuare i risultati prima che vengano pubblicati? Aggiungi questo repository come sorgente di hook pre-commit invece di, o insieme a, la GitHub Action:```yaml repos:
- repo: https://github.com/akanthed/SecureAI-Scan
rev: v0.10.0
hooks:
- id: secureai-scan
The hook scans the whole project on every commit (not just changed files — a dataflow trace into file A can depend on file B, which a partial scan would miss) and blocks the commit on `high`+ severity findings by default. Override the threshold in your own config:```yaml
- id: secureai-scan
args: ["--fail-on", "critical"]
Regole
42 regole, mappate sulla OWASP Top 10 ufficiale per applicazioni LLM (2026) — più, dove applicabile, la OWASP Top 10 per applicazioni agentiche (2026, ASI), la OWASP MCP Top 10 (2025) e un articolo dell'EU AI Act. Vedi la copertura e i limiti 2026 versionati; threat-model genera la matrice per ogni progetto scansionato.
| Regola | Cosa dimostra | OWASP |
|---|---|---|
| AI001 | L'input utente fluisce in un prompt di sistema/sviluppatore (sorgente tracciata → sink, anche attraverso i confini di funzione/file) | LLM01 |
| AI002 | Contenuto del prompt o segreti scritti nei log (in file che usano un SDK LLM) | LLM02 |
| AI003 | Chiamata LLM in un gestore di richieste senza controllo di autenticazione prima | LLM06 |
| AI004 | Intero oggetto utente/sessione serializzato in un prompt (la selezione di campi non viene segnalata) | LLM02 |
| AI005 | L'output LLM raggiunge sink eval/exec/SQL/HTML | LLM10 |
| AI006 | Strumenti ad alto impatto (delete, pay, deploy, …) esposti senza un gate di approvazione | LLM03 |
| AI007 | Contenuto RAG recuperato interpolato in prompt privilegiati | LLM01 |
| AI008 | Segreti incorporati nel testo del prompt di sistema | LLM08 |
| AI009 | Input utente illimitato / limiti di token mancanti | LLM06 |
| AI010 | Contenuto esterno recuperato che fluisce nei prompt | LLM01 |
| AI011 | Output dell'agente elevato a ruolo di sistema nelle chiamate a valle | LLM03 |
| AI012 | Output LLM analizzato senza validazione dello schema | LLM10 |
| MCP001 | I metadati degli strumenti MCP raggiungono il prompt di sistema senza validazione | LLM01 |
| MCP002 | URL del server MCP costruito da input utente | LLM04 |
| MCP003 | Risultati degli strumenti MCP elevati a ruolo di sistema | LLM10 |
| MCP004 | Server MCP avviato come pacchetto npx -y non bloccato | LLM04 |
| MCP005 | Segreto inline in una configurazione MCP committata | LLM02 |
| MCP006 | Server MCP su HTTP in chiaro | LLM04 |
| MCP007 | Unicode invisibile/bidi nascosto in nomi o descrizioni di strumenti MCP | LLM01 · MCP03 |
| MCP008 | Frasi di iniezione dirette all'agente nelle descrizioni degli strumenti MCP | LLM01 · MCP03 |
| MCP009 | Una descrizione di strumento che indirizza le chiamate a uno strumento diverso (shadowing) | LLM01 · MCP03 |
| MCP010 | Comando/argomenti del server MCP stdio costruiti da input utente (RCE) | LLM04 · MCP05 |
| SKL001 | Unicode invisibile/bidi ovunque in un bundle di Agent Skill | LLM01 |
| SKL002 | Frasi di iniezione dirette all'agente nella descrizione o nel corpo di una skill (rilevate tramite offuscamento) | LLM01 |
| SKL003 | Il contenuto di una skill determina quando/come viene usata una skill diversa (shadowing) | LLM01 |
| SKL004 | Payload in più fasi/autoestraente: blob opaco + istruzioni per decodificarlo ed eseguirlo | LLM04 · MCP04 |
| SKL005 | Lettura di credenziali + egress esterno hardcoded in un file di supporto del bundle | LLM02 · MCP04 |
| SKL006 | Esecuzione di comandi al caricamento tramite la sintassi di iniezione dinamica del contesto di Claude Code (!`cmd`/```!), prima di qualsiasi gate di autorizzazione degli strumenti | LLM04 · MCP05 |
| SKL007 | Concessione Bash senza ambito nel frontmatter allowed-tools di una skill | LLM03 |
| SKL008 | La skill recupera istruzioni da un URL esterno e dirige l'agente a seguirle ("Circus of Skills") | LLM04 |
| SKL009 | La skill persiste una backdoor scrivendo in un altro file di contesto (MEMORY.md/SOUL.md/AGENTS.md/CLAUDE.md) | LLM05 |
| SKL010 | Tag di deserializzazione YAML/JSON non sicuro nel frontmatter di una skill o in un file di configurazione incluso | LLM04 |
| VEC001 | Ricerca vettoriale senza filtro tenant/utente | LLM09 |
| VEC002 | Limite di ricerca illimitato o controllato dall'utente | LLM06 |
| VEC003 | Contenuto utente inserito in uno store vettoriale condiviso | LLM05 |
| VEC004 | Inserimento senza tagging tenant/namespace | LLM09 |
| DEP001 | Nome della dipendenza non trovato nel registro (opt-in --check-dependencies) | LLM04 |
| DEP002 | Nome della dipendenza a una modifica di distanza da un pacchetto popolare (opt-in) | LLM04 |
| DEP003 | Dipendenza con una release dannosa documentata o una CVE critica — verificata offline a ogni scansione, consapevole dell'intervallo di versioni (postmark-mcp, mcp-remote CVE-2025-6514, …) | LLM04 · MCP04 |
| LLC001 | Segreto hardcoded in un config.yaml del proxy LiteLLM | LLM02 |
| LLC002 | api_base del proxy LiteLLM raggiungibile su HTTP in chiaro | LLM04 |
| LLC003 | La configurazione del proxy LiteLLM non ha una sezione guardrails: (euristica, solo --paranoid) | LLM03 |
secureai-scan explain <RULE_ID> fornisce il walkthrough dello sfruttamento e un esempio di codice prima/dopo per qualsiasi regola.
Architettura
Tre superfici di scansione indipendenti alimentano un'unica lista di risultati unita e deduplicata:``` ┌─────────────────────┐ *.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)
Ogni regola AST chiama una funzione "LLM call" solo se questa risolve attraverso import reali a un SDK noto — mai solo tramite corrispondenza del nome. Vedi [`docs/Architecture.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Architecture.md) per la ripartizione completa di ogni superficie, e [`docs/DetectionEngine.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/DetectionEngine.md) per come funziona il contratto dei livelli di evidenza.
## Server MCP (usalo da Claude)
Il pacchetto include un server MCP che espone `scan_repository`, `explain_rule`, `generate_bom` e `scan_untrusted_target` (recupera e analizza una skill o un server MCP prima che Claude ne consigli l'installazione — stesso comportamento di fetch-senza-esecuzione dei comandi CLI `skill`/`mcp`):```json
{
"mcpServers": {
"secureai-scan": {
"command": "node",
"args": ["/path/to/secureai-scan/mcp-server/index.js"]
}
}
}
Claude Skill
Per gli utenti di Claude Code / Claude.ai, skills/secureai-scan/SKILL.md insegna a Claude quando eseguire una scansione (durante la revisione di codice AI/LLM, o quando si verifica un server MCP/Skill di un Agente prima di installarlo) e come interpretare i risultati — nessun processo separato da eseguire, a differenza del server MCP sopra. Copia la directory skills/secureai-scan/ nella tua .claude/skills/ per usarla.
Resistenza all'evasione
A luglio 2026, due ricerche pubbliche hanno dimostrato che gli scanner di Skill per Agenti vengono aggirati banalmente.
Cloak and Detonate (Ji, Xu, Li, Gao, Wei, Wang & Cheung — HKUST, arXiv:2607.02357) ha condotto uno studio avversariale contro nove scanner di skill pubblicati utilizzando 1.613 skill dannose reali. Due famiglie di attacco, entrambe in grado di preservare esattamente il payload:
- Reify — riscrive i byte che una regola intercetta, mentre l'agente ricompone comunque l'originale a runtime. >80% di bypass sulla maggior parte degli scanner statici.
- Disperse / impacchettamento SFS — sposta il payload fuori dallo scope della regola: in una directory che lo scanner salta, dietro un'estensione rinominata, oppure archiviato e codificato in
.git/, con un innocuoSKILL.mddi copertura che lo ricostruisce al primo avvio. ≥90% di bypass su ogni scanner testato, ≥96% sulla maggior parte. Il tasso di rilevamento di uno scanner ibrido è crollato da ~99% a ~10%.
Separatamente, Gecko Security (VentureBeat) ha distribuito un payload in un file *.test.ts. Ogni scanner pubblico lo ha saltato — ma Jest e Vitest rilevano ed eseguono automaticamente quei file al successivo npm test.
SecureAI-Scan gestisce ogni tecnica pubblicata. Ogni riga sotto ha un test corrispondente in test/deobfuscate.test.js o una fixture in test-fixtures/vulnerable/skills/:
| Tecnica (nome del paper) | Esempio | Come viene gestita |
|---|---|---|
| Inserimento di larghezza zero | Ignore all previous… | Il testo viene confrontato con varianti prive di caratteri invisibili |
| Glifi confondibili | сurl (с cirillica) | Piegatura degli omoglifi in ASCII prima del confronto |
| Inversione di maiuscole/minuscole | EvAl | Tutti i pattern di contenuto sono case-insensitive |
| Splicing di variabili | 'cu' + 'rl' | Le stringhe letterali adiacenti vengono unite prima del confronto |
| Suddivisione per riga | ev⏎al(x) | Le interruzioni di riga intra-parola vengono unite come variante aggiuntiva |
| Estrazione per file | payload in un helper incluso | Viene scansionato l'intero bundle, non solo SKILL.md |
| Spostamento di directory | scripts/x.sh → build/x.sh | Nessuna skip-list di directory all'interno di un bundle |
| Rinomina dell'estensione | x.sh → x.txt | I file vengono scansionati per contenuto, non per estensione |
| Padding di dimensione | riempimento oltre il limite dello scanner | I file sovradimensionati vengono letti dall'inizio, mai saltati |
| Impacchettamento SFS | payload in .git/skillpack.dat | Qualsiasi file non-git sotto .git/ di un bundle è proven (SKL004) |
| Staging di file di test | payload in *.test.ts | La scansione dei bundle deliberatamente non declassa i percorsi di test (SKL005) |
Questo non indebolisce il contratto di precisione
La deoffuscazione è di solito un rischio per la precisione — più corrispondenze, più rumore. Qui la logica è invertita: una corrispondenza che appare solo dopo la deoffuscazione viene promossa a proven, non declassata. La documentazione ordinaria non contiene un joiner a larghezza zero dentro "ignore previous instructions", né una с cirillica dentro curl. L'occultamento è di per sé una prova affermativa dell'intento.
Il confronto avviene rispetto all'insieme delle corrispondenze grezze, non semplicemente "il testo grezzo corrispondeva o no" — altrimenti un attaccante potrebbe mascherare il segnale lasciando in chiaro una frase innocua.
Le due nuove regole per i bundle scattano solo su congiunzioni, mai su una singola parola chiave:
- SKL004 richiede un blob opaco e una direttiva di decompressione che faccia riferimento a quel blob per nome — un README che menziona
tar -xaccanto a un asset binario non correlato non basta. Gli archivi reali (gzip/zip/png/pdf/wasm — verificati tramite magic bytes, non estensione) non sono mai "opachi" in primo luogo, comunque vengano compressi. - SKL005 richiede un segnale concreto di credenziali — un percorso (
~/.aws/credentials, non la parola "token") o un'enumerazione massiva di variabili d'ambiente (os.environ.items(), nonos.environ["API_KEY"]) — e un egresso verso un host non locale hardcoded, entro 25 righe l'uno dall'altro nello stesso file, oppure un fetch remoto che viene eseguito dopo essere stato riassegnato tramite una o più rinomine. Un helper di pubblicazione che legge~/.npmrcin una funzione e chiama un registry quaranta righe dopo resta pulito, e la lettura di una singola variabile d'ambiente nominata per una chiamata API non viene mai segnalata — entrambe le forme sono fissate come fixture sicure.
Validato su due corpora del mondo reale, non solo su fixture scritte da noi: 0 rilevamenti su tutti i 18 bundle di skill reali in anthropics/skills e tutti i 14 in vercel/ai, e 6/6 corretto su cisco-ai-defense/skill-scanner's corpus di valutazione etichettato (20 skill, ciascuna con un verdetto _expected.json) con zero falsi positivi su qualsiasi cosa etichettata come sicura. Vedi Testing & benchmarking.
Cosa non è questo
Limitazione onesta: la conclusione del paper è che la detonazione a runtime batte l'analisi statica, ed è corretto. Un avversario adattivo che conosce queste regole può scrivere una trasformazione che non coprono. Ciò che cambia qui è il costo dell'evasione — le tecniche pubblicate e attualmente in circolazione non funzionano più, e l'offuscamento necessario per sconfiggerle ora di per sé aumenta la gravità del rilevamento. La scansione statica è un filtro, non un confine di sicurezza. Tratta una skill non fidata come codice non fidato, indipendentemente da ciò che dice qualsiasi scanner.
Garanzia di fiducia e rilascio
- La CI viene eseguita su Linux, Windows e macOS su tutte le versioni Node supportate.
- CodeQL, audit delle dipendenze di produzione, OpenSSF Scorecard, Dependabot e l'auto-scansione bloccante di questo stesso scanner forniscono controlli indipendenti.
- Ogni pubblicazione npm manuale invoca test, soglie di copertura, il gate di regressione sul repository reale revisionato e l'ispezione del tarball tramite
prepublishOnly. - GitHub Actions non riceve alcuna password o token npm e non può pubblicare il pacchetto.
- Garanzia di rilascio, governance a manutentore singolo, segnalazione di sicurezza e prove di benchmark versionate sono pubbliche.
Questo è un progetto a manutentore singolo senza SLA contrattuale o certificazione indipendente. I controlli sopra riducono il rischio; non trasformano una scansione statica in una prova di sicurezza.
Il contratto di precisione
I falsi positivi uccidono gli scanner. Il motore di regole di SecureAI-Scan segue tre regole ferree:
- I sink vengono risolti tramite gli import. Se un identificatore risolve a un modulo che non è un SDK LLM, non è definitivamente una chiamata LLM — qualunque sia il suo nome.
- Le prove sono etichettate, mai mescolate. Un dataflow tracciato e una corrispondenza per prossimità di parole non sono la stessa cosa, quindi non condividono mai un livello.
- Il corpus sicuro blocca ogni rilascio.
test-fixtures/safe/contiene i pattern che in passato causavano falsi positivi (payload PII redatti, client Google Maps, chiavi API da variabili d'ambiente accanto a client LLM, logging ordinario delle risposte, campi di metadati OAuth,chunksdi risposte in streaming, testo di prompt di narrativa/finzione). Qualsiasi rilevamento lì fa fallire la suite.
Testing & benchmarking
Tre livelli, perché uno solo non basta per fidarsi delle affermazioni di uno scanner — precisione e recall sono modalità di errore diverse, ed entrambe vengono verificate.
1. Corpus di fixture — precisione + recall, eseguito a ogni 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) vengono scansionati insieme: ogni fixture vulnerabile deve attivare la regola prevista con evidenza `proven`/`likely` (recall), ogni fixture sicura deve produrre **zero** risultati `proven`/`likely` (precision). Veloce e deterministico — ma dimostra solo che lo scanner si comporta correttamente su codice scritto appositamente per testarlo.
**2. Benchmark di regressione sul mondo reale — contro repository pubblici che non abbiamo scritto noi.**```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 set curato e diversificato di repository pubbliche reali (OpenAI/Anthropic/Vercel AI SDK, i server MCP ufficiali e TypeScript SDK, LlamaIndex, oltre a anthropics/skills e cisco-ai-defense/skill-scanner per la copertura dei bundle di skill — spaziando tra TS e Python, codice di esempio consumer di SDK e sorgente autore di SDK) e scansiona ciascuna con la CLI compilata.
Esce con codice non-zero su qualsiasi riscontro proven/likely non già presente in test/regression-baseline.json — un registro revisionato manualmente di riscontri già letti rispetto alla loro riga sorgente. Le impronte digitali sono repo|rule|file, non numeri di riga, quindi il normale churn a monte non produce rumore. Una nuova impronta digitale è un'affermazione che lo scanner deve giustificare: se non è un problema reale è un bug della regola, corretto alla causa principale e bloccato come nuova fixture test-fixtures/safe/. Mettere in baseline un riscontro che non hai letto vanifica l'intero meccanismo.
La copertura dei bundle di skill ha una propria riga perché il corpus evals/ di cisco-ai-defense/skill-scanner è etichettato — ciascuna delle sue 20 fixture include un verdetto _expected.json e si trova sotto una directory letteralmente chiamata malicious/ o safe/, quindi funge anche da controllo di recall, non solo di precisione: 6/6 fixture malicious in scope scattano, 0 riscontri su qualsiasi cosa etichettata safe, e 0 riscontri su tutti i 18 bundle reali in anthropics/skills e tutti i 14 in vercel/ai. (Le restanti categorie Cisco — SQL injection, path traversal, resource exhaustion, eval() generico di un argomento di funzione, un payload deliberatamente suddiviso su quattro file — sono fuori dallo scope documentato LLM/MCP/RAG o oltre l'analisi di congiunzione nello stesso file; vedi la voce del changelog 0.6.0 per il ragionamento specifico su ciascuna.)
Prima/dopo storico dell'esecuzione che ha guidato le correzioni di precisione originali (riscontri al livello di evidenza predefinito, senza --paranoid):
| Repo | Prima | Dopo | Cosa non andava |
|---|---|---|---|
| vercel/ai | 773 | 1 | examples/, tests/ di primo livello e directory in stile ecosystem-tests/ con trattino non erano riconosciute come percorsi a fiducia inferiore; chunks (una variabile comune di risposta in streaming) era trattata come evidenza RAG inequivocabile |
| openai/openai-node | 47 | 0 | Stessa lacuna nel rilevamento dei percorsi, applicata a examples//ecosystem-tests/ dello SDK stesso |
| anthropics/anthropic-sdk-typescript | 2 | 0 | Stessa lacuna nel rilevamento dei percorsi su una directory tests/ di primo livello |
| modelcontextprotocol/typescript-sdk | 3 | 0 | Campi di metadati OAuth in stile token_endpoint/tokenType segnalati come segreti trapelati |
| run-llama/llama_index | 18 | 15 | Un controllo Python segnalava qualsiasi campo description= contenente "system prompt" come avvelenamento di tool MCP proven, indipendentemente dal contesto. I restanti 15 sono hit VEC001 sulle definizioni generiche di retriever della libreria stessa — si scansiona il sorgente di uno SDK di vector-DB, non codice applicativo, quindi un filtro non può esistere da verificare; un limite onesto e intrinseco, non un bug |
Esecuzione corrente (2026-08-06) — l'evidenza versionata è registrata in docs/benchmarks/v0.9.0.json:
| Repo | Riscontri | Regole | Stato |
|---|---|---|---|
| openai-node, anthropic-sdk-typescript, anthropic-sdk-python, modelcontextprotocol/typescript-sdk, modelcontextprotocol/servers | 0 | — | pulito |
| anthropics/skills (18 bundle di skill reali) | 0 | — | pulito — controllo di precisione puro per SKL001–005 |
| vercel/ai (5.691 file) | 0 | — | era 40 (AI001, AI003, AI005, AI010, MCP002) prima del triage — ognuno revisionato manualmente rispetto al sorgente e confermato falso positivo, ricondotto a 3 bug indipendenti alla causa principale (vedi sotto), corretto e riconfermato pulito con una nuova scansione completa |
| run-llama/llama_index | 46 | VEC001 | limite intrinseco, non un bug — le definizioni generiche di retriever della libreria stessa, dove nessun filtro tenant può esistere da trovare |
| cisco-ai-defense/skill-scanner | 7 | SKL001, SKL002, SKL005 | tutti su fixture etichettate malicious/ — 6/6 in scope, 0 su qualsiasi cosa etichettata safe/ |
Il triage di vercel/ai ha trovato tre bug reali, con causa principale individuata — nessuno specifico delle regole skill v0.6.0, tutti nella logica condivisa usata da molte regole:
resolveLlmSinktrattava qualsiasi chiamata risolta a un modulo SDK LLM come un'invocazione di modello, indipendentemente dal nome del metodo — segnalandoisToolUIPart(un type guard che il pacchettoaiesporta proprio accanto agenerateText) come chiamata LLM. Da solo, questo ha causato 3 dei 5 gruppi di riscontri (AI001, AI003, AI010).DANGEROUS_CALLEESin AI005 include"query"per sink in stile SQL injection, ma"query"è anche un verbo legittimo di invocazione LLM/agente —claudeSdk.query({ prompt, options }), la chiamata di modello del Claude Agent SDK stesso, veniva segnalata come "output LLM passato a un sink pericoloso" solo per il nome di metodo condiviso.REQUEST_SOURCES(duplicato identicamente in MCP002, MCP010, VEC003) corrispondeva a un semplice"params."— qualsiasi parametro di funzione convenzionalmente chiamatoparams, non necessariamente dati di richiesta HTTP. Un validatore di schema URL (assertOpenLinkParams(params: unknown)) veniva segnalato come "URL del server MCP da input utente."
Tutti e tre corretti alla causa principale (non al sito di chiamata specifico) e bloccati come fixture permanenti sotto test-fixtures/. Dettagli completi in CHANGELOG.md.
3. Validazione vulnerabile-vs-patched — dimostra il recall, non solo la precisione.
I due livelli precedenti verificano solo che lo scanner resti silenzioso su codice sicuro. I controlli advisory di DEP003 sono validati nell'altro modo: blocca un pacchetto a una versione documentata come vulnerabile e conferma che venga segnalato, poi bloccalo alla versione patched e conferma che non lo sia.```bash
node --test test/dependency-guard.test.js
covers: `[email protected]` (CVE-2025-6514, vulnerabile) segnalato / `[email protected]` (corretto) pulito; `[email protected]` (prima della backdoor) pulito / `[email protected]` (dopo — non esiste alcuna patch legittima per un pacchetto dannoso) ancora segnalato; `llama-cpp-python==0.2.71` (CVE-2024-34359, dal set generato da OSV) segnalato / `==0.2.72` (corretto) pulito, anche con la normalizzazione dei nomi PyPI (`llama_cpp_python`); e specificatori non pinnati tipo `langchain>=0.1.0` che producono **zero** risultati nel report predefinito. Costruire questo test ha colto una lacuna reale: `DEP003` prima confrontava gli advisory solo per nome del pacchetto, senza mai confrontare effettivamente la versione dichiarata con l'intervallo interessato dell'advisory — corretto in [`src/scanner/semver.ts`](https://github.com/akanthed/secureai-scan/blob/main/src/scanner/semver.ts).
L'ambiguità viene risolta in modo diverso per tipo di advisory, deliberatamente. Un pacchetto **dannoso** scatta anche quando la versione dichiarata non può essere risolta — installare una backdoor è irreversibile, quindi fallisce verso la segnalazione. Una **CVE** scatta a `proven` solo quando la versione dichiarata è un pin esatto dimostrabilmente all'interno dell'intervallo interessato; le versioni non pinnate ma potenzialmente interessate scendono a `heuristic` (solo con `--paranoid`). Applicare la regola del tipo dannoso a uno snapshot CVE di 162 voci metterebbe un risultato critico su ogni repository che dichiara `langchain>=0.1.0` — rumore inutilizzabile su larga scala.
## Roadmap
Vedi [`ROADMAP.md`](https://github.com/akanthed/secureai-scan/blob/main/ROADMAP.md) per ciò che è stato rilasciato e ciò che è pianificato. Entrambi i motori linguistici sono basati su AST: ts-morph per TypeScript/JavaScript e Tree-sitter per Python. Import, chiamate, assegnazioni, decoratori, scope, argomenti con parola chiave, campi di dizionario e stringhe Python sono nodi di sintassi; il codice target non viene mai importato o eseguito, e non è richiesto alcun interprete Python. Il gap Python rimanente è la profondità di taint limitata tra funzioni/file, non il parsing. Le prestazioni di scansione e i limiti noti sono documentati in [`docs/Performance.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Performance.md).
## Contribuire
I contributi sono benvenuti — vedi [`CONTRIBUTING.md`](https://github.com/akanthed/secureai-scan/blob/main/CONTRIBUTING.md) per il flusso di lavoro, 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) per come aggiungere una regola di rilevamento che soddisfi lo standard di precisione sopra. Ogni nuova regola richiede un fixture sia in [`test-fixtures/vulnerable/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/vulnerable) che in [`test-fixtures/safe/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/safe), una voce in `src/scanner/catalog.ts`, e un caso in `test/corpus.test.js` — `npm test` impone tutti e tre.
## Licenza
MIT © Akshay Kanthed

