Security musings

Catégories

Tags

🔍 Licence d'Utilisation 🔍

Sauf mention contraire, le contenu de ce blog est sous licence CC BY-NC-ND 4.0.

© 2025 à 2042 Sébastien Gioria. Tous droits réservés.

⏱️
Temps de lecture estimé
~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

  1. yaml.safe_load par 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.
  2. 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.
  3. 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.
  4. Sandboxer l’installation des dépendances. Traiter requirements.txt, package.json et pyproject.toml des skills comme du code non fiable. Les installer dans un environnement isolé, sans accès réseau sortant arbitraire.
  5. 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.
  6. 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


Quelques références pour aller plus loin


À 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.

AST05