Parcours « Comprendre et sécuriser l’IA » — étape 13/15
12Sécuriser un corpus RAG contre l’injection indirecte14Sécuriser les actions irréversibles des agents LLM
Comment valider le function calling d’un LLM
Section intitulée « Comment valider le function calling d’un LLM »Le function calling transforme un grand modèle de langage, ou LLM (Large Language Model), en producteur de requêtes vers des systèmes réels. Cette capacité accélère l’automatisation, mais elle déplace la frontière de confiance : toute sortie du modèle doit être traitée comme une entrée non fiable, même quand elle respecte parfaitement le format attendu.
Le function calling ressemble à un employé qui remplit des formulaires pour déclencher des actions dans une entreprise. Il fait gagner du temps, mais personne ne devrait envoyer un formulaire directement à la banque, au service informatique ou au service de livraison sans vérifier ce qui est demandé.
→ Lire l’explication complète de la marmotte
Frontière de confiance entre LLM et outil
Section intitulée « Frontière de confiance entre LLM et outil »Le compromis central est net : accepter une exécution automatique réduit la latence et la charge humaine, mais augmente la portée d’une erreur de génération, d’une instruction malveillante ou d’un défaut d’autorisation. La recommandation est donc de réserver l’exécution sans validation humaine aux actions réversibles, bornées et couvertes par une autorisation applicative indépendante du modèle.
Un LLM ne décide pas d’une action autorisée. Il propose un appel structuré. Cette distinction doit survivre au code, aux journaux et aux revues d’architecture. Un champ tool_name généré par le modèle n’est pas un droit d’accès, un champ account_id n’est pas une identité, et une chaîne qui ressemble à une commande n’est pas devenue moins dangereuse parce qu’elle arrive dans du JSON (JavaScript Object Notation).
L’outil doit recevoir deux flux séparés :
- une demande générée par le modèle, considérée hostile par défaut ;
- un contexte d’exécution fiable, issu de la session authentifiée, de la politique d’autorisation et de la configuration serveur.
Mélanger les deux flux est l’erreur structurante. Si le modèle peut fournir à la fois tenant_id, role et resource_id, il peut fabriquer un récit administratif très convaincant. Les modèles excellent à produire du texte plausible. C’est précisément le problème, pas une fonctionnalité bonus vendue sur une slide avec des robots souriants.
Les outils exposés doivent être déclarés statiquement. Un routeur ne doit jamais accepter un nom de fonction arbitraire, charger un module à partir d’une chaîne ou transmettre des arguments à un interpréteur de commandes. La liste blanche doit mapper un identifiant fonctionnel vers une implémentation connue, une politique d’accès, un schéma d’entrée, un schéma de sortie et une classe de risque.
Cette séparation rejoint les principes appliqués aux identités et contrôles d’accès : l’identité authentifiée porte les droits, pas le texte produit au cours d’une conversation. Un LLM n’est ni un sujet IAM (Identity and Access Management), ni un moteur de politique. Lui déléguer ce rôle revient à transformer une probabilité linguistique en mécanisme d’autorisation. L’industrie a déjà assez de cérémonies vaudou sans y ajouter des jetons d’accès.
Validation syntaxique, sémantique et métier des paramètres
Section intitulée « Validation syntaxique, sémantique et métier des paramètres »Valider un schéma JSON est nécessaire, mais insuffisant. Un schéma vérifie des types, des champs requis, des bornes et parfois des motifs. Il ne vérifie pas qu’un identifiant appartient au locataire courant, qu’une date respecte une période de clôture ou qu’un montant est compatible avec un plafond de délégation.
La chaîne de validation doit comporter quatre niveaux, dans cet ordre :
- Décodage strict : refuser le JSON invalide, les champs dupliqués et les types ambigus. Ne pas appliquer de conversion silencieuse entre chaîne, entier et booléen.
- Validation structurelle : imposer un schéma par outil, avec
additionalProperties: falseou l’équivalent. Les champs inconnus sont refusés, pas ignorés. - Validation sémantique : vérifier formats canoniques, plages, relations entre champs, identifiants de ressource et contraintes de pagination.
- Autorisation métier : évaluer l’action demandée contre l’identité, le locataire, l’état de la ressource et la politique serveur.
Le modèle peut produire {"amount": 100} alors que la devise est absente, ou {"path": "../../secrets"} alors que le champ respecte une longueur maximale. Les validations de type n’arrêtent ni l’escalade de privilèges ni la traversée de répertoires. Elles empêchent seulement une partie des accidents syntaxiques, ce qui est déjà honorable pour une barrière qui ne sait pas lire.
Les identifiants doivent être résolus côté serveur à partir d’un espace de noms contrôlé. Pour une action sur un document, accepter un identifiant opaque puis vérifier son appartenance au locataire courant. Ne jamais accepter un chemin local, une URL (Uniform Resource Locator) complète, un nom d’hôte ou une requête SQL (Structured Query Language) comme paramètre métier brut. Si un outil doit appeler un service distant, construire l’URL depuis une base approuvée et des segments encodés.
Les opérations à effet de bord exigent une idempotency key, ou clé d’idempotence, générée côté serveur et liée à l’intention validée. Sans cela, une reprise de conversation, un délai réseau ou une réponse partielle peut déclencher deux virements, deux suppressions ou deux créations de tickets. Le LLM n’a aucune notion fiable de livraison exactement une fois. Aucun système distribué sérieux non plus, d’ailleurs.
Exemple de passerelle d’exécution contrôlée
Section intitulée « Exemple de passerelle d’exécution contrôlée »L’exemple suivant utilise Python 3.11 et Pydantic 2.10. Installer la dépendance avec python -m pip install "pydantic==2.10.*". Le code démontre un outil de remboursement : le modèle propose les paramètres, mais l’identité et le locataire proviennent exclusivement du contexte fiable.
# Python 3.11+, dépendance : pydantic==2.10.*from decimal import Decimalfrom uuid import UUIDfrom pydantic import BaseModel, ConfigDict, Field, ValidationError
# Champs supplémentaires interdits : un argument inconnu doit échouer.class RefundArgs(BaseModel): model_config = ConfigDict(extra="forbid", strict=True)
invoice_id: UUID amount: Decimal = Field(gt=Decimal("0"), le=Decimal("500.00")) reason_code: str = Field(pattern=r"^(duplicate|service_failure)$")
def authorize_refund(actor_id: str, tenant_id: str, invoice_id: UUID, amount: Decimal) -> None: # Cette fonction consulte le référentiel métier côté serveur. # Le tenant et l'acteur ne viennent jamais de la réponse du LLM. if actor_id != "support-agent-42": raise PermissionError("acteur non autorisé") if tenant_id != "tenant-a": raise PermissionError("locataire invalide") if amount > Decimal("100.00"): raise PermissionError("validation humaine requise")
def execute_refund(model_arguments: dict, actor_id: str, tenant_id: str) -> dict: try: args = RefundArgs.model_validate(model_arguments) except ValidationError as error: return {"status": "rejected", "reason": error.errors()}
authorize_refund(actor_id, tenant_id, args.invoice_id, args.amount)
# Appel réel : transmettre une clé d'idempotence créée par le serveur. return {"status": "accepted", "invoice_id": str(args.invoice_id)}
# Exemple : actor_id et tenant_id proviennent d'une session déjà vérifiée.print(execute_refund( { "invoice_id": "123e4567-e89b-12d3-a456-426614174000", "amount": Decimal("80.00"), "reason_code": "duplicate", }, actor_id="support-agent-42", tenant_id="tenant-a",))Le plafond de 500.00 est une limite structurelle, pas une politique d’autorisation. Le plafond de 100.00 relève ici de la politique métier et impose une validation humaine. Cette différence compte : modifier la politique sans toucher au schéma évite de transformer chaque évolution de délégation en changement de contrat d’interface.
La sortie de l’outil mérite la même discipline. Ne pas retourner au modèle des secrets, des jetons de session, des messages internes de base de données ou des réponses HTTP (Hypertext Transfer Protocol) brutes. Produire un résultat minimal, typé et orienté tâche. Une erreur détaillée destinée au diagnostic peut devenir une exfiltration de topologie dès qu’elle est remise dans le contexte du modèle.
Injections indirectes et surface d’attaque des outils
Section intitulée « Injections indirectes et surface d’attaque des outils »L’injection directe consiste à demander au modèle d’ignorer ses règles. L’injection indirecte est plus dangereuse : une instruction hostile est placée dans une page web, un document, un courriel ou une base de connaissances que le modèle consulte. Le contenu récupéré n’est jamais une source de politique. Il reste une donnée non fiable, même s’il porte un logo institutionnel et une prose qui sent le PDF de comité.
L’attaque vise souvent à obtenir une action, pas à modifier un texte. Exemples fréquents : exfiltrer un extrait de mémoire vers un outil HTTP, rechercher des secrets dans un dépôt, élargir un filtre de données, envoyer un message à un destinataire externe ou appeler une adresse interne. Le risque augmente avec les connecteurs de recherche, les pièces jointes et les outils réseau.
Les contre-mesures ne reposent pas sur une consigne ajoutée au prompt. Un prompt améliore un comportement moyen, il ne constitue pas une barrière de sécurité. Il faut :
- isoler les capacités par outil et par environnement ;
- appliquer une politique de sortie réseau avec liste d’hôtes, méthodes HTTP et ports autorisés ;
- interdire les adresses locales, les plages privées non nécessaires et les redirections non revalidées pour limiter la SSRF (Server-Side Request Forgery) ;
- borner le volume lu, écrit et exporté par appel ;
- exiger une confirmation humaine pour les opérations externes irréversibles ;
- journaliser la demande brute, la version du schéma, la décision de politique et le résultat, avec masquage des données sensibles.
L’accès aux documents peut enrichir une réponse, pas signer un bon de commande. Les pages de recherche sémantique et embeddings doivent donc être traitées comme des sources de contenu, jamais comme des autorités capables d’accorder un privilège.
Secrets, exploitation et reprise après incident
Section intitulée « Secrets, exploitation et reprise après incident »Chaque outil doit utiliser une identité de service distincte, avec le privilège minimal et un périmètre fonctionnel étroit. Un jeton unique partagé par tous les outils rend le journal d’audit décoratif et la révocation brutale. Préférer des jetons courts, liés à l’outil, au locataire et à l’opération, puis vérifier ces attributs au point d’application.
Les secrets d’outils ne doivent pas entrer dans le contexte du LLM, les journaux de conversation ou les traces de débogage. Lorsqu’une clé longue durée ne peut être évitée, la stocker dans un gestionnaire de secrets ou un HSM (Hardware Security Module) selon le niveau de risque, puis prévoir rotation, révocation et test de restauration. La rotation qui n’a jamais été testée reste une superstition avec un calendrier.
La supervision doit distinguer les refus normaux des anomalies : hausse des erreurs de schéma, appels à des outils inhabituels, refus d’autorisation, destinations réseau rejetées, volume de données exportées et répétitions d’une même action. Un seuil d’alerte sans contexte produit surtout une collection de faux positifs, ce grand élevage industriel.
En cas d’incident, pouvoir désactiver un outil sans arrêter le modèle entier. La reprise exige aussi de rejouer les demandes à partir de journaux immuables, sans réexécuter les effets de bord. Conserver l’identifiant de requête, la clé d’idempotence, la version de politique et l’empreinte de la réponse validée. Sans ces éléments, l’analyse se résume à demander au modèle ce qu’il pensait faire. Réponse attendue : une prose impeccable, inutilisable en enquête.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans validation stricte des arguments produits par le modèle, la sortie du LLM est exécutée comme une commande de confiance alors qu’elle doit être traitée comme une entrée hostile, même parfaitement formatée. Un argument inattendu, une valeur hors bornes ou un champ supplémentaire passe alors directement vers un système réel : remboursement d’un montant arbitraire, action sur un compte qui n’appartient pas à l’utilisateur, appel d’un outil que le contexte a détourné par injection d’instructions. La frontière de confiance entre le modèle et l’outil s’efface, et une consigne cachée dans un document ou un message se transforme en action côté banque, infrastructure ou livraison. Sans schéma qui interdit les champs inconnus et sans autorisation vérifiée au niveau de l’outil, rien ne distingue une demande légitime d’une demande fabriquée, et les actions destructrices s’exécutent sans validation extérieure.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Un modèle de langage peut produire une demande pour utiliser un outil, par exemple rembourser une facture, consulter un document ou envoyer un message. Cette demande ne doit pas être exécutée directement. Le système reçoit d’abord les données proposées par le modèle et les traite comme des données potentiellement dangereuses.
Il vérifie ensuite que les champs attendus existent, que leur type est correct et qu’aucun champ imprévu n’a été ajouté. Puis il contrôle les règles réelles : le montant demandé, la ressource concernée, l’état de cette ressource et les droits de la personne connectée. L’identité de cette personne vient de la connexion au service, pas du texte créé par le modèle.
Si une vérification échoue, l’outil ne fait rien et enregistre le refus. Si tout est accepté, le système lance l’action avec un compte technique limité à cette tâche. Les actions sensibles peuvent demander une confirmation humaine avant de continuer.
Les documents lus par le modèle, les messages reçus et les pages web peuvent contenir des consignes malveillantes. Ils ne doivent donc jamais donner de droits supplémentaires. Les accès réseau, les destinations autorisées et la quantité de données envoyées doivent être limités. Enfin, les actions, les refus et les erreurs sont enregistrés pour pouvoir arrêter un outil compromis, retirer ses accès et comprendre ce qui s’est produit sans répéter une action dangereuse.
À lire aussi
- Sécuriser les actions irréversibles des agents LLMSécuriser les agents autonomes basés sur LLM disposant de function calling ou d'exécution de code face aux actions irréversibles déclenchées par une hallucination.
- Guardrails programmatiques pour assistant IA de productionArchitecture de garde-fous programmatiques pour un assistant IA en production, au-delà du prompt système et du contrôle d’accès.
- Empoisonnement des données d'affinage d'un LLM local et backdoorComment l'empoisonnement des données d'entraînement installe une porte dérobée dans un modèle de langage affiné localement, et comment la provenance, la détection statistique et les tests de déclencheurs la contrent.
- Comment red teamer un LLM local avant productionMéthode de red teaming pour tester les jailbreaks, les garde-fous et les scénarios d’abus d’un modèle de langage déployé localement.
Pages voisines
La citent
- Comment red teamer un LLM local avant production
- Guardrails programmatiques pour assistant IA de production
- Agentique : définition, lectures d'OpenAI et d'Anthropic, Claude Code
- Backend Foundry Local expliqué et comparé à LM Studio
- Différence entre IA et LLM : un champ entier, un type de modèle
- Gardien IA pour LLM, fonctionnement, exemples et choix de produit
- AI Act : contrôle humain d'une IA à haut risque (article 14)
- Faille chatbot Chevrolet 2023, Tahoe à 1 dollar et injection
Comment valider le function calling d’un LLM
Elle cite
