Sorties IA non validées vers des opérations sensibles
Comprendre la frontière entre sortie probabiliste d'un modèle d'IA et contrat déterministe d'une opération sensible, et comment concevoir des barrières d'exécution étanches.
Sur cette page (15 sections)▼
L'intégration croissante de l'intelligence artificielle générative au sein des architectures web transforme profondément la surface d'attaque logicielle. De plus en plus d'applications modernes ne se contentent plus d'afficher du texte généré : elles confient aux modèles de langage (LLM) la génération d'arguments transmis directement à des fonctions critiques (mutations en base de données, requêtes réseau sortantes, écritures de fichiers, redirections ou injection de balises HTML).
Cette pratique introduit un changement de paradigme fondamental : un modèle génératif produit une sortie probabiliste, tandis qu'une opération sensible attend un contrat d'exécution strictement déterministe.
Lorsque la sortie d'un modèle est consommée comme paramètre d'un point d'action critique (sink) sans barrière de contrôle déterministe, l'application s'expose à des corruptions d'état, des contournements d'autorisation et des injections indirectes.
Le modèle mental : la frontière probabiliste / déterministe
Le risque ne provient pas du fait qu'un modèle d'IA « se trompe » ou hallucine occasionnellement. Le risque réside dans l'absence de frontière technique étanche entre l'espace de raisonnement statistique du modèle et l'espace d'exécution formel de votre système.
flowchart TD
A[Entrée non fiable / Prompt utilisateur] --> B[Modèle IA génératif]
B --> C[Sortie brute probabiliste]
C --> D{Barrière déterministe ?}
D -- Non / Contrôle absent --> E[Sink sensible critique]
style E stroke:#ef4444,stroke-width:2px
D -- Oui : Validation structurelle & métier --> F[Décision d'autorisation]
F --> G[Opération sensible sécurisée]
style G stroke:#22c55e,stroke-width:2px
Dans ce schéma, la barrière déterministe remplit un rôle de garde-fou non négociable. Elle ne tente pas de « raisonner » avec le modèle : elle vérifie de manière binaire et mathématique si la structure et les valeurs produites se conforment rigoureusement au contrat attendu.
Les puits sensibles critiques (sinks)
Dans le moteur d'audit GVO, la règle GVO-AI-UNVALIDATED-OUTPUT-TO-SINK-V1 inspecte les chemins de données reliant l'invocation d'un modèle d'IA (chat.completions, generateText, generateObject) à des fonctions d'exécution à fort impact. Cinq familles de sinks sont particulièrement critiques :
1. Les mutations en base de données (DATABASE_MUTATION)
Transmettre un objet issu d'un modèle directement à un appel ORM (prisma.order.create({ data: aiOutput }) ou supabase.from('users').update(aiOutput)) sans validation autorise le modèle à altérer des colonnes non prévues (colonnes d'administration, soldes financiers, identifiants de propriété user_id).
2. L'injection dans le Document Object Model (HTML_INJECTION)
Injecter la réponse brute d'un modèle dans une directive non assainie comme dangerouslySetInnerHTML expose les visiteurs à des failles XSS. Un tiers malveillant peut orchestrer une injection indirecte de prompt (indirect prompt injection) via un contenu externe résumé par le modèle pour faire émettre du code JavaScript malveillant.
3. Les requêtes réseau sortantes (EXTERNAL_FETCH)
Utiliser une URL suggérée par le modèle pour déclencher un appel fetch() côté serveur sans allowlist stricte recrée immédiatement le risque de faille SSRF. Le modèle peut être amené à cibler des interfaces de métadonnées cloud (169.254.169.254) ou des services internes d'entreprise.
4. Les redirections de session (REDIRECT)
Orienter le flux de navigation d'un utilisateur vers une destination produite par l'IA permet la mise en place d'attaques par redirection ouverte (open redirect), détournant des flux d'authentification ou d'invitation.
5. L'exécution de commandes système (COMMAND_EXECUTION)
Passer des arguments générés par IA à des processus enfants (exec, spawn) sans séparation formelle des commandes et des données représente le niveau de vulnérabilité le plus destructeur (exécution de code arbitraire).
Distinguer les niveaux de défense : validation de schéma vs validation métier
Une erreur fréquente consiste à considérer qu'un schéma de typage (comme Zod) suffit à sécuriser intégralement une action pilotée par IA. La validation structurelle n'est que le premier niveau d'une chaîne de défense en profondeur.
┌─────────────────────────────────────────────────────────────┐
│ 1. VALIDATION STRUCTURELLE (Zod / Typebox) │
│ Vérifie la forme : types, champs obligatoires, formats │
└──────────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────┐
│ 2. VALIDATION MÉTIER & INTERVALLES │
│ Vérifie le sens : montants autorisés, enum stricts, bornes│
└──────────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────┐
│ 3. CONTRÔLE D'AUTORISATION & OWNERSHIP │
│ Vérifie les droits de l'utilisateur actif sur la cible │
└──────────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────┐
│ 4. CONFIRMATION HUMAINE (Human-in-the-Loop) │
│ Pour les actions irréversibles ou destructives │
└─────────────────────────────────────────────────────────────┘
La validation structurelle (Le contenant)
Elle s'assure que la sortie respecte scrupuleusement une forme syntaxique définie : « l'attribut quantity est un entier positif », « l'attribut email correspond à un format valide ». Elle neutralise les corruptions techniques, mais ne garantit pas que la valeur ait du sens métier.
La validation métier (Le contenu)
Même si un modèle produit un entier valide 1000000, la règle métier de votre application n'autorise peut-être qu'un maximum de 50 crédits par transaction. Les règles de bornage et d'appartenance à un catalogue prédéfini relèvent de la validation métier.
L'autorisation et l'isolation des contextes
Le modèle ne doit jamais avoir l'autorité de décider qui a le droit d'effectuer une action. L'identifiant du propriétaire de la ressource (user_id, organization_id) doit être injecté de façon déterministe à partir de la session cryptographique de l'utilisateur connecté, et jamais extrait de la réponse générée par l'IA.
Anti-pattern : injection directe sans garde déterministe
Le fragment ci-dessous illustre l'erreur classique observée dans les projets intégrant rapidement un LLM :
// ANTI-PATTERN : LA SORTIE DU MODÈLE COMMANDE DIRECTEMENT L'ÉCRITURE EN BASE
import { openai } from '@/lib/ai';
import { db } from '@/lib/db';
export async function updateTaskFromAi(userId: string, taskId: string, userPrompt: string) {
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: 'Analyse la demande et retourne un JSON avec title et priority' },
{ role: 'user', content: userPrompt }
],
response_format: { type: 'json_object' }
});
// DANGER : JSON.parse garantit uniquement la syntaxe, pas l'intégrité de la structure
const rawPayload = JSON.parse(response.choices[0].message.content || '{}');
// VULNÉRABILITÉ : rawPayload peut écraser userId, role, ou injecter des attributs imprévus
await db.task.update({
where: { id: taskId },
data: rawPayload // Mutation sensible non vérifiée
});
}
Dans cet exemple, l'option json_object garantit uniquement la validité syntaxique du JSON. Elle n'offre aucune garantie sur les clés retournées. Si le modèle injecte { "userId": "autre-compte", "status": "COMPLETED" }, la mutation est exécutée aveuglément.
Pattern recommandé : schéma strict, allowlist et ownership
L'exemple ci-dessous illustre la défense en profondeur : validation structurelle stricte (ici avec Zod, mais toute bibliothèque de schéma robuste convient), rejet des propriétés inconnues, puis rattachement explicite à l'identité vérifiée de l'acteur. La validation structurelle seule n'est que le premier niveau de la chaîne décrite plus haut.
// PATTERN RECOMMANDÉ : VALIDATION STRUCTURELLE STRICTE ET ISOLATION DE CONTEXTE
import { z } from 'zod';
import { openai } from '@/lib/ai';
import { db } from '@/lib/db';
import { requireAuthenticatedUser } from '@/lib/auth';
// 1. Définition du contrat déterministe strict
const TaskUpdateSchema = z.object({
title: z.string().min(1).max(120).trim(),
priority: z.enum(['LOW', 'MEDIUM', 'HIGH']),
estimatedHours: z.number().int().min(1).max(40).optional(),
}).strict(); // Rejette toute clé superflue générée par l'IA
export async function updateTaskFromAiSafe(taskId: string, userPrompt: string) {
// 2. Vérification déterministe de l'identité de l'appelant
const currentUser = await requireAuthenticatedUser();
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: 'Extrais title et priority. Retourne un JSON conforme.' },
{ role: 'user', content: userPrompt }
],
response_format: { type: 'json_object' }
});
// 3. Validation par la barrière de schéma
const jsonParsed = JSON.parse(response.choices[0].message.content || '{}');
const validation = TaskUpdateSchema.safeParse(jsonParsed);
if (!validation.success) {
throw new Error('La sortie générée ne respecte pas le contrat structurel attendu.');
}
// 4. Mutation sécurisée : seules les données validées sont appliquées,
// avec vérification formelle de la propriété de la tâche (ownership)
return await db.task.update({
where: {
id: taskId,
userId: currentUser.id, // Isolation étanche garantie par la session
},
data: {
title: validation.data.title,
priority: validation.data.priority,
estimatedHours: validation.data.estimatedHours,
},
});
}
Ce que GVO observe lors de l'audit
Le moteur d'audit déterministe GVO analyse le graphe d'appel de vos fichiers serveur (SERVER_ACTION, DATABASE_MUTATION, HTML_INJECTION, EXTERNAL_FETCH).
- Repérage des invocations d'IA : Détection des imports de SDK IA reconnus (OpenAI, Anthropic, Vercel AI SDK, LangChain, Groq) et de leurs primitives de complétion.
- Suivi des variables de sortie : Traçabilité statique des variables recevant les résultats textuels ou structurés générés.
- Analyse des sinks critiques : Localisation des opérations de persistance, de rendu HTML dangereux ou de navigation initiées dans le même périmètre.
- Vérification de la barrière de validation : Le moteur recherche la présence d'une validation structurelle observable (
zod.safeParse, allowlist d'énumérations, assainisseur HTML) entre l'inférence et le puits sensible. - Statuts de résultat — Le moteur GVO peut émettre cinq statuts :
CONFIRMED,PROBABLE,COVERAGE_GAP,TO_VERIFYetDISMISSED. Dans le contexte des sorties IA non validées, les statuts les plus fréquemment produits sont :CONFIRMED: La donnée issue de l'IA atteint directement une opération sensible sans barrière observable.PROBABLE: Un flux probable vers un sink critique est identifié, sans certitude absolue sur la résolution dynamique.COVERAGE_GAP: Le moteur détecte une zone de code non couverte par ses heuristiques (ex. : construction de requête dynamique hors des patterns reconnus).TO_VERIFY: Une fonction de traitement intermédiaire non résolue statiquement est présente.DISMISSED: Une barrière déterministe stricte est établie sur le chemin de données.
Liens internes et guides connexes
- XSS : injection de contenu actif et assainissement — Sécuriser les affichages web contre l'injection de scripts.
- SSRF : requêtes serveur vers des destinations contrôlables — Verrouiller les appels réseau sortants.
- Row Level Security Supabase — Protéger l'accès aux données au niveau de la base PostgreSQL.
- Guide : Sécuriser les mutations Next.js — Bonnes pratiques d'autorisation et de validation des Server Actions.
À poursuivre
Vérifier ce point sur votre projet
GVO confronte le code et les configurations de votre application aux règles de sécurité réelles.