Un chatbot d’assistance pour le locataire acme reçoit une demande de remboursement. L’utilisateur envoie uniquement du texte via le chat ; pourtant, assist-api permet au modèle de générer une requête payments.refund et transmet cette demande en utilisant les identifiants de paiement étendus du service. Le backend émet une proposition de remboursement que u_123 n’était pas autorisé à initier. Aucune manipulation complexe n’a été nécessaire : du texte non fiable a transité du message de l’utilisateur vers une action, alors que le modèle n’avait pas l’autorité pour décider si cette action était permise.
Un garde-fou (ou guardrail) est un point de contrôle qui restreint ce qu’une application LLM accepte, dépense, renvoie ou exécute. Dans ce scénario de défaillance, le garde-fou utile le plus simple consiste à rejeter toute demande de remboursement à moins que l’entité authentifiée ne dispose de la portée (scope) requise et qu’une étape de validation côté serveur n’approuve les paramètres réels du remboursement. La formulation du prompt ne peut pas assurer cette protection, car le modèle se contente de suivre le texte qu’il reçoit ; il n’authentifie pas les utilisateurs, ne gère pas de budgets et n’autorise pas l’utilisation d’outils.
Le système en production comporte un chemin que nous conserverons tel quel. L’utilisateur final u_123 appartient au locataire acme et appelle assist-api via llm-gateway. Le service FastAPI assemble un prompt, appelle le fournisseur providerA sur la route chat en utilisant $CHAT_MODEL, et peut demander l’exécution d’un outil. u_123 dispose des droits chat:invoke et payments:refund:propose, mais pas d’une autorisation de paiement illimitée.
Le contrat de paiement reste restreint et explicite. Le seul outil de paiement est payments.refund, avec un invoice_id (ex. : inv_987), un amount_cents sous forme d’entier (ex. : 1299) et un reason sous forme de chaîne de caractères. Une proposition de remboursement ne constitue pas une autorisation d’exécution du remboursement. Le serveur vérifie l’appartenance au locataire, la portée, les arguments et l’approbation avant toute exécution d’une action de paiement irréversible.
Toute frontière véhiculant du texte est considérée comme non fiable, y compris le message de l’utilisateur, les documents récupérés, les résultats des outils et la sortie du modèle. Un document d’assistance récupéré peut contenir des instructions entrant en conflit avec la politique de l’application, et la sortie du modèle peut inclure des champs qui deviendraient dangereux s’ils étaient transmis à une API de paiement sans validation préalable. Il s’agit de la vulnérabilité LLM01 (Injection de prompt) du classement OWASP Top 10 pour les applications LLM 2025, publié par le projet OWASP GenAI Security en novembre 2024. L’invariant est concret : aucun appel d’outil ne peut s’exécuter avec plus d’autorité que celle conférée par l’action initiale de u_123.
Le chemin fixe regroupe les entrées non fiables et les limites d’application des règles dans une même vue.
La passerelle identifie l’appelant, tandis qu’assist-api applique les règles relatives au locataire (tenant) de l’utilisateur, aux portées (scopes), au contrat de l’outil et aux exigences d’approbation. Le fournisseur providerA génère une réponse en utilisant ses propres identifiants, mais ces derniers ne doivent en aucun cas conférer l’autorisation d’effectuer un remboursement au nom de u_123. Utiliser les permissions étendues du service pour un utilisateur qui en est dépourvu crée une vulnérabilité de type « confused deputy » (mandataire confus).
Règle de délimitation
Le modèle peut suggérer l’action
payments.refund; seuls des contrôles côté serveur peuvent l’autoriser ou l’exécuter.
Chaque frontière ayant un rôle distinct, un refus doit identifier l’étape qui n’a pas pu se poursuivre en toute sécurité. llm-gateway renvoie le code 401 Unauthorized en cas d’identifiants manquants, invalides, révoqués ou compromis. Après l’authentification, la passerelle ou l’application renvoie 403 Forbidden si l’appelant ne dispose pas d’une portée requise. Une destination bloquée pour la récupération d’un outil entraîne également un 403 Forbidden, et cette destination n’est jamais contactée.
Une fois que providerA a répondu, assist-api valide la sortie du modèle avant qu’elle ne soit transmise à un analyseur, à une API de paiement ou à tout autre composant en aval. Une sortie non conforme au schéma entraîne le renvoi de 422 Unprocessable Content (terme de la RFC 9110 remplaçant l’appellation « Unprocessable Entity » présente dans les anciennes documentations). La réutilisation d’une clé d’idempotence avec des paramètres de remboursement différents renvoie également 422 Unprocessable Content, tandis que la réutilisation de la même clé alors que la première requête est toujours active renvoie 409 Conflict.
Les autres résultats possibles reflètent une limite opérationnelle plutôt qu’une décision du modèle.
202 Accepted est renvoyé lorsqu’un remboursement irréversible est mis en attente d’approbation, accompagné d’un identifiant de demande en attente.429 Too Many Requests (avec l’en-tête Retry-After) est renvoyé lorsqu’un quota partagé ou une limite de débit est atteint.503 Service Unavailable est renvoyé lorsque le magasin de règles d’application est indisponible et que l’application ne peut pas appliquer la politique en toute sécurité.504 Gateway Timeout est renvoyé lorsque providerA dépasse le délai imparti pour l’appel au fournisseur.Ces résultats évitent aux clients de considérer un refus comme une simple défaillance indéfinie du modèle. Ils indiquent également où mener les investigations : s’agit-il d’un problème d’identité au niveau de llm-gateway, d’une validation dans assist-api ou d’un appel au fournisseur qui n’a pas abouti ?
Dans le scénario d’échec initial, l’attaquant ne dispose que d’un message de discussion, mais parvient à déclencher une proposition de remboursement irréversible. Quelle étape doit effectuer le refus en premier : A, l’authentification de l’appelant et l’association au locataire (tenant binding) au niveau de llm-gateway ; B, l’autorisation et l’approbation des outils au niveau de assist-api ; ou C, les vérifications de sortie après le modèle ?