Redaction SSE : Défendre les prompts système des LLM dans les architectures de streaming
Par Better ISMS — Février 2026
Si vous construisez un produit basé sur un LLM, votre prompt système constitue la logique même de votre produit. Si quelqu'un parvient à l'extraire, il obtient votre raisonnement, vos garde-fous, votre avantage concurrentiel — tout. Et si vous diffusez des réponses via des Server-Sent Events (ce qui est probablement le cas), se défendre contre l'extraction est plus complexe qu'il n'y paraît.
Cet article décrit la redaction SSE, une technique que nous avons conçue pour ISMS Copilot afin de détecter et de neutraliser les fuites de prompt système en plein flux. Nous partageons cette architecture pour que d'autres concepteurs de produits LLM puissent implémenter une solution similaire.
Le Problème
La plupart des applications LLM diffusent les réponses au client segment par segment (chunks) via SSE. Chaque segment est envoyé dès qu'il est généré. Il n'y a pas d'étape de « révision de la réponse complète avant envoi » — cela irait à l'encontre même du principe du streaming.
Cela crée une faille de sécurité : si un prompt de jailbreak convainc le modèle de livrer ses instructions système, le contenu est déjà transmis au client avant que vous ne puissiez l'arrêter. Le temps que vous réalisiez ce qui se passe, l'utilisateur a déjà vu des centaines ou des milliers de caractères de votre prompt système.
Le filtrage de sortie traditionnel ne fonctionne pas ici. Vous ne pouvez pas mettre toute la réponse en mémoire tampon (la latence détruirait l'expérience utilisateur) et vous ne pouvez pas vérifier chaque petit segment de manière isolée (un fragment de 5 mots ne ressemble pas à un prompt système).
Pour des stratégies générales de prévention du jailbreak, consultez Mitigate Jailbreaks and Prompt Injections. La redaction SSE est une mesure de défense en profondeur au cas où ces préventions échoueraient.
L'Architecture
La redaction SSE fonctionne en quatre étapes.
Étape 1 — Empreinte numérique (Fingerprinting). Avant toute conversation, vous extrayez un ensemble de phrases d'empreinte de votre prompt système. Il s'agit de chaînes de caractères distinctives qui n'apparaîtraient ensemble que si le modèle reproduisait ses instructions. Vous devez choisir des phrases réparties dans différentes sections de votre prompt — définitions de rôles, noms de contraintes, règles de comportement. Le nombre d'empreintes et le seuil de correspondance sont des paramètres ajustables que vous gardez secrets.
Étape 2 — Accumulation et vérification périodique. À mesure que le modèle diffuse les segments, un garde (guard) accumule le texte complet de la réponse. À intervalles réguliers (mesurés par le nombre de caractères, pas par segment), il vérifie le contenu accumulé par rapport à l'ensemble d'empreintes. Vérifier chaque segment serait inefficace — les empreintes ont besoin de suffisamment de contexte environnant pour correspondre de manière significative.
Étape 3 — Propagation d'erreur. Lorsque le garde détecte suffisamment de correspondances d'empreintes, il lève une erreur typée (dans notre cas, SystemPromptLeakError). C'est ici que réside toute la subtilité. Dans une architecture de streaming, la boucle de traitement des segments possède généralement un bloc try/catch pour gérer les données SSE malformées (mauvais JSON, formats inattendus). Ce bloc catch générique engloutira votre erreur de sécurité si vous n'y prenez pas garde. Vous avez besoin d'une clause de garde qui renvoie (re-throw) spécifiquement votre type d'erreur avant que le gestionnaire générique ne s'exécute :
catch (e) {
if (e instanceof Error && e.name === 'SystemPromptLeakError') throw e;
// generic error handling continues for everything else
}C'est une simple ligne de code, mais sans elle, tout le système de détection est inerte. Le garde se déclenche, enregistre la détection dans les logs, et le flux continue joyeusement de livrer votre prompt système à l'attaquant. Nous l'avons appris à nos dépens — notre garde détectait parfaitement les fuites dans les logs tout en ne faisant absolument rien pour les stopper.
Étape 4 — Redaction. Une fois que l'erreur se propage jusqu'au contrôleur de flux, celui-ci envoie un événement SSE redact au client. Le client remplace tout ce qui a déjà été affiché par un message de refus. Simultanément, le serveur remplace le contenu stocké dans la base de données afin que la fuite ne persiste pas.
Ce que voit l'utilisateur
L'attaquant voit brièvement un contenu partiel diffusé — peut-être l'équivalent de quelques secondes — puis la réponse entière est remplacée par un refus générique. L'expérience est la suivante : le texte apparaît, puis disparaît et est remplacé. Le contenu partiel entrevu est incomplet et mélangé à du texte de réponse normal, ce qui le rend peu fiable pour une extraction.
Pour en savoir plus sur le fonctionnement des messages de refus, consultez Handle Refusals and Scope Limits.
Le problème du bloc Catch
Ce point mérite d'être souligné car c'est le genre de bug qui passe tous les tests mais échoue en production.
Si vous utilisez des générateurs asynchrones pour le streaming, votre boucle d'analyse SSE ressemble probablement à ceci :
for (const line of sseLines) {
try {
const data = JSON.parse(line);
const text = extractText(data);
await onChunkCallback(text); // <-- guard runs here
yield text;
} catch (e) {
console.error('Error parsing chunk:', e);
// continues to next line
}
}Le rappel (callback) se trouve à l'intérieur du bloc try. Si le garde lève une erreur, le catch l'enregistre comme une erreur d'analyse et continue. Dans notre cas, le garde détectait correctement la fuite sur chaque segment après le seuil — les logs montraient SystemPromptLeakError se déclenchant à répétition — alors que le flux se terminait normalement, enregistrait le prompt fuité complet dans la base de données et l'envoyait au client.
Complication supplémentaire : ce comportement dépend de l'environnement d'exécution. Dans Node.js, les erreurs des générateurs asynchrones issues des callbacks peuvent se propager différemment que dans Deno. Nos tests réussissaient dans l'environnement de test Node.js car l'erreur s'y propageait par hasard. En production sur Deno, elle était étouffée. Si vous construisez cela, testez dans votre environnement de production réel, pas seulement dans votre exécuteur de tests.
Décisions de conception notables
Pourquoi des empreintes plutôt que des plongements lexicaux (embeddings) ou une correspondance exacte ? Les empreintes sont rapides (comparaison de chaînes), déterministes (pas d'appels au modèle) et robustes face à la paraphrase. Le modèle paraphrase rarement son propre prompt système lors d'une fuite — il le reproduit textuellement ou presque. La similarité par embeddings ajoute de la latence à chaque vérification et introduit un risque de faux positifs sur du contenu de conformité légitime. La correspondance exacte de sous-chaînes est trop fragile (différences d'espaces, de formatage).
Pourquoi vérifier périodiquement au lieu de chaque segment ? Les segments sont petits (souvent 3 à 10 caractères). Un seul segment est insignifiant pour la détection. Accumuler jusqu'à un seuil minimum avant la vérification réduit le calcul et garantit suffisamment de contexte pour une correspondance fiable.
Pourquoi ne pas mettre en tampon (buffer) toute la réponse ? La mise en mémoire tampon tue l'expérience du streaming. Les utilisateurs s'attendent à voir le texte apparaître en temps réel. Un tampon de 2 secondes est perceptible ; un tampon pour une réponse complète de plus de 4000 caractères est inacceptable. La redaction SSE préserve le streaming en temps réel pour 99,99 % des conversations et n'intervient que lors d'une fuite active.
Pourquoi remplacer aussi dans la base de données ? Si vous ne pratiquez la rédaction que côté client, le contenu fuité persiste côté serveur. Toute personne ayant accès à la base de données, toute fonction d'exportation ou tout point de terminaison d'historique de conversation l'exposerait.
Ce que cela ne résout pas
La redaction SSE est une mesure de défense en profondeur, pas une solution miracle.
Cela n'empêche pas le modèle de tenter de fuiter. C'est le rôle des instructions de votre prompt système (instructions de refus explicites, sections de contraintes). La redaction SSE est le filet de sécurité lorsque ces instructions échouent — et avec assez de créativité, les jailbreaks réussissent parfois.
Cela n'empêche pas les fuites plus courtes que le seuil de détection. Si quelqu'un amène le modèle à révéler une seule phrase du prompt système, le nombre d'empreintes n'atteindra pas le seuil. C'est volontaire — il s'agit d'un arbitrage entre la capture d'extractions complètes (haute confiance) et le signalement de mentions partielles (risque élevé de faux positifs).
L'attaquant voit effectivement un contenu partiel avant la rédaction. Pendant quelques secondes, le texte diffusé est visible. C'est inhérent aux architectures de streaming. Le contenu partiel est incomplet et manque de structure, mais l'exposition n'est pas nulle.
La redaction SSE complète mais ne remplace pas les meilleures pratiques de sécurité des prompts système. Consultez System Prompts et Protect Workspace and Custom Instructions pour les mesures de sécurité fondamentales.
Liste de contrôle pour l'implémentation
Si vous souhaitez construire cela pour votre propre produit LLM :
Extrayez des phrases d'empreinte de votre prompt système — choisissez des chaînes distinctives couvrant différentes sections.
Construisez un garde qui accumule le contenu diffusé et vérifie périodiquement les empreintes.
Définissez une classe d'erreur typée avec un nom distinctif pour la détection de fuite.
Auditez chaque bloc
catchdans votre pipeline de streaming — ajoutez des clauses de protectionre-throwpour votre type d'erreur.Dans votre contrôleur de flux, gérez l'erreur en envoyant un événement
redactet en remplaçant le contenu stocké.Côté client, gérez l'événement
redacten remplaçant le contenu affiché par un message de refus.Testez dans votre environnement de production, pas seulement dans votre exécuteur de tests.
Gardez vos empreintes, vos seuils et vos intervalles de vérification secrets.
Conclusion
La partie la plus difficile n'a pas été l'algorithme de détection — c'était un bug d'une seule ligne dans un bloc catch qui désactivait silencieusement tout le système. La sécurité dans les architectures de streaming échoue au niveau de la « tuyauterie », pas de l'algorithme. Si vous concevez des fonctionnalités de sécurité LLM, tracez le chemin complet de l'erreur, de la détection jusqu'à l'action côté utilisateur, et vérifiez-le dans votre environnement de production réel.
Pour une vision plus large des pratiques de sécurité IA chez ISMS Copilot, consultez AI Safety & Responsible Use Overview.
Better ISMS conçoit des outils de conformité pour les équipes de sécurité de l'information. ISMS Copilot est notre assistant IA pour ISO 27001, SOC 2, RGPD et les cadres associés.