~7 minutes
Cet article fait partie de la série OWASP Agentic Skills Top 10. Retrouvez l’introduction et le plan complet sur la page de la série.
En janvier 2026, le package npm yutube-dl-core — découvert dans plusieurs skills ClawHub pendant l’investigation ClawHavoc s’exécutait silencieusement à l’installation des dépendances. Pas d’interaction utilisateur. Pas de confirmation. Le loader du skill tirait le package, npm install le déclenchait, et le payload s’exécutait avec les permissions complètes de l’agent. La désérialisation non sécurisée n’est pas une nouveauté : PyYAML et js-yaml ont tous deux des antécédents documentés. Dans l’écosystème des skills, le vecteur est simplement nouveau et largement sous-estimé.
AST05 couvre ce risque : des payloads exécutables embarqués dans les fichiers de configuration des skills, qui se déclenchent automatiquement au chargement, avant toute interaction humaine.
Description du risque
On constate que les fichiers de configuration des skills concentrent plusieurs formats historiquement dangereux sur un seul cycle de chargement : YAML (SKILL.md, manifest.yaml), JSON (package.json, manifest.json), Markdown avec frontmatter. Ce qui change par rapport aux applications traditionnelles : le chargement est automatique et silencieux, déclenché par l’agent sans intervention humaine, avec le contexte de permissions complet du runtime.
Un skill se charge automatiquement. L’attaquant n’a pas besoin d’interaction utilisateur ; le loader fait le travail à sa place.
La surface d’attaque s’étend au-delà du SKILL.md : requirements.txt, package.json, pyproject.toml et les définitions de tools MCP (JSON Schema) sont tous parsés pendant l’initialisation. Les frameworks d’agents modernes — LangChain, CrewAI, AutoGen, Claude Code — intègrent tous des mécanismes de chargement de skills ou d’outils qui traversent ces formats sans sandboxing systématique.
Exemples d’incidents
| Incident | Date | Impact |
|---|---|---|
| yutube-dl-core (npm) | Jan 2026 | Package malveillant tiré par des skills ClawHub via npm install. S’exécute automatiquement au chargement ; compromis silencieux sans interaction utilisateur |
| ClawHavoc — staged loaders | Jan 2026 | SKILL.md propre en apparence, mais requirements.txt référencé tire des packages malveillants à l’installation. 9 000+ environnements compromis |
| Snyk toxicskills-goof | Fév 2026 | Démonstration publique : payload YAML + base64 + Unicode zero-width dans SKILL.md. Exécution au parsing avec yaml.load() non sécurisé ; invisible à la review humaine |
| MCP tool definitions | Q1 2026 | Serveurs MCP utilisant des loaders JSON non validés : prototype pollution possible dans les runtimes Node.js consommant les définitions de tools |
Scénarios d’attaque
On distingue ici quatre chemins d’exploitation, du plus direct au plus furtif.
1. Exécution via YAML
Le frontmatter du SKILL.md contient un tag YAML dangereux. Si le loader utilise yaml.load() sans Loader=yaml.SafeLoader, le tag s’exécute au parsing :
Python, Node.js (via js-yaml en mode unsafe) et Ruby (Psych) sont tous concernés par leurs defaults historiquement non sécurisés.
2. Staged loader
Le SKILL.md passe l’inspection de surface : aucun payload visible. Mais le requirements.txt ou le package.json référencé tire un package malveillant pendant l’installation automatique des dépendances. Ce package exécute son payload dans le hook postinstall ou setup.py, avant même que l’agent n’ait terminé l’initialisation.
Le skill propre n’est qu’un leurre. La charge utile voyage dans les dépendances.
3. JSON prototype pollution
Le manifest.json contient une clé __proto__ ou constructor qui empoisonne le prototype d’objet du loader dans les runtimes Node.js. Le loader ne plante pas ; il continue de fonctionner, mais avec un namespace de configuration altéré que l’attaquant contrôle partiellement.
4. Config injection
Des formats de configuration alternatifs (TOML, INI, .env) avec un parsing insuffisamment sandboxé permettent l’injection de propriétés dans le namespace de configuration du skill runner. L’injection est invisible dans les logs puisqu’elle s’opère pendant la phase d’initialisation, avant que le logging ne soit activé.
Mitigations
yaml.safe_loadpar défaut, sans exception. Désactiver explicitement les tags dangereux (!!python/object,!!python/apply). Traiter tout autre loader comme non fiable jusqu’à preuve du contraire.- Parser dans un subprocess isolé. Séparer le processus de désérialisation du runtime principal ; un crash ou une exécution malveillante ne peut pas atteindre le contexte de l’agent.
- Allowlist stricte des clés autorisées. Rejeter tout champ YAML ou JSON inattendu avant toute désérialisation ; ne pas ignorer silencieusement les clés inconnues.
- Sandboxer l’installation des dépendances. Traiter
requirements.txt,package.jsonetpyproject.tomldes skills comme du code non fiable. Les installer dans un environnement isolé, sans accès réseau sortant arbitraire. - Réduire les privilèges au moment du parsing. Ne jamais désérialiser les fichiers de skills avec les permissions complètes de l’agent. Dropper au minimum avant l’ouverture des fichiers.
- Valider le schéma avant toute désérialisation. Une étape JSON Schema ou Pydantic qui s’exécute sur la représentation brute — avant tout cast — bloque la majorité des injections de structure.
Mapping OWASP
- A8 (Insecure Deserialization, OWASP Top 10 Web)
- LLM03 (Supply Chain)
- CWE-502 (Deserialization of Untrusted Data)
- ASVS V5.5 (Deserialization)
Risques liés
- AST01 : Malicious Skills : la désérialisation non sécurisée est un des vecteurs d’exécution des payloads malveillants
- AST02 : Supply Chain Compromise : les staged loaders exploitent la chaîne de dépendances pour masquer le payload
- AST04 : Insecure Metadata : métadonnées malformées déclenchant des vulnérabilités de désérialisation
- AST06 : Weak Isolation : l’exécution en mode hôte amplifie l’impact d’une RCE au chargement
Quelques références pour aller plus loin
- OWASP AST10 : AST05 (repo officiel)
- Snyk : toxicskills-goof — SKILL.md to Shell Access
- Snyk : 280+ Leaky Skills sur ClawHub
- OWASP Top 10 A8 : Insecure Deserialization
- PyYAML documentation : safe_load vs load
✓ À retenir 📌
✓ `yaml.load()` est un vecteur d'exécution de code. Un tag `!!python/object` dans un `SKILL.md` s'exécute au parsing, avant toute interaction utilisateur. La solution immédiate : `yaml.safe_load()`.
✓ La surface d'attaque ne se limite pas au `SKILL.md` : tous les fichiers de configuration parsés à l'initialisation (`requirements.txt`, `package.json`, définitions MCP) sont des vecteurs potentiels.
✓ La défense en profondeur combine trois couches : parseur safe (yaml.safe_load) + validation de schéma (Pydantic/JSON Schema) + sandbox d'installation des dépendances. Chaque couche bloque un scénario que les deux autres ne couvrent pas.
