SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

Prompt injection : sécuriser un agent de programmation

Un agent de programmation traite aussi les textes contrôlés par un attaquant comme des instructions. Repérez les surfaces d’injection serveur et priorisez les défenses efficaces.

Ce qu’est une prompt injection contre un agent de programmation

Une prompt injection contre un agent de programmation se définit simplement : le texte que l’agent lit est traité comme une instruction que l’agent suit. L’agent ouvre un fichier, un commentaire de pull request, une page web ou le résultat d’un appel d’outil. Tout cela arrive sous la même forme de texte que votre propre demande. Si un attaquant contrôle une partie de ce texte, il écrit dans votre session.

Tous les produits d’agent actuels ont cette propriété. Le modèle reçoit une seule séquence de tokens. Votre demande, le system prompt, le contenu des fichiers et les résultats des outils sont assemblés, puis le modèle prédit la suite. Aucun token ne porte de niveau de privilège. Rien dans le format n’indique quelle partie vous avez autorisée et quelle partie provient du README d’un tiers.

Cette page présente le modèle de menace : comment du texte contrôlé par un attaquant atteint un agent exécuté sur un serveur, ce que l’attaquant peut obtenir à chaque étape et quelles protections justifient l’effort nécessaire. Les recommandations de confinement de nos autres guides n’ont de sens qu’une fois que vous savez de quoi vous protégez l’agent.

Pourquoi le modèle ne peut pas séparer le contenu des instructions

L’entraînement aide, mais ne règle pas le problème. Les modèles actuels sont entraînés à traiter les textes récupérés avec méfiance et refusent de nombreuses tentatives grossières. Un refus correspond à une probabilité, pas à une règle. Un attaquant peut reformuler sa demande, réessayer et dissimuler le texte dans un format que personne n’avait prévu. Rien ne limite le nombre de formulations qu’il peut essayer.

Le projet OWASP GenAI répertorie ce problème sous LLM01:2025 Prompt Injection et le divise en deux catégories. L’injection directe est un prompt utilisateur qui modifie le comportement du modèle. L’injection indirecte est un contenu externe, comme un site web ou un fichier, qui modifie le comportement lorsque le modèle le traite. C’est l’injection indirecte qui nous intéresse sur un serveur, car un agent lit beaucoup plus de texte que celui que vous saisissez.

La première étude systématique est celle de Greshake et de ses collègues, Ce pour quoi vous n’avez pas souscrit (2023). Voici la conclusion à retenir : lorsqu’une application transmet à un modèle du texte récupéré et que ce modèle peut appeler des outils, le traitement de ce texte est proche d’une exécution de code arbitraire.

La condition qui transforme une lecture en compromission

Lire un texte hostile ne cause pas de dommages en soi. Les données doivent pouvoir sortir de la machine.

Simon Willison a nommé cette combinaison le trio létal en juin 2025. Un agent qui détient des données privées, qui est exposé à du contenu non fiable et qui peut envoyer des données vers l’extérieur peut être amené à faire sortir les premières par le troisième.

Un agent de programmation sur votre VPS réunit ces trois conditions dès le premier jour. Les données privées sont votre code source, votre fichier .env, vos clés SSH et votre historique shell. Le contenu non fiable comprend chaque dépôt, chaque page et chaque résultat d’outil qu’il lit. La sortie peut être git push, curl, npm publish, le contenu d’une pull request ou un lien affiché dans votre terminal sur lequel vous cliquez.

Vous ne pouvez pas supprimer la deuxième condition, car la lecture de texte non fiable est précisément la tâche pour laquelle vous avez installé l’agent. Toute défense pratique agit donc sur les deux autres.

Lorsque du texte non fiable atteint un agent de programmation sur un serveur

Le dépôt dans lequel l’agent travaille

Chaque fichier du checkout constitue une entrée. Commentaires du code source, README.md, journaux des modifications, fixtures de test, code vendorisé et fichiers d’instructions de l’agent eux-mêmes : CLAUDE.md, AGENTS.md et leurs équivalents. Un agent chargé de comprendre une base de code les lit, puisque c’est ce que vous lui demandez.

L’attaquant peut ainsi atteindre toutes les personnes qui clonent le dépôt et y dirigent un agent. Les fichiers d’instructions sont la voie la plus directe, car ils sont conçus pour être lus comme des instructions. Une pull request qui ajoute quatre lignes utiles à CLAUDE.md et une ligne qui redirige l’agent est facilement ignorée par un reviewer humain.

Issues, pull requests et commentaires de code review

Tout ce qu’un inconnu peut saisir dans votre tracker atteint l’agent dès que vous lui demandez de faire le tri. En mai 2025, Invariant Labs a publié une analyse de sécurité GitHub MCP présentant exactement ce scénario. L’agent d’un développeur avait accès à un dépôt public et à des dépôts privés. Un attaquant a ouvert une issue dans le dépôt public. Lorsque le développeur a demandé à l’agent d’examiner les issues ouvertes, l’agent a lu le contenu des dépôts privés et l’a écrit dans une pull request du dépôt public.

Ce rapport décrit un problème d’architecture, et non un défaut du code du serveur MCP. L’agent disposait d’un token d’accès trop large, lisait une boîte de réception publique et avait les droits d’écriture. Rien n’était mal configuré au sens habituel du terme. La réponse consiste donc à limiter les droits, et non à appliquer un patch.

Les pages web récupérées par l’agent

Documentation, réponses sur un forum, page d’un fournisseur, résultat de recherche. N’importe lequel de ces contenus peut contenir du texte destiné à l’agent plutôt qu’à vous. La conversion du HTML en texte offre davantage de possibilités à un attaquant, car le contenu qu’un navigateur n’affiche jamais atteint tout de même le modèle.

L’attaquant prend le contrôle au moment où l’agent est le moins surveillé. Personne ne lit intégralement une page que l’agent a récupérée pendant une recherche.

La sortie des outils MCP

MCP (model context protocol) est le moyen courant par lequel les agents se connectent à des outils externes. Les résultats reviennent sous forme de texte et sont directement placés dans la fenêtre de contexte. Il existe ici deux surfaces, et non une seule. Les données renvoyées par un outil constituent la première. Le nom et la description de l’outil, que le modèle lit pour décider quand l’appeler, constituent la seconde. Un serveur que vous ne contrôlez pas peut modifier l’un ou l’autre entre deux appels.

Un attaquant qui insère du texte dans la sortie d’un outil atteint tous les autres outils dont dispose l’agent. C’est ainsi qu’une injection dans un élément peu sensible peut finalement piloter un élément très sensible.

Journaux CI, sortie de build et métadonnées des dépendances

npm install affiche du texte provenant de paquets que vous n’avez pas écrits. Un échec de test affiche un message d’assertion provenant d’une bibliothèque. Le journal d’un job d’intégration continue (CI) contient des milliers de lignes de sortie provenant de tiers. Demandez à un agent de corriger un build en échec, et il lit l’ensemble de ces données.

Dans ce cas, l’attaquant atteint la machine de build. Celle-ci contient généralement des identifiants de déploiement et des tokens de registre, tout en étant moins surveillée qu’un ordinateur portable.

Ce qu’un attaquant obtient réellement

Quatre conséquences doivent être prises en compte.

Vol d’identifiants. Tout ce que le processus de l’agent peut lire entre dans le périmètre : variables d’environnement, ~/.aws/credentials, ~/.ssh, token gh, fichier de configuration Docker. Leur exfiltration ne nécessite pas curl. Un commit sur une branche, la description d’une pull request, un package publié dans un registry ou une requête DNS vers un nom contrôlé par l’attaquant suffit à faire sortir les données du serveur.

Modifications de code que vous approuvez. L’agent écrit du code. Lui faire écrire une ligne subtilement erronée est donc le résultat le plus facile à obtenir. Il peut ajouter une dépendance ou un appel de journalisation qui transmet un token dans un journal que vous expédiez vers un autre système.

Persistance. Un fichier écrit une seule fois continue de fonctionner sans aucun modèle : un hook dans .git/hooks, un script postinstall dans package.json, une ligne ajoutée à un fichier de démarrage du shell ou une ligne supplémentaire dans CLAUDE.md. La commande suivante l’exécute.

Déplacement dans votre réseau. L’agent s’exécute à l’endroit où vous le lancez. Si ce serveur peut atteindre une base de données sur loopback, un service d’administration interne, le metadata service de votre fournisseur cloud ou un autre hôte du réseau privé, tout ce qui pilote l’agent le peut également.

Les modes d’approbation automatique suppriment le dernier contrôle

En mode par défaut, Claude Code demande votre confirmation avant d’exécuter une commande ou de modifier un fichier. Cette demande constitue le contrôle humain entre chacune des surfaces présentées ci-dessus et l’exécution réelle. Les modes qui suppriment cette demande suppriment également ce contrôle.

La documentation est explicite concernant bypassPermissions : utilisez-le uniquement dans des environnements isolés, comme des conteneurs ou des machines virtuelles, où Claude Code ne peut pas causer de dommages. Le mode automatique est moins strict. Il approuve automatiquement les appels d’outils et effectue des contrôles de sécurité en arrière-plan pour vérifier que les actions correspondent à votre demande. Ces contrôles détectent de nombreux problèmes. Ils reposent toutefois sur le jugement du modèle à propos de la sortie du modèle. Considérez-les donc comme un filtre, et non comme une limite de sécurité.

Un administrateur peut supprimer ces deux contrôles. Définissez permissions.disableBypassPermissionsMode ou permissions.disableAutoMode sur "disable" dans un fichier de configuration, puis placez ce fichier dans les paramètres gérés afin qu’un projet extrait du dépôt ne puisse pas le remplacer. Notre guide sur le mode automatique et les règles d’autorisation de Claude Code explique où chaque règle s’applique.

Défenses, classées selon ce qu’elles vous apportent

Aucune de ces mesures ne constitue un correctif. Chacune limite soit ce que l’agent détient, soit ce qu’il peut en faire.

  1. Une machine que vous pouvez détruire et reconstruire, afin qu’une compromission vous coûte une heure plutôt qu’un incident.
  2. Des identifiants distincts des vôtres, limités à un seul dépôt et à courte durée de validité.
  3. Aucun secret persistant dans l’environnement dont héritent les commandes de l’agent.
  4. Des règles du système d’exploitation qui contrôlent les connexions réseau sortantes et l’accès aux fichiers pour chaque processus lancé par l’agent.
  5. Les demandes d’approbation activées pour les écritures et les appels réseau.
  6. Des hooks comme filet de sécurité déterministe pour les actions précises que vous pouvez identifier.
  7. Lire le diff avant de le fusionner.

L’ordre compte. Les points 1 à 4 restent efficaces même lorsque le modèle est entièrement contrôlé par un attaquant. Les points 5 à 7 dépendent de l’attention d’une personne, précisément ce qui cesse de fonctionner lors d’une longue exécution de l’agent.

Placez l’agent sur une machine que vous pouvez détruire

Un VPS qui contient un checkout et un seul token limité représente une cible bien moins intéressante qu’un ordinateur portable contenant vos clés. Exécutez l’agent avec son propre utilisateur non privilégié, et non avec votre compte de connexion ou avec root. Nos guides sur une VM jetable pour les agents de développement et les utilisateurs avec le principe du moindre privilège sur un VPS décrivent la configuration. Le guide exécuter Claude Code en toute sécurité sur un VPS décrit son utilisation quotidienne.

Retirez les secrets de l’environnement

Une variable d’environnement est lisible par chaque processus enfant, donc par chaque commande exécutée par l’agent. Le sandbox de Claude Code peut supprimer certaines variables nommées avant chaque commande exécutée dans le sandbox. Sous Linux, le sandbox nécessite d’abord 2 paquets :

sudo apt-get install bubblewrap socat

Puis, dans ~/.claude/settings.json :

{
  "sandbox": {
    "enabled": true,
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org"]
    },
    "credentials": {
      "envVars": [
        { "name": "GITHUB_TOKEN", "mode": "deny" },
        { "name": "NPM_TOKEN", "mode": "deny" }
      ]
    }
  }
}

Une entrée deny supprime cette variable avant l’exécution de chaque commande dans le sandbox. allowedDomains contient les commandes exécutées dans le sandbox vers les hôtes que vous indiquez. Le bloc credentials nécessite Claude Code v2.1.187 ou une version ultérieure, vérifiée en août 2026. Exécutez /sandbox dans une session pour voir quelles couches sont actives et quelles dépendances manquent. Déterminer quels secrets doivent réellement exister sur cette machine constitue la plus grande partie du travail. Le guide garder les secrets hors de portée d’un agent IA vous accompagne dans cette démarche.

Limitez les connexions réseau sortantes au niveau du système d’exploitation

Une règle de firewall ne dépend pas de la décision prise par le modèle. Exécutez l’agent avec un utilisateur agent dédié, puis bloquez ce que cet utilisateur envoie :

table inet agentcage {
  chain output {
    type filter hook output priority filter; policy accept;
    meta skuid "agent" ct state established,related accept
    meta skuid "agent" oif lo accept
    meta skuid "agent" counter drop
  }
}

L’utilisateur agent ne dispose alors que de loopback. Son trafic doit donc passer par un proxy exécuté sur la même machine, qui contient l’allowlist des hostnames. Avec https_proxy configuré vers ce proxy, le client envoie une requête CONNECT et le proxy effectue la résolution de nom. L’agent n’a donc pas besoin de son propre DNS sortant (domain name system). Vérifiez votre configuration avec sudo nft list ruleset et surveillez le compteur de la règle de blocage augmenter lorsque l’agent tente d’atteindre une nouvelle destination.

Gardez une deuxième session SSH ouverte lorsque vous modifiez les règles du firewall. Vérifiez également le comportement de votre runtime de conteneurs avec ces règles : Docker écrit ses propres chaînes, et les ports Docker publiés contournent ufw décrit le problème qui en résulte.

Hooks : le contrôle que le modèle ne peut pas contourner par un argumentaire

Les règles d’autorisation et les hooks sont appliqués par Claude Code, et non par le modèle. La documentation l’indique clairement : les instructions de votre prompt ou de CLAUDE.md déterminent ce que Claude tente de faire, mais elles ne modifient pas ce que Claude Code autorise. Cette distinction constitue toute la valeur du mécanisme. Une ligne de CLAUDE.md indiquant « ne lancez jamais curl » est une suggestion qu’un paragraphe injecté peut contester. Un hook est un processus qui renvoie un code de sortie.

Enregistrez un hook PreToolUse dans .claude/settings.json :

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/no-egress.sh"
          }
        ]
      }
    ]
  }
}

Le hook reçoit l’appel d’outil au format JSON sur l’entrée standard. Le code de sortie 2 bloque l’appel et affiche à Claude la raison fournie sur la sortie d’erreur standard. Le code de sortie 0 laisse l’appel poursuivre le flux normal d’autorisation.

#!/usr/bin/env bash
# PreToolUse: stdin holds the tool call, exit 2 blocks it.
cmd=$(jq -r '.tool_input.command // ""')
if printf '%s' "$cmd" | grep -qE '(^|[;&|]|\s)(curl|wget|nc|ncat)(\s|$)'; then
  echo "Blocked: this repository does not allow outbound network commands." >&2
  exit 2
fi
exit 0

Voici le point important. Il s’agit d’une denylist appliquée à une chaîne shell, et les denylists de chaînes shell présentent des failles. python3 -c ouvre un socket sans utiliser le mot curl. Une cible make deploy masque le même appel un niveau plus bas. Écrivez des hooks pour les erreurs que vous pouvez identifier, et placez la limite sur laquelle vous comptez réellement dans le kernel ou sur le réseau.

Les règles de refus des permissions ont également une limite de correspondance qu’il faut connaître. Les règles de refus Read et Edit couvrent les propres outils de fichiers de Claude ainsi que les commandes de fichiers qu’il reconnaît dans Bash, telles que cat, head, tail et sed. Elles ne couvrent pas un script Python ou Node qui ouvre lui-même le fichier. Les règles sont évaluées dans l’ordre suivant : refus, puis demande, puis autorisation. Une règle de refus ne peut donc pas comporter d’exception sous forme d’allowlist.

{
  "permissions": {
    "deny": [
      "Read(.env)",
      "Read(./secrets/**)",
      "Bash(git push *)"
    ]
  }
}

Surveillez ce qui sort et lisez le diff

Une exécution de l’agent produit un diff et un ensemble d’appels réseau. Les deux doivent être examinés avant toute fusion ou tout déploiement. Une revue de sécurité auto-hébergée du diff détecte une catégorie de changements différente de celle qu’un survol humain permet de repérer. Le guide savoir ce qu’un agent de développement envoie hors de la machine vous indique à quoi ressemble le trafic normal, afin qu’une requête inhabituelle se remarque.

Ce qui reste à résoudre

Il n’existe toujours pas de séparation fiable entre le contenu et les instructions. Toutes les défenses disponibles sont soit des filtres avec un taux d’échec, soit des limites sur les conséquences. Aucun élément de la stack n’indique qu’un fragment de texte est une donnée qui ne doit jamais être suivie comme une instruction.

Les filtres sont utiles, mais ils échouent aussi. Un classifieur qui détecte la plupart des tentatives d’injection doit être correct à chaque fois, tandis qu’un attaquant ne doit réussir qu’une seule fois. Cette asymétrie explique pourquoi le taux de réussite publié pour une défense constitue un point de départ pour la tentative suivante, et non une garantie.

Les travaux les plus prometteurs portent sur la conception plutôt que sur le modèle. CaMeL, présenté dans Defeating Prompt Injections by Design (Debenedetti et ses collègues, 2025), extrait d’abord le flux de contrôle et le flux de données de la requête approuvée. Les données non approuvées ne peuvent ainsi pas modifier le comportement du programme. Le système applique ensuite des contrôles de capacités lors des appels aux outils. Les propres chiffres de l’article sur le benchmark AgentDojo montrent le coût de cette approche.

ChartAgentDojo tasks solved, published figures from the CaMeL paper (2025)
The data behind this chart
[
  {
    "label": "Undefended agent",
    "tasks_solved_pct": 84
  },
  {
    "label": "CaMeL",
    "tasks_solved_pct": 77
  }
]

L’agent sans défense a résolu 84 pour cent des tâches. CaMeL en a résolu 77 pour cent, avec une garantie de sécurité. Ces chiffres sont ceux publiés dans l’article pour un benchmark donné. Ils ne mesurent pas votre charge de travail. L’écart entre les deux correspond approximativement au coût réel d’une garantie aujourd’hui.

Tant qu’une conception de ce type n’est pas disponible dans les outils que vous utilisez chaque jour, partez du principe que l’agent finira par être compromis et faites en sorte que l’incident reste sans conséquence. C’est tout l’intérêt d’une machine disposable, de credentials aux droits limités, d’un egress contrôlé et de l’habitude de lire le diff.

FAQ

Puis-je empêcher l’injection de prompt en demandant à l’agent d’ignorer les instructions présentes dans les fichiers ?

Non. Cette phrase se trouve dans la même fenêtre de contexte que l’attaque et entre donc en concurrence directe avec le texte de l’attaquant. La documentation de Claude Code établit clairement la distinction : les instructions de votre prompt ou CLAUDE.md indiquent ce que l’agent essaie de faire, mais ne modifient pas ce que l’outil autorise. Considérez un fichier d’instructions comme une déclaration d’intention. Placez tout ce sur quoi vous comptez dans des règles d’autorisation, un hook PreToolUse ou une règle de pare-feu.

L’injection de prompt représente-t-elle un risque réel si l’agent n’interagit qu’avec mon propre dépôt ?

Oui, car votre dépôt contient beaucoup de texte que vous n’avez pas écrit. Les fichiers README des dépendances, les URL des lockfiles, les fixtures de test, le code vendorisé et la sortie de npm install sont présents dans le cadre d’une tâche normale. Tout ce qui provient d’un issue tracker ou d’un site de documentation arrive de la même manière. Le risque augmente avec la quantité de contenu que l’agent lit, et un agent utile en lit beaucoup.

L’exécution de l’agent dans un conteneur règle-t-elle le problème ?

Elle limite les dommages, mais uniquement si vous retirez également les identifiants. Un conteneur auquel votre agent SSH est forwardé, qui contient des identifiants cloud dans son environnement et qui dispose d’un accès réseau sans restriction donne presque les mêmes possibilités à un attaquant que l’hôte. Le conteneur vous apporte surtout un système de fichiers que vous pouvez supprimer et un environnement propre pour appliquer des règles d’egress. Associez-le à un token limité à un seul dépôt.

Quelle modification unique réduit le plus le risque ?

Retirez les identifiants persistants de l’environnement dont héritent les commandes de l’agent, puis appliquez à cette machine une politique d’egress en deny par défaut. Ensemble, ces deux mesures suppriment la troisième condition de la trifecta létale : le texte peut toujours détourner l’agent, mais les données auxquelles il accède ne peuvent aller nulle part d’utile. Les demandes d’approbation et la revue des diffs sont également utiles. Elles dépendent toutefois de la vigilance d’une personne pendant une longue exécution, raison pour laquelle elles viennent après ces deux mesures.