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

Air Security a montré que Claude Code, Codex, GitHub Copilot et Gemini CLI installent un plugin « épinglé » sur un commit approuvé sans jamais vérifier qu’ils ont bien récupéré ce commit. Il suffit d’une branche nommée comme le hash. L’agent installe le code de l’attaquant et annonce avoir respecté le pin.


Un pin qui ne vérifie pas est un post-it

Épingler une dépendance sur un hash de commit fait partie des bons réflexes de la supply chain. On relit un état précis du code, on note son SHA, et on exige que rien d’autre ne s’installe. Les marketplaces de plugins pour agents de code font la même chose : un plugin est relu, puis verrouillé sur un commit.

Air Security a trouvé en mai que quatre agents ne tenaient pas cette promesse. Claude Code, OpenAI Codex, GitHub Copilot et Gemini CLI demandaient à Git de récupérer le commit épinglé, puis installaient ce qui sortait du checkout, sans comparer le résultat au hash attendu. Le rapport est public depuis la mi-septembre, et le nom qu’il a reçu dit l’essentiel : Plugin4Shell.

Avant les détails, un point : aucun mécanisme cryptographique n’est cassé. L’attaque exploite une absence de vérification, et celle-là ne se durcit pas par configuration.

Note du PandaRedacteur : un SHA épinglé, c’est un ticket de vestiaire. Personne n’a jamais vérifié que le manteau rendu était le sien, mais le ticket était parfaitement conforme.

Le tour de passe-passe, étape par étape

Le mécanisme tient en une ambiguïté de Git : une chaîne de quarante caractères hexadécimaux peut désigner un objet commit ou le nom d’une branche. Quand les deux coexistent, Git peut préférer le nom de référence à l’objet.

  1. Une marketplace verrouille un plugin sur un commit précis, identifié par son SHA.
  2. Le propriétaire du dépôt du plugin crée une branche dont le nom est ce même SHA, et la pointe vers un code différent. Il peut la définir comme branche par défaut.
  3. L’agent lance le checkout du SHA épinglé. Git résout le nom de branche, et récupère le code de la branche.
  4. L’agent n’effectue aucune vérification après coup, et rapporte l’installation de la version verrouillée.
  5. Le plugin s’exécute avec les droits du développeur : fichiers, clés d’API, accès CI/CD, systèmes internes, environnements cloud.

Gemini CLI a sa variante. L’agent récupère le commit puis bascule sur FETCH_HEAD ; une branche nommée FETCH_HEAD redirige alors le checkout vers un contenu choisi par l’attaquant.

flowchart LR
    A["Marketplace : plugin épinglé sur SHA"] --> B["Agent : git checkout SHA"]
    B --> C{"Une branche porte<br/>le nom du SHA ?"}
    C -->|"non"| D["Commit approuvé installé"]
    C -->|"oui"| E["Code de l'attaquant installé"]
    E --> F["L'agent annonce : version verrouillée OK"]

Une précision qui compte pour le risque réel : GitHub bloque les noms de branches et de tags qui ressemblent à des hashes. Les marketplaces hébergées sur GitHub sont donc protégées par la plateforme, pas par l’agent. Bitbucket et les serveurs Git auto-hébergés peuvent rester exploitables, selon les chercheurs. Dans un grand groupe, ce sont souvent les forges où vivent les plugins internes, justement ceux qu’on a relus.

Où en sont les éditeurs

AgentStatut à la divulgation
Claude CodeCorrigé en 2.1.179
Codex (OpenAI)Corrigé en 0.146.0 ; OpenAI décrit le bug dans sa PR publique : Git peut interpréter un SHA demandé comme un nom de branche
GitHub CopilotPas de correctif, malgré une notification en juin
Gemini CLIProduit déprécié, pas de correctif prévu ; Google oriente vers Antigravity, où cette attaque ne s'applique pas

Aucun CVE n’était assigné à la date de la divulgation, et aucune exploitation dans la nature n’a été rapportée. Air Security résume l’enjeu en une phrase, et je la trouve exacte : chaque agent vérifie le verrouillage sur la machine de l’utilisateur, pas dans la marketplace, donc aucune marketplace ne peut corriger ça ; le correctif doit se faire dans l’agent.

Analyse STRIDE

Catégorie STRIDEApplicableExplication
SpoofingOuiUne branche prend le nom du commit relu et se fait passer pour lui. Le dépôt se déguise en version approuvée.
TamperingOuiLe code installé n'est pas celui qui a été relu ; la garantie d'intégrité du pin est annulée.
RepudiationOuiL'agent déclare avoir installé le SHA épinglé. Un audit qui se fie à ce message conclut à tort que tout est conforme.
Information disclosureOuiLe plugin lit tout ce que lit le développeur : clés, jetons CI/CD, accès cloud.
Denial of ServiceNonAucun vecteur de déni de service dans cette faille ; l'objectif de l'attaquant est l'exécution de code, pas l'indisponibilité.
Elevation of privilegeOuiDu propriétaire d'un dépôt de plugin à l'exécution de code avec les droits d'un développeur, sans clic supplémentaire selon les chercheurs.

Impact potentiel

Dimension Niveau Conséquence concrète
Confidentialité Critique Clés d'API, jetons CI/CD et accès cloud du compte développeur, exposés dès l'exécution du plugin.
Intégrité Critique Commits, configuration de l'agent et fichiers du poste modifiables avec les droits de l'utilisateur.
Disponibilité Modéré Pas l'objectif de l'attaque, mais un code exécuté avec ces droits peut aussi détruire.
Réputation Élevé Le pin figurait dans les preuves de conformité. Découvrir qu'il ne vérifiait rien est difficile à expliquer à un client ou à un auditeur.

Mitigations

  • Mettre à jour as usual. Pour Copilot, aucune version corrigée n’existe à la divulgation : la mitigation passe par les points suivants. Pour Gemini CLI, migrez.
  • Réduire les sources. Installez des plugins depuis les marketplaces GitHub intégrées, où la plateforme bloque les noms de branches qui imitent un hash, et désactivez la mise à jour automatique des plugins issus de dépôts tiers.
  • Inventorier les plugins de chaque poste et de chaque agent, avec leur origine et leur SHA. Un plugin que l’on ne sait pas lister ne se vérifie pas.
  • Surveiller les dépôts de plugins : création de branches, changement de branche par défaut, transfert de propriété.
  • Préférer des artefacts signés à des références Git quand c’est possible : une signature se vérifie, un nom de branche se détourne (Cosign en est le point de départ).
  • Vérifier après coup. La correction attendue des éditeurs tient en une comparaison, et rien n’empêche une équipe de la faire dans son propre outillage d’installation :

La famille de défauts

Plugin4Shell ressemble à ce que racontait GitSpawn : une configuration ou une référence Git suffisent à faire exécuter du code à un agent, avant que le moindre contrôle humain intervienne. Et le volet 1 des AI-Pods range cette catégorie dans les multiplicateurs qui se cumulent : privilèges, opacité et monoculture. Quatre éditeurs, la même absence de vérification : un défaut de famille, pas un incident isolé. DeepSeek Harness montrait quelques jours plus tôt le même genre d’erreur d’un autre côté : un contrôle qui croit une déclaration au lieu de vérifier un fait.

Note du PandaRedacteur : « on a épinglé, on est tranquilles » est la phrase qui ouvre le plus d’incidents de supply chain. La suite commence généralement par : « et personne ne vérifiait que le pin tenait ».

📚 Quelques références pour aller plus loin


À retenir 📌

  • ✓ Quatre agents concernés : Claude Code, Codex, GitHub Copilot et Gemini CLI installaient le plugin sans vérifier que le HEAD extrait correspondait au SHA épinglé.
  • ✓ Le trou est dans Git, pas dans la crypto : une branche nommée comme le hash est préférée à l'objet commit ; Gemini CLI se fait détourner par une branche FETCH_HEAD.
  • ✓ GitHub bloque ces noms, pas les autres forges : Bitbucket et les serveurs Git internes peuvent rester exploitables.
  • ✓ Statut : corrigé dans Claude Code 2.1.179 et Codex 0.146.0 ; pas de correctif pour Copilot ; Gemini CLI déprécié.
  • ✓ Vérifier après coup : comparer git rev-parse HEAD au SHA relu coûte trois lignes ; inventorier les plugins et surveiller les dépôts qui les portent.

Plugin4Shell : le pin qui ne vérifiait rien - Générée par IA

Plugin4Shell : le pin qui ne vérifie rien - Générée par IA