Rédaction SSE : Protéger les prompts système des LLM dans les architectures en streaming
Par Better ISMS — février 2026
Par Better ISMS — février 2026
Si vous construisez un produit basé sur un LLM, votre prompt système représente votre logique produit. Lorsqu'une personne l'extrait, elle obtient votre raisonnement, vos garde-fous, votre avantage concurrentiel — tout. Et si vous diffusez les réponses en continu via Server-Sent Events (ce que vous faites probablement), se défendre contre l'extraction est plus difficile que vous ne le pensez.
Cet article décrit la rédaction SSE, une technique que nous avons développée pour ISMS Copilot afin de détecter et neutraliser les fuites de prompts système en cours de diffusion. Nous partageons l'architecture afin que d'autres personnes construisant des produits LLM puissent implémenter quelque chose de similaire.
Le problème
La plupart des applications LLM diffusent les réponses au client morceau par morceau en utilisant SSE. Chaque morceau 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 du but du streaming.
Cela crée une faille de sécurité : si un prompt de jailbreak convainc le modèle de divulguer ses instructions système, le contenu est déjà en route vers le client avant que vous puissiez l'arrêter. Au moment où vous réalisez ce qui s'est passé, l'utilisateur a déjà vu des centaines, voire des milliers de caractères de votre prompt système.
Le filtrage traditionnel des sorties ne fonctionne pas ici. Vous ne pouvez pas mettre en tampon l'intégralité de la réponse (la latence tue l'expérience utilisateur), et vous ne pouvez pas vérifier chaque petit morceau isolément (un fragment de 5 mots ne ressemble pas à un prompt système).
Pour des stratégies générales de prévention des jailbreaks, consultez Atténuer les jailbreaks et les injections de prompts. La rédaction SSE est une mesure de défense en profondeur pour les cas où ces préventions échouent.
L'architecture
La rédaction SSE fonctionne en quatre étapes.
Étape 1 — Empreinte. Avant toute conversation, vous extrayez un ensemble de phrases d'empreinte de votre prompt système. Ce sont des chaînes distinctives qui n'apparaîtraient ensemble que si le modèle reproduisait ses instructions. Vous voulez des phrases réparties dans différentes sections de votre prompt — définitions de rôle, noms de contraintes, règles comportementales. 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. Au fur et à mesure que le modèle diffuse les morceaux, un garde accumule le texte complet de la réponse. À intervalles réguliers (mesurés par le nombre de caractères, et non par morceau), il vérifie le contenu accumulé par rapport à l'ensemble d'empreintes. Vérifier chaque morceau serait inefficace — les empreintes ont besoin de suffisamment de contexte environnant pour correspondre de manière significative.
Étape 3 — Propagation des erreurs. Lorsque le garde détecte suffisamment de correspondances d'empreintes, il lève une erreur typée (dans notre cas, SystemPromptLeakError). C'est là que réside la subtilité. Dans une architecture en streaming, la boucle de traitement des morceaux a généralement un bloc try/catch pour gérer les données SSE malformées (JSON incorrect, formats inattendus). Ce bloc catch générique avalera votre erreur de sécurité si vous n'y prenez pas garde. Vous avez besoin d'une clause de garde qui relance votre type d'erreur spécifique avant que le gestionnaire générique ne s'exécute :
catch (e) {
if (e instanceof Error && e.name === 'SystemPromptLeakError') throw e;
// le traitement des erreurs génériques continue pour tout le reste
}C'est une ligne de code, mais sans elle, l'ensemble du système de détection est inerte. Le garde se déclenche, enregistre la détection, et le flux continue joyeusement à livrer votre prompt système à l'attaquant. Nous l'avons appris à nos dépens — notre garde détectait parfaitement les fuites dans les journaux tout en ne faisant absolument rien pour les arrêter.
Étape 4 — Rédaction. Une fois que l'erreur se propage jusqu'au contrôleur de flux, celui-ci envoie un événement SSE de rédaction au client. Le client remplace tout ce qui a déjà été affiché par un message de refus. Le serveur remplace simultanément 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 quelques secondes de contenu — puis l'intégralité de la réponse est remplacée par un message de refus générique. L'expérience est la suivante : du texte apparaît, puis disparaît et est remplacé. Le contenu partiel qu'il a entraperçu est incomplet et mélangé avec du texte de réponse normal, le rendant peu fiable pour une extraction.
Apprenez-en davantage sur le fonctionnement des messages de refus dans Gérer les refus et les limites de portée.
Le problème du bloc catch
Cela mérite d'être souligné car il s'agit du type de bug qui passe tous les tests mais échoue en production.
Si vous utilisez des générateurs asynchrones pour le streaming, votre boucle de parsing SSE ressemble probablement à ceci :
for (const line of sseLines) {
try {
const data = JSON.parse(line);
const text = extractText(data);
await onChunkCallback(text); // <-- le garde s'exécute ici
yield text;
} catch (e) {
console.error('Error parsing chunk:', e);
// passe à la ligne suivante
}
}Le callback se trouve à l'intérieur du bloc try. Si le garde lève une erreur, le catch la journalise comme une erreur de parsing et passe à la suite. Dans notre cas, le garde détectait correctement la fuite sur chaque morceau après le seuil — les journaux montraient que SystemPromptLeakError se déclenchait à plusieurs reprises — tandis que le flux se terminait normalement, enregistrait le prompt complet divulgué 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 de callbacks peuvent se propager différemment que dans Deno. Nos tests passaient dans l'environnement de test Node.js parce que l'erreur se propageait par hasard. En production Deno, elle était avalée. Si vous construisez cela, testez dans votre environnement de production réel, et pas seulement dans votre exécuteur de tests.
Décisions de conception à noter
Pourquoi des empreintes plutôt que la similarité d'embeddings ou la correspondance exacte ? Les empreintes sont rapides (correspondance de chaînes), déterministes (pas d'appels de modèle), et robustes face à la paraphrase. Le modèle paraphrase rarement son propre prompt système lors d'une fuite — il le reproduit mot pour mot ou presque. La similarité d'embeddings ajoute de la latence par 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'espacement, de formatage).
Pourquoi vérifier périodiquement plutôt qu'à chaque morceau ? Les morceaux sont petits (souvent 3 à 10 caractères). Un seul morceau n'a aucun sens pour la détection. Accumuler jusqu'à un seuil minimum avant de vérifier réduit le calcul et garantit suffisamment de contexte pour une correspondance fiable.
Pourquoi ne pas mettre en tampon l'intégralité de la réponse ? La mise en tampon tue l'expérience utilisateur en streaming. Les utilisateurs s'attendent à voir le texte apparaître en temps réel. Un tampon de 2 secondes est perceptible ; mettre en tampon une réponse complète de 4000+ caractères est inacceptable. La rédaction 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 faites que rédiger côté client, le contenu divulgué persiste côté serveur. Toute personne ayant accès à la base de données, toute fonctionnalité d'export, tout point de terminaison d'historique de conversation l'exposerait.
Ce que cela ne résout pas
La rédaction SSE est une mesure de défense en profondeur, pas une solution miracle.
Elle n'empêche pas le modèle de tenter de divulguer des informations. C'est ce que gèrent les instructions de votre propre prompt système (instructions explicites de refus, sections de contraintes). La rédaction SSE est le filet de sécurité pour lorsque ces instructions échouent — et avec suffisamment de créativité, les jailbreaks réussissent occasionnellement.
Elle 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 par conception — vous faites un compromis entre la détection des extractions complètes (haute confiance) et le signalement des mentions partielles (risque élevé de faux positifs).
L'attaquant voit effectivement un contenu partiel avant la rédaction. Pendant quelques secondes, du texte diffusé est visible. Cela est inhérent aux architectures en streaming. Le contenu partiel est incomplet et manque de structure, mais ce n'est pas une exposition nulle.
La rédaction SSE complète mais ne remplace pas les bonnes pratiques de sécurité des prompts système. Consultez Prompts système et Protéger l'espace de travail et les instructions personnalisées pour des mesures de sécurité fondamentales.
Liste de contrôle d'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 plusieurs sections.
- Construisez un garde qui accumule le contenu diffusé et vérifie périodiquement par rapport aux empreintes.
- Définissez une classe d'erreur typée avec un nom distinctif pour la détection de fuites.
- Auditez chaque bloc catch dans votre pipeline de streaming — ajoutez des gardes de relance pour votre type d'erreur.
- Dans votre contrôleur de flux, gérez l'erreur en envoyant un événement de rédaction et en remplaçant le contenu stocké.
- Côté client, gérez l'événement de rédaction en remplaçant le contenu affiché par un message de refus.
- Testez dans votre environnement d'exécution de production, et pas seulement dans votre exécuteur de tests.
- Gardez vos empreintes, seuils et intervalles de vérification secrets.
Réflexion finale
La partie la plus difficile de ceci n'était pas l'algorithme de détection — c'était un bug d'une ligne dans un bloc catch qui désactivait silencieusement l'ensemble du système. La sécurité dans les architectures en streaming échoue au niveau de la plomberie, pas au niveau de l'algorithme. Si vous construisez des fonctionnalités de sécurité LLM, tracez le chemin complet de l'erreur depuis la détection jusqu'à l'action visible par l'utilisateur, et vérifiez-le dans votre environnement de production réel.
Pour une vue d'ensemble des pratiques de sécurité IA chez ISMS Copilot, consultez Aperçu de la sécurité IA et de l'utilisation responsable.
Better ISMS développe des outils de conformité pour les équipes de sécurité de l'information. ISMS Copilot est notre assistant IA pour les normes ISO 27001, SOC 2, RGPD et les cadres connexes.