
SecureAI-Scan v0.4.0
SecureAI-Scan est un outil CLI qui analyse les bases de code TypeScript et JavaScript pour les problèmes de sécurité spécifiques aux applications basées sur l'IA — prompt injection, MCP tool abuse, RAG data poisoning, agent trust violations, et plus encore.
SecureAI-Scan
CLI hors ligne qui analyse TypeScript, JavaScript et Python pour détecter les risques LLM, MCP, Agent Skill et RAG — preuves de flux de données résolues par import, zéro faux positif par défaut, mappées sur l'OWASP LLM/ASI/MCP Top 10.
La plupart des scanners dans ce domaine font correspondre un mot-clé et l'appellent une découverte. SecureAI-Scan trace le chemin réel source → flux → puits à travers un code réel, résolu par import — et une analyse par défaut ne vous montre que ce qu'il peut prouver. Pas de compte, pas d'upload cloud, rien ne quitte votre machine.
Couvre l'officiel OWASP Top 10 pour les applications LLM 2026, le Top 10 pour les applications agentiques (2026), et le MCP Top 10 dès la semaine de lancement.
Commencez en 30 secondes```bash
npx --yes [email protected] scan .
Aucun compte, téléversement cloud, interpréteur Python ou configuration requis. Les bundles TypeScript, JavaScript, Python, configurations MCP et compétences d'agent sont détectés automatiquement.
**Candidat de version `0.9.0` mesuré :** 136/136 tests · 88,08 % de couverture des instructions · 12 676 fichiers sur 9 dépôts publics · 0 nouvelle empreinte de niveau par défaut par rapport à la base de référence examinée. [Preuves](https://github.com/akanthed/secureai-scan/blob/main/docs/benchmarks/v0.9.0.json) · [méthodologie et 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.
Est-ce fait pour vous ? SecureAI-Scan est délibérément ciblé sur les risques LLM, MCP et RAG/agents — injection de prompts, empoisonnement d'outils, gestion non sécurisée des sorties, contrôle d'accès aux magasins de vecteurs, empoisonnement des compétences d'agents. Ce n'est pas un scanner SAST généraliste ni un scanner de secrets, et il ne cherche pas à l'être ; un paquet connu comme malveillant sans charge utile de type LLM (par exemple, une adresse d'exfiltration codée en dur dans un appel d'API e-mail) est détecté par la liste d'avis hors ligne (DEP003), et non par une règle de motif. Si votre codebase communique avec un LLM, un serveur MCP, un magasin de vecteurs, ou embarque des Agent Skills, cet outil est fait pour vous.
Nouveau : analyse statique de configuration pour LiteLLM Proxy (config.yaml) — secrets codés en dur, points de terminaison de fournisseurs en texte clair, garde-fous manquants. Voir Règles (LLC001–LLC003).
Sommaire
- Pourquoi ce scanner est différent
- Comment il se compare
- Démarrage en 30 secondes
- Voyez-le en action
- Commandes
- GitHub Action
- Hook pre-commit
- Règles
- Architecture
- Serveur MCP (utilisez-le depuis Claude)
- Claude Skill
- Confiance et garantie de publication
- Le contrat de précision
- Tests et benchmarks
- Feuille de route
- Contribuer
Pourquoi ce scanner est différent
- Niveaux de preuve, pas de bruit. Chaque résultat est
proven(flux de données tracé ou fait de configuration analysé),likely(cible résolue, un saut heuristique), ouheuristic. Une analyse par défaut n'affiche que proven + likely. Les heuristiques sont activables via--paranoid. - Détection résolue par import. Un appel n'est un « appel LLM » que s'il se résout vers un import SDK réel (
openai,@anthropic-ai/sdk,ai,@google/genai, LangChain, Bedrock, …). Votre client Google Maps ne sera plus jamais signalé comme un LLM. - Contrôlé par la précision, et benchmarké sur de vrais dépôts. La suite de tests vérifie que chaque fixture vulnérable déclenche une alerte et que chaque fixture sûre reste propre — un faux positif sur le corpus sûr fait échouer la build. Au-delà,
npm run regressionanalyse de vrais dépôts publics (OpenAI/Anthropic/Vercel AI SDKs, serveurs MCP officiels, LlamaIndex) par rapport à une base de référence validée manuellement et échoue sur tout nouveau résultatproven/likely. Voir Tests et benchmarks pour les chiffres avant/après réels, ou Ce que nous avons trouvé en analysant de vrais dépôts pour l'histoire qui les sous-tend — un taux de détection de 6/6 sur un corpus de compétences malveillantes étiquetées, et pourquoi nous ne qualifions pas llama_index de « vulnérable » pour un résultat honnête au niveau de la bibliothèque. Compte-rendu de discussion → - SARIF pour le code scanning GitHub.
--output report.sarifplace les résultats en ligne sur les pull requests et dans l'onglet Security. - AI-BOM.
secureai-scan bom .construit un inventaire dérivé de la syntaxe des SDK, identifiants de modèles, magasins de vecteurs, frameworks d'agents et serveurs MCP, mappé aux besoins de documentation OWASP LLM Top 10 / EU AI Act. - Analyse de configuration MCP. Analyse
.mcp.json,claude_desktop_config.json,.cursor/mcp.json: serveursnpx -ynon épinglés, secrets en ligne, transports HTTP en texte clair. - Détection d'empoisonnement d'outils MCP. Détecte le motif derrière le rug-pull WhatsApp MCP et la backdoor postmark-mcp — Unicode invisible, phrases d'injection dirigées vers l'agent, et shadowing croisé d'outils dans les noms/descriptions d'outils, statiquement, avant même d'exécuter le serveur.
- Détection d'injection de commandes MCP. Signale les
command/argsde transport stdio MCP construits à partir de données de requête — le motif derrière la divulgation RCE MCP STDIO 2026. - Détection d'empoisonnement d'Agent Skills. Les mêmes vérifications d'Unicode invisible, de phrases d'injection et de shadowing appliquées aux fichiers
SKILL.md— les Agent Skills se chargent en bloc dans le contexte, donc une compétence empoisonnée est une description d'outil empoisonnée sous un autre nom. - Analyse de compétences résistante à l'évasion. Les bundles de compétences sont analysés comme des répertoires, pas seulement leur
SKILL.md, et chaque vérification de contenu s'exécute sur des variantes désobfusquées du texte. Cela cible les techniques publiées — homoglyphes, découpage en largeur nulle, charges utiles placées dans.git/oubuild/, exfiltration cachée dans un fichier*.test.ts— qui ont contourné >90 % des neuf scanners étudiés dans Cloak and Detonate (arXiv:2607.02357). Voir Résistance à l'évasion. - Avis de paquets connus comme vulnérables et malveillants, sensibles à la version. Vérifie chaque dépendance et chaque paquet lancé par MCP par rapport à un instantané d'avis intégré — une liste triée à la main de backdoors documentées en circulation, plus les avis OSV HIGH/CRITICAL pour une liste de surveillance de paquets LLM/MCP/RAG, régénérée par
scripts/sync-advisories.js. S'exécute hors ligne à chaque analyse, sans drapeau requis. Un CVE ne se déclenche que lorsque votre version épinglée est prouvablement dans la plage affectée ; un paquet documenté comme malveillant se déclenche même sur une plage ambiguë, car installer une backdoor est irrécupérable. - Local d'abord. Rien ne quitte votre machine.
Comment il se compare
SecureAI-Scan n'est pas un remplacement d'un outil SAST généraliste ni d'un scanner de conteneurs/IaC — exécutez-le en parallèle, pas à la place. Il est conçu sur mesure pour la surface d'attaque LLM/MCP/RAG et privilégie la preuve par flux de données plutôt que des résultats plats par mots-clés.
| SecureAI-Scan | Semgrep (règles OSS) | Trivy | GitHub Advanced Security | |
|---|---|---|---|---|
| Injection de prompts (source→cible tracée) | ✅ flux de données résolu par import | ⚠️ règles de motifs uniquement, maintenues par la communauté | ❌ | ⚠️ CodeQL le peut, mais aucun ensemble de règles spécifique à l'IA |
| Empoisonnement d'outils / risque de configuration MCP | ✅ MCP007–010, scanner de configuration | ❌ | ❌ | ❌ |
Empoisonnement d'Agent Skills (SKILL.md) | ✅ résistant à l'évasion, conscient des bundles | ❌ | ❌ | ❌ |
| Mauvaise configuration RAG / magasin de vecteurs | ✅ VEC001–004 | ❌ | ❌ | ❌ |
| Avis de paquets IA connus comme malveillants | ✅ DEP003, hors ligne, sensible à la version | ❌ | ⚠️ flux CVE général, non spécifique à l'IA | ⚠️ Dependabot, flux CVE général |
| SAST généraliste (SQLi, XSS, traversée de chemin) | ❌ hors périmètre par conception | ✅ | ❌ | ✅ |
| Analyse de conteneurs / IaC | ❌ | ❌ | ✅ | ⚠️ via CodeQL/Actions |
| Niveaux de preuve (proven/likely/heuristic) | ✅ | ❌ résultats plats | ❌ | ⚠️ CodeQL en a certains, non adaptés à l'IA |
| Sortie SARIF (code scanning GitHub) | ✅ | ✅ | ✅ | natif |
| Fonctionne hors ligne, sans compte | ✅ | ✅ (règles OSS) | ✅ | ❌ nécessite GitHub |
Si vous exécutez déjà Semgrep ou GHAS, gardez-les — ajoutez SecureAI-Scan pour la surface de risque qu'ils ne modélisent pas du tout.
Vous préférez poser des questions d'abord ? Essayez le SecureAI-Scan AI Security Advisor gratuit sur ChatGPT.
Sur le point d'exécuter un serveur MCP trouvé sur GitHub ou Twitter ? Collez d'abord sa description d'outil dans MCP X-Ray — il vérifie l'Unicode caché, les instructions injectées et les paquets connus comme malveillants dans votre navigateur, sans installation.
Voyez-le en action
secureai-scan scan . de bout en bout, sortie réelle contre un vrai fichier (petit, délibérément vulnérable) — source :
Formes d'attaque que le scanner trace de bout en bout :
| Flux de données d'empoisonnement d'outils MCP | Flux de données d'injection de contexte RAG |
|---|---|
![]() | ![]() |
Commandes
Celle dont vous avez besoin 95 % du temps :```bash secureai-scan scan .
Tout le reste est là quand vous en avez besoin. `secureai-scan scan . --help` affiche tout cela dans le terminal, regroupé de la même manière :
**Usage quotidien**
| Indicateur | Effet |
|------|---------------|
| *(aucun)* | résultats `proven` + `likely` — le comportement par défaut, aucun indicateur requis |
| `--paranoid` | inclut également les résultats de niveau `heuristic` |
| `-s, --severity <niveau>` | n'affiche que les résultats à/au-dessus de `low`\|`medium`\|`high`\|`critical` |
| `--output <fichier>` | écrit un rapport complet — `.sarif` (analyse de code GitHub), `.json`, `.md` ou `.html` |
**Portée des règles exécutées**
| Indicateur | Effet |
|------|---------------|
| `-r, --rules <liste>` | exécute uniquement ces identifiants de règles, p. ex. `AI001,MCP007` |
| `--only-ai` / `--only-mcp` / `--only-vec` / `--only-skl` | exécute uniquement une catégorie de règles |
| `--check-dependencies` | vérifie également `package.json`/`requirements.txt` par rapport au registre npm/PyPI pour détecter les fautes de frappe et les paquets hallucinés (`DEP001`/`DEP002`). Activé automatiquement si vous sélectionnez ces règles directement via `-r` — vous n'avez jamais besoin de penser à passer les deux. Non requis pour `DEP003` (paquets connus comme malveillants), qui s'exécute toujours hors ligne |
**CI / flux de travail**
| Indicateur | Effet |
|------|---------------|
| `--fail-on <sévérité>` | sort avec le code `1` si des résultats à/au-dessus de cette sévérité existent |
| `--baseline <fichier>` | ne suit que les problèmes nouveaux/modifiés par rapport à une référence enregistrée |
| `--policy <fichier>` | charge les seuils, les chemins ignorés et les règles bloquées depuis un `.secureai-policy.json` (détecté automatiquement s'il est présent — `secureai-scan init` en crée un) |
**Avancé**
| Indicateur | Effet |
|------|---------------|
| `--min-confidence <0-1>` | plus précis que `--paranoid` : masque les résultats en dessous d'un score de confiance exact (`0.9` proven / `0.65` likely / `0.35` heuristic) |
| `--limit <n>` | nombre maximal de groupes de règles affichés dans le terminal (par défaut `10`) — le détail complet va toujours dans `--output` |
| `--debug` | affiche chaque fichier analysé et les règles exécutées |
**Analysez avant d'installer — sans clone, sans configuration :**```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 et mcp récupèrent la cible et l'analysent, puis suppriment la copie téléchargée (--keep pour l'inspecter à la place). Rien de ce qui est récupéré n'est jamais exécuté : une cible npm est téléchargée avec npm pack — uniquement l'archive, pas d'install, pas de scripts de cycle de vie — et une cible git est un simple git clone --depth 1. C'est le moment le plus important : avant qu'une compétence n'atterrisse dans ~/.claude/skills/ ou qu'un serveur n'atterrisse dans .mcp.json, pas après.
Autres commandes :```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
Supprimer un résultat examiné dans le code :```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
Les résultats apparaissent sous forme d'annotations en ligne sur la PR et dans l'onglet Sécurité du dépôt. (`secureai-scan init` génère un workflow équivalent utilisant directement la CLI.)
Analyse propre ? Ajoutez le badge à votre propre README :```md
[](https://github.com/akanthed/SecureAI-Scan)
Pre-commit hook
Vous préférez détecter les résultats avant qu'ils ne soient poussés ? Ajoutez ce dépôt comme source de hook pre-commit au lieu de, ou en plus de, l'action GitHub :```yaml repos:
- repo: https://github.com/akanthed/SecureAI-Scan
rev: v0.10.0
hooks:
- id: secureai-scan
Le hook analyse l’ensemble du projet à chaque commit (pas seulement les fichiers modifiés — une trace de flux de données dans le fichier A peut dépendre du fichier B, qu’une analyse partielle manquerait) et bloque le commit sur les résultats de sévérité `high`+ par défaut. Remplacez le seuil dans votre propre configuration :```yaml
- id: secureai-scan
args: ["--fail-on", "critical"]
Règles
42 règles, mappées sur l'OWASP Top 10 officiel pour les applications LLM (2026) — plus, le cas échéant, l'OWASP Top 10 pour les applications agentiques (2026, ASI), l'OWASP MCP Top 10 (2025) et un article de l'AI Act européen. Voir la couverture et les limites de la version 2026 ; threat-model génère la matrice pour chaque projet analysé.
| Règle | Ce qu'elle prouve | OWASP |
|---|---|---|
| AI001 | Les entrées utilisateur circulent dans un prompt système/développeur (source tracée → puits, y compris à travers les frontières de fonctions/fichiers) | LLM01 |
| AI002 | Le contenu du prompt ou des secrets sont écrits dans les journaux (dans les fichiers qui utilisent un SDK LLM) | LLM02 |
| AI003 | Appel LLM dans un gestionnaire de requêtes sans vérification d'authentification avant | LLM06 |
| AI004 | Objet utilisateur/session entier sérialisé dans un prompt (la sélection de champs n'est pas signalée) | LLM02 |
| AI005 | La sortie LLM atteint des puits eval/exec/SQL/HTML | LLM10 |
| AI006 | Outils à fort impact (suppression, paiement, déploiement, …) exposés sans passerelle d'approbation | LLM03 |
| AI007 | Contenu RAG récupéré interpolé dans des prompts privilégiés | LLM01 |
| AI008 | Secrets intégrés dans le texte du prompt système | LLM08 |
| AI009 | Entrée utilisateur illimitée / limites de jetons manquantes | LLM06 |
| AI010 | Contenu externe récupéré circulant dans les prompts | LLM01 |
| AI011 | Sortie d'agent élevée au rôle système dans les appels en aval | LLM03 |
| AI012 | Sortie LLM analysée sans validation de schéma | LLM10 |
| MCP001 | Les métadonnées d'outil MCP atteignent le prompt système sans validation | LLM01 |
| MCP002 | URL de serveur MCP construite à partir d'entrées utilisateur | LLM04 |
| MCP003 | Résultats d'outil MCP élevés au rôle système | LLM10 |
| MCP004 | Serveur MCP lancé comme package npx -y non épinglé | LLM04 |
| MCP005 | Secret intégré dans une configuration MCP validée | LLM02 |
| MCP006 | Serveur MCP sur HTTP en clair | LLM04 |
| MCP007 | Unicode invisible/bidi caché dans les noms ou descriptions d'outils MCP | LLM01 · MCP03 |
| MCP008 | Phrases d'injection dirigées par l'agent dans les descriptions d'outils MCP | LLM01 · MCP03 |
| MCP009 | Description d'outil qui oriente les appels vers un autre outil (shadowing) | LLM01 · MCP03 |
| MCP010 | Commande/arguments de serveur MCP stdio construits à partir d'entrées utilisateur (RCE) | LLM04 · MCP05 |
| SKL001 | Unicode invisible/bidi n'importe où dans un bundle de compétences d'agent | LLM01 |
| SKL002 | Formulation d'injection dirigée par l'agent dans la description ou le corps d'une compétence (correspondance via obfuscation) | LLM01 |
| SKL003 | Le contenu d'une compétence oriente quand/comment une autre compétence est utilisée (shadowing) | LLM01 |
| SKL004 | Payload échelonné/auto-extractible : blob opaque + instructions pour le décoder et l'exécuter | LLM04 · MCP04 |
| SKL005 | Lecture d'identifiants + sortie externe codée en dur dans un fichier compagnon du bundle | LLM02 · MCP04 |
| SKL006 | Exécution de commande au moment du chargement via la syntaxe d'injection de contexte dynamique de Claude Code (!`cmd`/```!), avant toute passerelle de permission d'outil | LLM04 · MCP05 |
| SKL007 | Octroi Bash sans portée dans le frontmatter allowed-tools d'une compétence | LLM03 |
| SKL008 | La compétence récupère des instructions depuis une URL externe et dirige l'agent pour les suivre (« Circus of Skills ») | LLM04 |
| SKL009 | La compétence persiste une porte dérobée en écrivant dans un autre fichier de contexte (MEMORY.md/SOUL.md/AGENTS.md/CLAUDE.md) | LLM05 |
| SKL010 | Balise de désérialisation YAML/JSON non sécurisée dans le frontmatter d'une compétence ou un fichier de configuration groupé | LLM04 |
| VEC001 | Recherche vectorielle sans filtre de locataire/utilisateur | LLM09 |
| VEC002 | Limite de recherche illimitée ou contrôlée par l'utilisateur | LLM06 |
| VEC003 | Contenu utilisateur ingéré dans un magasin vectoriel partagé | LLM05 |
| VEC004 | Ingestion sans étiquetage de locataire/espace de noms | LLM09 |
| DEP001 | Nom de dépendance introuvable dans le registre (opt-in --check-dependencies) | LLM04 |
| DEP002 | Nom de dépendance à une modification près d'un package populaire (opt-in) | LLM04 |
| DEP003 | Dépendance avec une version malveillante documentée ou une CVE critique — vérifiée hors ligne à chaque analyse, sensible à la plage de versions (postmark-mcp, mcp-remote CVE-2025-6514, …) | LLM04 · MCP04 |
| LLC001 | Secret codé en dur dans un config.yaml de proxy LiteLLM | LLM02 |
| LLC002 | api_base de proxy LiteLLM accessible sur HTTP en clair | LLM04 |
| LLC003 | La configuration du proxy LiteLLM n'a pas de section guardrails: (heuristique, --paranoid uniquement) | LLM03 |
secureai-scan explain <RULE_ID> fournit la procédure pas à pas d'exploitation et un exemple de code avant/après pour chaque règle.
Architecture
Trois surfaces d'analyse indépendantes alimentent une liste de résultats fusionnée et dédupliquée :``` ┌─────────────────────┐ *.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)
Chaque règle AST ne qualifie une fonction d'« appel LLM » que si elle résout, via de véritables imports, vers un SDK connu — jamais par simple correspondance de nom. Voir [`docs/Architecture.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Architecture.md) pour la répartition complète de chaque surface, et [`docs/DetectionEngine.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/DetectionEngine.md) pour le fonctionnement du contrat de niveau de preuve.
## Serveur MCP (utilisez-le depuis Claude)
Le paquet fournit un serveur MCP exposant `scan_repository`, `explain_rule`, `generate_bom` et `scan_untrusted_target` (récupère et analyse une compétence ou un serveur MCP avant que Claude ne recommande son installation — même comportement de récupération sans exécution que les commandes CLI `skill`/`mcp`) :```json
{
"mcpServers": {
"secureai-scan": {
"command": "node",
"args": ["/path/to/secureai-scan/mcp-server/index.js"]
}
}
}
Compétence Claude
Pour les utilisateurs de Claude Code / Claude.ai, skills/secureai-scan/SKILL.md apprend à Claude quand lancer une analyse (lors de la revue de code IA/LLM, ou avant d'installer un serveur MCP / une compétence d'agent) et comment interpréter les résultats — aucun processus séparé à exécuter, contrairement au serveur MCP ci-dessus. Copiez le répertoire skills/secureai-scan/ dans votre .claude/skills/ pour l'utiliser.
Résistance à l'évasion
En juillet 2026, deux travaux de recherche publics ont montré que les analyseurs de compétences d'agent sont trivialement contournables.
Cloak and Detonate (Ji, Xu, Li, Gao, Wei, Wang & Cheung — HKUST, arXiv:2607.02357) a mené une étude contradictoire contre neuf analyseurs de compétences publiés, en utilisant 1 613 compétences malveillantes réelles. Deux familles d'attaques, qui préservent toutes deux la charge utile à l'identique :
- Reify — réécrit les octets qu'une règle détecte, tandis que l'agent réassemble toujours l'original à l'exécution. >80 % de contournement sur la plupart des analyseurs statiques.
- Disperse / empaquetage SFS — déplace la charge utile hors de portée de la règle : dans un répertoire ignoré par l'analyseur, derrière une extension renommée, ou archivée et encodée dans
.git/, avec unSKILL.mdde couverture bénin qui la reconstruit au premier lancement. ≥90 % de contournement sur chaque analyseur testé, ≥96 % sur la plupart. Le taux de détection d'un analyseur hybride est passé d'environ 99 % à environ 10 %.
Par ailleurs, Gecko Security (VentureBeat) a livré une charge utile dans un fichier *.test.ts. Chaque analyseur public l'a ignorée — mais Jest et Vitest découvrent et exécutent automatiquement ces fichiers lors du prochain npm test.
SecureAI-Scan traite chaque technique publiée. Chaque ligne ci-dessous possède un test correspondant dans test/deobfuscate.test.js ou un fixture dans test-fixtures/vulnerable/skills/ :
| Technique (nom de l'article) | Exemple | Comment elle est traitée |
|---|---|---|
| Insertion de caractères de largeur nulle | Ignore all previous… | Le texte est comparé aux variantes sans caractères invisibles |
| Glyphes confondables | сurl (с cyrillique) | Réduction des homoglyphes en ASCII avant la comparaison |
| Inversion de casse | EvAl | Tous les motifs de contenu sont insensibles à la casse |
| Éclatement de variables | 'cu' + 'rl' | Les littéraux de chaîne adjacents sont concaténés avant la comparaison |
| Découpage par ligne | ev⏎al(x) | Les sauts de ligne intra-mot sont joints comme variante supplémentaire |
| Extraction par fichier | charge utile dans un helper importé | L'ensemble du bundle est analysé, pas seulement SKILL.md |
| Relocalisation de répertoire | scripts/x.sh → build/x.sh | Aucune liste d'exclusion de répertoire dans un bundle |
| Renommage d'extension | x.sh → x.txt | Les fichiers sont analysés par contenu, pas par extension |
| Remplissage de taille | rembourrage au-delà de la limite de l'analyseur | Les fichiers surdimensionnés sont lus en tête, jamais ignorés |
| Empaquetage SFS | charge utile dans .git/skillpack.dat | Tout fichier non-git sous le .git/ d'un bundle est proven (SKL004) |
| Mise en scène de fichier de test | charge utile dans *.test.ts | L'analyse de bundle ne déclasse délibérément pas les chemins de test (SKL005) |
Cela n'affaiblit pas le contrat de précision
La désobfuscation est généralement un risque de précision — plus de correspondances, plus de bruit. Ici, la logique est inversée : une correspondance qui n'apparaît qu'après désobfuscation est promue en proven, pas déclassée. Une documentation ordinaire ne contient pas de joncteur de largeur nulle dans « ignore previous instructions », ni un с cyrillique dans curl. La dissimulation est en soi une preuve affirmative d'intention.
La comparaison se fait sur l'ensemble des correspondances brutes, pas seulement sur « le texte brut correspondait-il ou non » — sinon un attaquant pourrait masquer le signal en laissant une phrase anodine en clair.
Les deux nouvelles règles de bundle ne se déclenchent que sur des conjonctions, jamais sur un mot-clé seul :
- SKL004 nécessite un blob opaque et une directive de dépaquetage qui référence ce blob par son nom — un README mentionnant
tar -xà côté d'un actif binaire sans rapport ne suffit pas. Les archives réelles (gzip/zip/png/pdf/wasm — vérifiées par octets magiques, pas par extension) ne sont jamais « opaques » en premier lieu, quelle que soit leur compression. - SKL005 nécessite un signal d'identifiants concret — un chemin (
~/.aws/credentials, pas le mot « token ») ou une énumération massive de variables d'environnement (os.environ.items(), pasos.environ["API_KEY"]) — et une exfiltration vers un hôte non local codé en dur, à moins de 25 lignes l'un de l'autre dans le même fichier, ou une récupération distante exécutée après avoir été réassignée via un ou plusieurs renommages. Un helper de publication qui lit~/.npmrcdans une fonction et appelle un registre quarante lignes plus loin reste propre, et la lecture d'une variable d'environnement nommée pour un appel API n'est jamais signalée — les deux formes sont figées comme fixtures sûres.
Validé sur deux corpus du monde réel, pas seulement sur des fixtures que nous avons écrites nous-mêmes : 0 résultat sur les 18 bundles de compétences réels dans anthropics/skills et les 14 dans vercel/ai, et 6/6 corrects sur le corpus d'évaluation étiqueté de cisco-ai-defense/skill-scanner (20 compétences, chacune avec un verdict _expected.json) avec zéro faux positif sur tout ce qui est étiqueté sûr. Voir Tests et benchmarks.
Ce que cela n'est pas
Limite honnête : la conclusion de l'article est que la détonation à l'exécution bat l'analyse statique, et c'est exact. Un adversaire adaptatif qui connaît ces règles peut écrire une transformation qu'elles ne couvrent pas. Ce qui change ici, c'est le coût de l'évasion — les techniques publiées et actuellement en circulation ne fonctionnent plus, et l'obfuscation nécessaire pour les vaincre élève désormais elle-même la gravité du résultat. L'analyse statique est un filtre, pas une frontière de sécurité. Traitez une compétence non fiable comme un code non fiable, quel que soit ce que dit un analyseur.
Garantie de confiance et de publication
- La CI s'exécute sur Linux, Windows et macOS sur les versions de Node prises en charge.
- CodeQL, l'audit des dépendances de production, OpenSSF Scorecard, Dependabot et l'auto-analyse bloquante de cet analyseur fournissent des contrôles indépendants.
- Chaque publication npm manuelle invoque les tests, les seuils de couverture, la porte de régression sur le référentiel réel examiné et l'inspection du tarball via
prepublishOnly. - GitHub Actions ne reçoit aucun mot de passe ni jeton npm et ne peut pas publier le paquet.
- Garantie de publication, gouvernance à mainteneur unique, signalement de sécurité et preuves de benchmark versionnées sont publics.
C'est un projet à mainteneur unique, sans SLA contractuel ni certification indépendante. Les contrôles ci-dessus réduisent le risque ; ils ne transforment pas une analyse statique en preuve de sécurité.
Le contrat de précision
Les faux positifs tuent les analyseurs. Le moteur de règles de SecureAI-Scan suit trois règles strictes :
- Les sinks sont résolus via les imports. Si un identifiant se résout vers un module qui n'est pas un SDK LLM, ce n'est définitivement pas un appel LLM — quel que soit son nom.
- Les preuves sont étiquetées, jamais mélangées. Un flux de données tracé et une correspondance par proximité de mots ne sont pas la même chose, ils ne partagent donc jamais un niveau.
- Le corpus sûr verrouille chaque publication.
test-fixtures/safe/contient les motifs qui causaient auparavant des faux positifs (charges utiles PII expurgées, clients Google Maps, clés API par variables d'environnement à côté de clients LLM, journalisation ordinaire des réponses, champs de métadonnées OAuth,chunksde réponses en streaming, texte de prompt de fiction/narration). Tout résultat là-bas fait échouer la suite.
Tests et benchmarks
Trois couches, car une seule ne suffit pas pour faire confiance aux affirmations d'un analyseur — la précision et le rappel sont des modes de défaillance différents, et les deux sont vérifiés.
1. Corpus de fixtures — précision + rappel, exécuté à chaque build.```bash npm test
[`test-fixtures/vulnerable/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/vulnerable) et [`test-fixtures/safe/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/safe) sont analysés ensemble : chaque fixture vulnérable doit déclencher sa règle attendue avec une preuve `proven`/`likely` (rappel), chaque fixture sûre doit produire **zéro** résultat `proven`/`likely` (précision). Rapide et déterministe — mais cela prouve uniquement que le scanner se comporte correctement sur du code écrit spécifiquement pour le tester.
**2. Benchmark de régression sur le monde réel — contre des dépôts publics que nous n'avons pas écrits.**```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 clone un ensemble organisé et diversifié de dépôts publics réels (OpenAI/Anthropic/Vercel AI SDKs, les serveurs MCP officiels et le SDK TypeScript, LlamaIndex, plus anthropics/skills et cisco-ai-defense/skill-scanner pour la couverture des bundles de compétences — couvrant TS et Python, du code d'exemple consommateur de SDK et du code source d'auteur de SDK) et analyse chacun avec le CLI compilé.
Il se termine avec un code non nul sur toute constatation proven/likely déjà présente dans test/regression-baseline.json — un enregistrement relu manuellement des constatations déjà vérifiées par rapport à leur ligne source. Les empreintes sont repo|rule|file, pas des numéros de ligne, donc le remaniement ordinaire en amont ne produit pas de bruit. Une nouvelle empreinte est une affirmation que le scanner doit justifier : si ce n'est pas un problème réel, c'est un bug de règle, corrigé à la cause racine et verrouillé comme nouveau fixture test-fixtures/safe/. Établir une référence de base sur une constatation que vous n'avez pas lue anéantit tout le mécanisme.
La couverture des bundles de compétences a sa propre ligne car le corpus evals/ de cisco-ai-defense/skill-scanner est étiqueté — chacun de ses 20 fixtures est livré avec un verdict _expected.json et se trouve sous un répertoire littéralement nommé malicious/ ou safe/, donc il sert aussi de contrôle de rappel, pas seulement de précision : 6/6 fixtures malveillants dans le périmètre déclenchent, 0 constatation sur tout ce qui est étiqueté safe, et 0 constatation sur les 18 bundles réels de anthropics/skills et les 14 de vercel/ai. (Les catégories Cisco restantes — injection SQL, traversée de chemin, épuisement des ressources, eval() générique d'un argument de fonction, une charge utile délibérément répartie sur quatre fichiers — sont soit hors du périmètre documenté LLM/MCP/RAG, soit au-delà de l'analyse de conjonction dans un même fichier ; voir l'entrée du changelog 0.6.0 pour le raisonnement spécifique sur chacune.)
Avant/après historique de l'exécution qui a conduit aux corrections de précision d'origine (constatations au niveau de preuve par défaut, sans --paranoid) :
| Dépôt | Avant | Après | Ce qui n'allait pas |
|---|---|---|---|
| vercel/ai | 773 | 1 | examples/, tests/ de niveau supérieur, et les répertoires de style ecosystem-tests/ avec trait d'union n'étaient pas reconnus comme chemins de confiance inférieure ; chunks (une variable courante de réponse en streaming) était traité comme preuve RAG sans ambiguïté |
| openai/openai-node | 47 | 0 | Même lacune de détection de chemin, appliquée aux propres examples//ecosystem-tests/ du SDK |
| anthropics/anthropic-sdk-typescript | 2 | 0 | Même lacune de détection de chemin sur un répertoire tests/ de niveau supérieur |
| modelcontextprotocol/typescript-sdk | 3 | 0 | Champs de métadonnées OAuth de style token_endpoint/tokenType signalés comme secrets divulgués |
| run-llama/llama_index | 18 | 15 | Un contrôle Python signalait tout champ description= contenant « system prompt » comme empoisonnement d'outil MCP proven, quel que soit le contexte. Les 15 restants sont des hits VEC001 sur les définitions génériques de récupérateur de la bibliothèque elle-même — analyser le code source d'un SDK de base de données vectorielle, pas du code applicatif, donc un filtre ne peut pas exister à vérifier ; une limite honnête et inhérente, pas un bug |
Exécution actuelle (2026-08-06) — les preuves versionnées sont enregistrées dans docs/benchmarks/v0.9.0.json :
| Dépôt | Constatations | Règles | Statut |
|---|---|---|---|
| openai-node, anthropic-sdk-typescript, anthropic-sdk-python, modelcontextprotocol/typescript-sdk, modelcontextprotocol/servers | 0 | — | propre |
| anthropics/skills (18 bundles de compétences réels) | 0 | — | propre — contrôle de précision pur pour SKL001–005 |
| vercel/ai (5 691 fichiers) | 0 | — | était de 40 (AI001, AI003, AI005, AI010, MCP002) avant le triage — chacune relue manuellement par rapport à la source et confirmée comme faux positif, tracée à 3 bugs indépendants de cause racine (voir ci-dessous), corrigée, et re-confirmée propre sur une re-analyse complète |
| run-llama/llama_index | 46 | VEC001 | limite inhérente, pas un bug — les définitions génériques de récupérateur de la bibliothèque elle-même, où aucun filtre de locataire ne peut exister à trouver |
| cisco-ai-defense/skill-scanner | 7 | SKL001, SKL002, SKL005 | toutes sur des fixtures étiquetés malicious/ — 6/6 dans le périmètre, 0 sur tout ce qui est étiqueté safe/ |
Le triage vercel/ai a trouvé trois bugs réels, corrigés à la cause racine — aucun spécifique aux règles de compétences v0.6.0, tous dans la logique partagée utilisée par de nombreuses règles :
resolveLlmSinktraitait tout appel résolu vers un module SDK LLM comme une invocation de modèle, quel que soit le nom de méthode — signalantisToolUIPart(un garde de type que le packageaiexporte juste à côté degenerateText) comme un appel LLM. Cela seul a causé 3 des 5 groupes de constatations (AI001, AI003, AI010).DANGEROUS_CALLEESdans AI005 inclut"query"pour les sinks de style injection SQL, mais"query"est aussi un verbe d'invocation légitime LLM/agent —claudeSdk.query({ prompt, options }), l'appel de modèle propre du SDK Claude Agent, était signalé comme « sortie LLM passée à un sink dangereux » uniquement à cause du nom de méthode partagé.REQUEST_SOURCES(dupliqué à l'identique dans MCP002, MCP010, VEC003) correspondait à un"params."nu — tout paramètre de fonction conventionnellement nomméparams, pas nécessairement des données de requête HTTP. Un validateur de schéma d'URL (assertOpenLinkParams(params: unknown)) était signalé comme « URL de serveur MCP provenant d'une entrée utilisateur. »
Tous les trois corrigés à la cause racine (pas au site d'appel spécifique) et épinglés comme fixtures permanents sous test-fixtures/. Détails complets dans CHANGELOG.md.
3. Validation vulnérable-vs-corrigé — prouve le rappel, pas seulement la précision.
Les deux couches ci-dessus ne vérifient que le scanner reste silencieux sur du code sûr. Les contrôles d'avis de DEP003 sont validés dans l'autre sens : épingler un package à une version documentée comme vulnérable et confirmer qu'il est signalé, puis l'épingler à la version corrigée et confirmer qu'il ne l'est pas.```bash
node --test test/dependency-guard.test.js
covers : `[email protected]` (CVE-2025-6514, vulnérable) signalé / `[email protected]` (corrigé) clair ; `[email protected]` (avant la porte dérobée) clair / `[email protected]` (après — aucun correctif légitime n'existe pour un paquet malveillant) toujours signalé ; `llama-cpp-python==0.2.71` (CVE-2024-34359, issu de l'ensemble généré par OSV) signalé / `==0.2.72` (corrigé) clair, y compris sous la normalisation du nom PyPI (`llama_cpp_python`) ; et les spécificateurs non épinglés de type `langchain>=0.1.0` produisant **zéro** résultat dans le rapport par défaut. La construction de ce test a révélé une lacune réelle : `DEP003` faisait correspondre les avis uniquement par nom de paquet, sans jamais comparer réellement la version déclarée à la plage affectée de l'avis — corrigé dans [`src/scanner/semver.ts`](https://github.com/akanthed/secureai-scan/blob/main/src/scanner/semver.ts).
L'ambiguïté est résolue différemment selon le type d'avis, délibérément. Un paquet **malveillant** se déclenche même lorsque la version déclarée ne peut pas être résolue — l'installation d'une porte dérobée est irrécupérable, donc il échoue vers le signalement. Une **CVE** se déclenche à `proven` uniquement lorsque la version déclarée est un épinglage exact prouvablement dans la plage affectée ; les versions non épinglées mais potentiellement affectées passent à `heuristic` (`--paranoid` uniquement). Appliquer la règle du type malveillant à un instantané CVE de 162 entrées placerait un résultat critique sur chaque dépôt déclarant `langchain>=0.1.0` — un bruit inexploitable à grande échelle.
## Feuille de route
Voir [`ROADMAP.md`](https://github.com/akanthed/secureai-scan/blob/main/ROADMAP.md) pour ce qui est livré et ce qui est prévu. Les deux moteurs linguistiques sont basés sur l'AST : ts-morph pour TypeScript/JavaScript et Tree-sitter pour Python. Les imports, appels, affectations, décorateurs, portées, arguments de mots-clés, champs de dictionnaires et chaînes Python sont des nœuds de syntaxe ; le code cible n'est jamais importé ni exécuté, et aucun interpréteur Python n'est requis. La lacune Python restante est une profondeur de taint inter-fonctions/inter-fichiers bornée, et non l'analyse syntaxique. Les performances d'analyse et les limites connues sont documentées dans [`docs/Performance.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Performance.md).
## Contribution
Les contributions sont les bienvenues — voir [`CONTRIBUTING.md`](https://github.com/akanthed/secureai-scan/blob/main/CONTRIBUTING.md) pour le flux de travail, et [`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) pour savoir comment ajouter une règle de détection qui respecte le niveau de précision ci-dessus. Chaque nouvelle règle nécessite un fixture dans [`test-fixtures/vulnerable/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/vulnerable) et [`test-fixtures/safe/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/safe), une entrée dans `src/scanner/catalog.ts`, et un cas dans `test/corpus.test.js` — `npm test` impose ces trois éléments.
## Licence
MIT © Akshay Kanthed

