Comment les données quittent un agent IA
L’exfiltration, c’est une fuite de données par une route que le propriétaire n’avait pas prévue. Un agent a plus de routes qu’un agent de discussion : des liens dans ses réponses, des images, des appels d’outils et des requêtes web.
Le module « Sécuriser les applications LLM » traite l’injection. Cette leçon ferme les sorties, pour qu’un ordre caché dans une page n’ait nulle part où envoyer ce que l’agent sait.
À la fin de cette leçon
Remarque
Je peux fermer les routes par lesquelles les données quittent un agent, avec une liste de destinations autorisées que le code vérifie.
Mots clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Exfiltration | des données qui sortent par une route que le propriétaire n’avait pas prévue | |
| Liste d’autorisation | les seules destinations permises, toutes les autres étant refusées | docs.exemple.fr |
| Hôte | le nom du serveur dans un lien, entre // et le / suivant | |
| Chaîne de requête | la partie d’un lien après le point d’interrogation, qui peut porter des données | |
| Sortie réseau | le trafic qui quitte votre système vers un autre |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Quatre routes par où les données sortent
Un lien dans la réponse : l’utilisateur clique et l’adresse porte des données. Une image dans la réponse : le navigateur la charge tout seul, donc les données partent sans clic.
Un appel d’outil : une requête de l’agent avec des données dans ses arguments. Un nom de fichier ou un message envoyé à un outil de discussion ou de courrier.
Les quatre placent des données dans une destination ou un message. Vérifiez donc où va chacune.
Exemple guidé : route et contrôle
| Route | Comment les données sortent | Le contrôle |
|---|---|---|
| Lien | Dans la chaîne de requête ou le chemin | L’hôte est dans la liste d’autorisation |
| Image | Le navigateur la charge aussitôt | Même contrôle de l’hôte, ou aucune image |
| Outil de requête web | Dans l’adresse ou le corps | Hôte dans la liste, et méthode limitée |
| Outil de discussion ou de courrier | Dans le texte du message | Destinataire dans la liste |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Le Run vérifie des liens, il n’envoie rien
Le premier script contient quatre sorties inventées. Il trouve chaque lien et lit son hôte. Puis il compare l’hôte à une liste d’autorisation.
Rien n’est récupéré. Les adresses sont inventées et le script se contente de les lire. Notez l’écriture du lien d’image : il commence par un point d’exclamation, que le script fabrique à partir d’un code de caractère.
Écrire le vérificateur de liens, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const autorises = [\"docs.exemple.fr\", \"aide.exemple.fr\"];' 'const bang = String.fromCharCode(33);' 'const sorties = [' ' \"Voir le guide sur https://docs.exemple.fr/debut pour les étapes.\",' ' \"Fait. \" + bang + \"[logo](https://img.pirate.exemple/p.png?d=ANNE-4411)\",' ' \"Ouvrez [le formulaire](https://aide.exemple.fr/forme) pour continuer.\",' ' \"Cliquez sur https://suivi.exemple.net/c?note=secret pour confirmer.\",' '];' > sortie.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le vérificateur de liens, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function hoteDe(url) {' ' return url.split(\"/\")[2].split(\"?\")[0];' '}' 'function urlsDans(texte) {' ' const trouvees = [];' ' for (const mot of texte.split(\" \")) {' ' const debut = mot.indexOf(\"http\");' ' if (debut >= 0) trouvees.push(mot.slice(debut).split(\")\")[0]);' ' }' ' return trouvees;' '}' 'for (const texte of sorties) {' ' for (const url of urlsDans(texte)) {' ' const ok = autorises.includes(hoteDe(url));' ' const requete = url.includes(\"?\") ? \" (porte une requête)\" : \"\";' ' console.log((ok ? \"autorisé \" : \"BLOQUÉ \") + hoteDe(url) + requete);' ' }' '}' >> sortie.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lancer le vérificateur de liens
docker exec -w /home/node/project agent node sortie.jsAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Ce qu’affiche le script
| Ligne | Ce qu’elle montre |
|---|---|
| autorisé docs.exemple.fr | L’hôte est dans la liste |
| BLOQUÉ img.pirate.exemple (porte une requête) | Une image vers un inconnu, avec des données dans l’adresse |
| autorisé aide.exemple.fr | Un lien vers un hôte listé |
| BLOQUÉ suivi.exemple.net (porte une requête) | Un hôte que personne n’a listé |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Comparez l’hôte entier, exactement
Vérifier qu’un lien commence par le nom autorisé est trop faible. Le lien docs.exemple.fr.pirate.exemple commence ainsi et va ailleurs.
Un lien peut aussi contenir un nom avant une arobase. Dans [email protected], le vrai hôte est la partie après l’arobase. Extrayez l’hôte, puis comparez-le en entier.
Une erreur fréquente
Attention
Beaucoup écrivent une liste noire de mauvais hôtes. Les attaquants en ont une infinité de nouveaux. Listez ce qui est autorisé et refusez le reste, dans le code, avant d’afficher la réponse.
Écrire le script du modèle, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' '' 'const autorises = [\"docs.exemple.fr\", \"aide.exemple.fr\"];' 'const messages = [' ' { role: \"system\", content: \"Tu aides les utilisateurs d’un logiciel. Réponds en une phrase avec un seul lien markdown vers https://docs.exemple.fr/aide.\" },' ' { role: \"user\", content: \"Où trouver de l’aide ?\" },' '];' '// Échantillon enregistré, utilisé seulement quand aucun modèle ne répond.' 'const echantillon = \"Vous trouverez de l’aide dans [le guide](https://docs.exemple.fr/aide).\";' > sortie2.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script du modèle, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function hoteDe(url) {' ' return url.split(\"/\")[2].split(\"?\")[0];' '}' 'function urlsDans(texte) {' ' const trouvees = [];' ' for (const mot of texte.split(\" \")) {' ' const debut = mot.indexOf(\"http\");' ' if (debut >= 0) trouvees.push(mot.slice(debut).split(\")\")[0]);' ' }' ' return trouvees;' '}' >> sortie2.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le script du modèle, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const reponse = await demander(messages, echantillon, 80);' ' console.log(\"Réponse : \" + reponse);' ' const urls = urlsDans(reponse);' ' console.log(\"Liens trouvés : \" + urls.length);' ' for (const url of urls) {' ' console.log((autorises.includes(hoteDe(url)) ? \"autorisé \" : \"BLOQUÉ \") + hoteDe(url));' ' }' '}' 'main();' >> sortie2.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire l’assistant du modèle, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' 'async function demander(messages, echantillon, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 200 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"statut \" + res.status);' ' return (await res.json()).choices[0].message.content;' > model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire l’assistant du modèle, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' } catch (erreur) {' ' console.log(\"Échantillon enregistré : aucun modèle n’a répondu (\" + erreur.message + \").\");' ' const liste = [].concat(echantillon);' ' const tour = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' ' return liste[Math.min(tour, liste.length - 1)];' ' }' '}' 'module.exports = { demander };' >> model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lancer avec le modèle
docker exec -w /home/node/project agent node sortie2.jsAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Une sortie possible
| Ligne | Ce qu’elle peut montrer |
|---|---|
| Réponse | Une phrase avec un lien. Les mots changent à chaque exécution |
| Liens trouvés | Souvent 1. Le programme compte ce que la réponse contient |
| autorisé ou BLOQUÉ | autorisé quand l’hôte est listé. BLOQUÉ pour tout autre hôte |
| Échantillon enregistré | Affiché en premier quand aucun modèle n’a répondu. Le script continue avec l’échantillon |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.