Retour
Sécurité de l'IA9 sept. 2026 · 7 min de lecture

L'injection de prompt n'est pas un bug qu'on corrige une fois

par detektd team

Écran d'ordinateur vu de biais affichant du code coloré dans une pièce sombre
Photo : rawpixel · CC0 · rawpixel

En janvier 2026, le chercheur RyotaK, de l'entreprise japonaise GMO Flatt Security, a révélé une faille critique dans claude-code-action, l'action GitHub officielle d'Anthropic qui fait tourner Claude Code dans les workflows d'intégration continue. Le principe de l'outil est simple et populaire : on mentionne l'agent dans une issue ou une pull request, il lit le contexte, écrit du code, répond en commentaire. C'est précisément cette boucle (lire du texte, puis agir) qui a été retournée contre les dépôts qui l'utilisaient.

Un problème de confiance, pas d'exécution

Le premier maillon était une vérification d'autorisation. Avant d'agir, l'action devait s'assurer que la personne qui la déclenchait avait des droits d'écriture sur le dépôt. Or la fonction chargée de ce contrôle, checkWritePermissions, faisait confiance sans condition à n'importe quel déclencheur de type « GitHub App ». N'importe qui peut enregistrer sa propre GitHub App : un attaquant pouvait donc lancer le workflow avec un contenu entièrement sous son contrôle.

La vérification qui faisait tout basculer (reconstitution simplifiée)
function checkWritePermissions(actor) {
  if (actor.type === "GitHub App") return true; // any app at all
  return hasRepoWriteAccess(actor);
}

Le second maillon était l'agent lui-même. Dans un runner GitHub Actions, les secrets du workflow sont des variables d'environnement du processus. Le contenu malveillant pouvait pousser l'agent à lire ces variables, puis à les recopier dans un endroit public : un commentaire d'issue, par exemple, via l'outil GitHub que l'action lui fournissait justement pour répondre. Parmi les données ainsi exposées : un jeton OIDC qui s'échange contre un jeton d'installation de la GitHub App de Claude, avec un accès en écriture complet au dépôt.

Issue écrite par l'attaquant
Contrôle d'autorisation contourné
L'agent lit les secrets du runner
Secrets publiés en commentaire

Le détail le plus inquiétant tient au dépôt d'Anthropic lui-même : il utilisait le même workflow vulnérable. Une attaque réussie aurait pu injecter du code dans l'action, et donc se propager à chaque dépôt qui l'utilisait en aval. C'est le schéma classique d'une attaque de chaîne d'approvisionnement, déclenché ici par une simple issue.

La « triade mortelle »

Le terme « prompt injection » a été popularisé en septembre 2022 par le développeur Simon Willison, qui en documente les variantes depuis. En juin 2025, il a formalisé une grille de lecture devenue une référence : la « lethal trifecta ». Un agent devient exploitable dès qu'il combine trois propriétés : un accès à des données privées, une exposition à du contenu non fiable, et la capacité de communiquer vers l'extérieur.

  • Données privées : les secrets du workflow, présents dans l'environnement du runner.
  • Contenu non fiable : le texte d'une issue, rédigé par n'importe qui.
  • Communication externe : la possibilité de poster un commentaire public.

Le cas claude-code-action coche les trois cases. Et c'est ce qui explique pourquoi la faille ne se résume pas à une ligne de code : corriger checkWritePermissions ferme une porte d'entrée, mais tant qu'un agent réunit les trois propriétés, n'importe quel autre moyen de lui faire lire du texte malveillant (un fichier du dépôt, une dépendance, un commentaire de pull request) rouvre la même situation.

Pourquoi on ne « patche » pas l'injection de prompt

La comparaison avec l'injection SQL est instructive. Pendant des années, les injections SQL venaient du même problème : des données (la saisie de l'utilisateur) mélangées aux instructions (la requête). La solution a été structurelle : les requêtes paramétrées, qui séparent physiquement les deux. Rien d'équivalent n'existe pour un modèle de langage : les instructions et les données arrivent dans le même flux de texte, et le modèle n'a aucun moyen fiable de savoir lesquelles il doit suivre. C'est pour cette raison que l'OWASP classe l'injection de prompt en tête de son Top 10 des risques liés aux applications LLM (LLM01).

RyotaK a depuis signalé une cinquantaine de façons distinctes de contourner le système de permissions de Claude Code.

GMO Flatt Security

Anthropic a corrigé le contournement dans claude-code-action v1.0.94, noté 7,8 en CVSS v4.0, et versé une prime de 4 800 dollars. Mais le chiffre d'une cinquantaine de contournements dit l'essentiel : chaque correctif individuel fonctionne, et la surface, elle, reste ouverte. Elle change de forme à chaque nouvel outil donné à l'agent, chaque nouveau connecteur MCP, chaque nouvelle façon de déguiser une instruction en donnée.

Cline : le scénario devenu réel

Un mois après la divulgation, en février 2026, le scénario théorique s'est produit ailleurs. Le workflow GitHub Actions de Cline, un agent de code open source concurrent, a été compromis : un attaquant a volé un jeton de publication npm et poussé une version cline@2.3.0 non autorisée directement sur le registre. Les utilisateurs qui installaient l'extension depuis npm recevaient une version que les mainteneurs n'avaient jamais publiée.

Le problème n'est d'ailleurs pas né avec les agents. En 2021, le GitHub Security Lab documentait déjà les « pwn requests » : des workflows qui s'exécutent avec les secrets du dépôt en réponse à du contenu venu de l'extérieur, comme une pull request issue d'un fork. Les agents IA ne créent pas cette classe de faille, ils l'élargissent, parce qu'un agent transforme n'importe quel texte en actions potentielles.

Durcir un workflow qui fait tourner un agent

Puisqu'on ne peut pas empêcher un modèle de lire une instruction malveillante, la défense consiste à casser la triade : réduire ce que l'agent peut atteindre, qui peut le déclencher, et ce qu'il peut faire sortir. Sur GitHub Actions, deux réglages font l'essentiel du travail : des permissions minimales pour le jeton du workflow, et une condition qui réserve le déclenchement aux membres du dépôt :

Permissions minimales et déclenchement réservé
permissions:
  contents: read
  issues: write

jobs:
  agent:
    if: >-
      contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'),
        github.event.comment.author_association)
    runs-on: ubuntu-latest
  • Ne donner au job de l'agent que les secrets dont il a strictement besoin, et aucun secret de publication (npm, registre de conteneurs, déploiement).
  • Séparer le travail de l'agent (proposer du code) de la publication (fusionner, publier), avec une validation humaine entre les deux.
  • Garder l'action à jour : les correctifs de ce type de faille arrivent régulièrement, et une version figée reste vulnérable.

Anatomie des secrets d'un runner

Pour mesurer ce qu'un agent peut atteindre dans un runner GitHub Actions, il faut savoir ce qui s'y trouve. Chaque job reçoit un GITHUB_TOKEN généré pour l'occasion, dont les droits sont fixés par le bloc permissions du workflow, d'où l'importance de ne pas laisser les droits par défaut. S'y ajoutent les secrets déclarés dans le workflow, exposés comme variables d'environnement là où on les référence.

Le cas le plus sensible est l'OIDC. Quand un workflow déclare la permission id-token: write, le runner reçoit deux variables d'environnement, ACTIONS_ID_TOKEN_REQUEST_URL et ACTIONS_ID_TOKEN_REQUEST_TOKEN, qui permettent de demander à GitHub un jeton d'identité signé. Ce jeton est ensuite échangé auprès d'AWS, de GCP, d'Azure (ou de la GitHub App de Claude dans le cas qui nous occupe) contre des accès réels. Autrement dit, quiconque lit l'environnement du runner pendant l'exécution peut fabriquer ce jeton. C'est pour cela que la lecture de /proc/self/environ était si grave : elle ne révélait pas seulement des secrets statiques, mais le moyen d'en obtenir de nouveaux.

Le jeton OIDC contient un claim sub qui décrit précisément son origine, par exemple repo:acme/app:ref:refs/heads/main, ou repo:acme/app:environment:production si le job utilise un environnement GitHub. La politique de confiance côté cloud doit épingler ce claim au plus près. Une condition du type « n'importe quelle branche de n'importe quel dépôt de l'organisation » transforme chaque workflow compromis en accès à la production.

Politique de confiance AWS : un seul dépôt, un seul environnement
"Condition": {
  "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:sub": "repo:acme/app:environment:production"
  }
}

Deux autres réflexes comptent. GitHub masque les secrets dans les logs, mais uniquement quand leur valeur exacte apparaît : une valeur encodée en base64, découpée ou publiée via l'API (dans un commentaire, par exemple) échappe au masquage. Et un secret ne devrait être exposé qu'à l'étape qui en a besoin, via le bloc env de cette étape, plutôt qu'au niveau du job entier où l'agent tourne.

Le secret n'existe que pour l'étape qui l'utilise
steps:
  - name: Run agent (no secrets in its environment)
    run: ./run-agent.sh

  - name: Upload report
    run: ./upload.sh
    env:
      UPLOAD_TOKEN: ${{ secrets.UPLOAD_TOKEN }}

Ce qu'un scan externe peut en voir

Un scanner comme detektd ne voit pas vos workflows CI. Il voit, en revanche, la partie de la surface IA que votre app expose publiquement, et c'est souvent là que la triade se forme du côté des utilisateurs :

  1. Prompts système ou schémas d'outils d'un agent envoyés au navigateur, lisibles par n'importe quel visiteur.
  2. Points d'entrée publics qui transmettent une saisie utilisateur non filtrée directement à un LLM.
  3. Endpoints MCP ou d'agent accessibles sans authentification, exposant des outils capables d'agir (fichiers, shell, requêtes HTTP).
L'injection de prompt n'est pas un bug qu'on corrige une fois | detektd