ISMS Copilot Docs

SSE-redactie: Bescherming van LLM-systeemprompts in streamingarchitecturen

Door Better ISMS — februari 2026

Door Better ISMS — februari 2026

Als je een product bouwt op basis van een LLM, is je systeemprompt je productlogica. Wanneer iemand deze extraheert, krijgt diegene je redeneerproces, je veiligheidsmaatregelen, je concurrentievoordeel — alles. En als je antwoorden streamt via Server-Sent Events (wat waarschijnlijk het geval is), is bescherming tegen extractie moeilijker dan je denkt.

Dit bericht beschrijft SSE-redactie, een techniek die we hebben ontwikkeld voor ISMS Copilot om lekken van systeemprompts tijdens het streamen te detecteren en te neutraliseren. We delen de architectuur zodat anderen die LLM-producten bouwen iets soortgelijks kunnen implementeren.

Het Probleem

De meeste LLM-toepassingen streamen antwoorden chunk voor chunk naar de client met behulp van SSE. Elke chunk wordt verzonden zodra deze is gegenereerd. Er is geen stap "bekijk het volledige antwoord voordat het wordt verzonden" — dat zou het doel van streaming tenietdoen.

Dit creëert een beveiligingslek: als een jailbreak-prompt het model overtuigt om zijn systeeminstructies te dumpen, is de inhoud al onderweg naar de client voordat je het kunt stoppen. Tegen de tijd dat je doorhebt wat er gebeurt, heeft de gebruiker al honderden of duizenden tekens van je systeemprompt gezien.

Traditionele outputfiltering werkt hier niet. Je kunt niet het volledige antwoord bufferen (latentie verpest de gebruikerservaring), en je kunt niet elke kleine chunk afzonderlijk controleren (een fragment van 5 woorden lijkt niet op een systeemprompt).

Voor algemene strategieën ter voorkoming van jailbreaks, zie Mitigeer Jailbreaks en Promptinjecties. SSE-redactie is een verdediging-in-de-diepte-maatregel voor wanneer die preventies falen.

De Architectuur

SSE-redactie werkt in vier fasen.

Fase 1 — Fingerprinting. Voordat er een gesprek plaatsvindt, extraheer je een set van fingerprint-zinnen uit je systeemprompt. Dit zijn kenmerkende strings die alleen samen voorkomen als het model zijn instructies reproduceert. Je wilt zinnen die verspreid zijn over verschillende secties van je prompt — roldefinities, namen van beperkingen, gedragsregels. Het aantal fingerprints en de drempelwaarde voor matching zijn instelbare parameters die je geheim houdt.

Fase 2 — Accumulatie en periodieke controle. Terwijl het model chunks streamt, verzamelt een bewaker de volledige respons. Met regelmatige tussenpozen (gemeten in aantal tekens, niet per chunk) controleert deze de verzamelde inhoud aan de hand van de fingerprint-set. Elke chunk controleren zou verspilling zijn — de fingerprints hebben voldoende context nodig om betekenisvol te matchen.

Fase 3 — Foutpropagatie. Wanneer de bewaker voldoende fingerprint-matches detecteert, gooit deze een getypeerde fout (in ons geval SystemPromptLeakError). Hier ligt de subtiliteit. In een streamingarchitectuur heeft de chunk-verwerkingslus meestal een try/catch voor het verwerken van misvormde SSE-data (slechte JSON, onverwachte formaten). Dat generieke catch-blok zal je beveiligingsfout opslokken als je niet oppast. Je hebt een bewakingsclausule nodig die je specifieke fouttype opnieuw gooit voordat de generieke handler wordt uitgevoerd:

catch (e) {
  if (e instanceof Error && e.name === 'SystemPromptLeakError') throw e;
  // generieke foutafhandeling gaat verder voor al het andere
}

Dit is een eenregelige oplossing, maar zonder deze is het hele detectiesysteem inactief. De bewaker detecteert lekken perfect in logs, terwijl het de stream gewoon blijft leveren aan de aanvaller. Dit hebben we op de harde manier geleerd — onze bewaker detecteerde lekken perfect in de logs, maar deed absoluut niets om ze te stoppen.

Fase 4 — Redactie. Zodra de fout zich voortplant naar de streamcontroller, stuurt deze een redact-SSE-event naar de client. De client vervangt alles wat al is weergegeven door een weigeringsbericht. De server vervangt tegelijkertijd de opgeslagen inhoud in de database, zodat het lek niet blijft bestaan.

Wat de gebruiker ziet

De aanvaller ziet kortstondig gedeeltelijke gestreamde inhoud — misschien een paar seconden — waarna de volledige respons wordt vervangen door een generiek weigeringsbericht. De ervaring is: tekst verschijnt, verdwijnt dan en wordt vervangen. De gedeeltelijke inhoud die ze hebben gezien, is incompleet en vermengd met normale antwoordtekst, waardoor het onbetrouwbaar is voor extractie.

Lees meer over hoe weigeringsberichten werken in Weigeringen en Scopebeperkingen behandelen.

Het Probleem met het Catch-blok

Dit verdient nadruk omdat het het soort bug is dat alle tests doorstaat, maar faalt in productie.

Als je async generators gebruikt voor streaming, ziet je SSE-parsingslus er waarschijnlijk zo uit:

for (const line of sseLines) {
  try {
    const data = JSON.parse(line);
    const text = extractText(data);
    await onChunkCallback(text);  // <-- bewaker wordt hier uitgevoerd
    yield text;
  } catch (e) {
    console.error('Fout bij het verwerken van chunk:', e);
    // gaat verder naar de volgende regel
  }
}

De callback bevindt zich binnen het try-blok. Als de bewaker een fout gooit, logt de catch deze als een parseerfout en gaat verder. In ons geval detecteerde de bewaker het lek correct bij elke chunk na de drempelwaarde — de logs toonden SystemPromptLeakError die herhaaldelijk werd geactiveerd — terwijl de stream normaal werd voltooid, het volledige gelekte prompt in de database opsloeg en naar de client stuurde.

Een bijkomende complicatie: dit gedrag is runtime-afhankelijk. In Node.js kunnen fouten van async generators in callbacks zich anders voortplanten dan in Deno. Onze tests slaagden in de Node.js-testomgeving omdat de fout toevallig werd doorgegeven. In Deno-productie werd deze ingeslikt. Als je dit bouwt, test dan in je daadwerkelijke productieruntime, niet alleen in je testrunner.

Ontwerpbeslissingen die het Vermelden Waard Zijn

Waarom fingerprints in plaats van embedding-gelijkenis of exacte matching? Fingerprints zijn snel (string matching), deterministisch (geen modelaanroepen) en robuust tegen parafrasering. Het model parafraseert zelden zijn eigen systeemprompt tijdens een lek — het reproduceert deze letterlijk of bijna letterlijk. Embedding-gelijkenis voegt latentie toe per controle en introduceert risico op valse positieven bij legitieme compliance-inhoud. Exacte substring-matching is te breekbaar (verschillen in witruimte, opmaak).

Waarom periodiek controleren in plaats van elke chunk? Chunks zijn klein (vaak 3–10 tekens). Een enkele chunk is betekenisloos voor detectie. Accumuleren tot een minimumdrempel voordat je controleert, vermindert de rekenlast en zorgt voor voldoende context voor betrouwbare matching.

Waarom niet het volledige antwoord bufferen? Bufferen verpest de streaming-gebruikerservaring. Gebruikers verwachten tekst in realtime te zien verschijnen. Een buffer van 2 seconden is merkbaar; een buffer van een volledige respons van 4000+ tekens is onacceptabel. SSE-redactie behoudt realtime streaming voor 99,99% van de gesprekken en grijpt alleen in tijdens een actief lek.

Waarom ook vervangen in de database? Als je alleen op de client redigeert, blijft de gelekte inhoud server-side bestaan. Iedereen met database-toegang, elke exportfunctie, elk gespreksgeschiedenis-eindpunt zou deze blootleggen.

Wat Dit Niet Oplost

SSE-redactie is een verdediging-in-de-diepte-maatregel, geen wondermiddel.

Het voorkomt niet dat het model probeert te lekken. Dat wordt afgehandeld door de instructies in je systeemprompt zelf (expliciete weigeringsinstructies, beperkingssecties). SSE-redactie is het vangnet voor wanneer die instructies falen — en met voldoende creativiteit slagen jailbreaks af en toe.

Het voorkomt geen lekken die korter zijn dan de detectiedrempel. Als iemand het model zover krijgt dat het één zin van de systeemprompt onthult, bereikt het aantal fingerprints de drempel niet. Dit is met opzet — je maakt een afweging tussen het detecteren van volledige extracties (hoge betrouwbaarheid) en het signaleren van gedeeltelijke vermeldingen (hoog risico op valse positieven).

De aanvaller ziet wel gedeeltelijke inhoud vóór redactie. Gedurende een paar seconden is gestreamde tekst zichtbaar. Dit is inherent aan streamingarchitecturen. De gedeeltelijke inhoud is incompleet en mist structuur, maar het is niet nul blootstelling.

SSE-redactie vult beveiligingsbest practices voor systeemprompts aan, maar vervangt deze niet. Zie Systeemprompts en Bescherm Werkruimte en Aangepaste Instructies voor fundamentele beveiligingsmaatregelen.

Implementatiechecklist

Als je dit voor je eigen LLM-product wilt bouwen:

  1. Extraheer fingerprint-zinnen uit je systeemprompt — kies kenmerkende, sectie-overspannende strings.
  2. Bouw een bewaker die gestreamde inhoud verzamelt en periodiek controleert aan de hand van fingerprints.
  3. Definieer een getypeerde foutklasse met een kenmerkende naam voor lekdetectie.
  4. Controleer elk catch-blok in je streamingpijplijn — voeg hergooi-bewakers toe voor je fouttype.
  5. Behandel in je streamcontroller de fout door een redact-event te verzenden en opgeslagen inhoud te vervangen.
  6. Behandel op de client het redact-event door weergegeven inhoud te vervangen door een weigeringsbericht.
  7. Test in je productieruntime, niet alleen in je testrunner.
  8. Houd je fingerprints, drempelwaarden en controle-intervallen geheim.

Afsluitende Gedachte

Het moeilijkste deel hiervan was niet het detectiealgoritme — het was een eenregelige bug in een catch-blok die het hele systeem stilzwijgend uitschakelde. Beveiliging in streamingarchitecturen faalt op het niveau van de infrastructuur, niet op algoritmeniveau. Als je LLM-beveiligingsfuncties bouwt, traceer dan het volledige foutpad van detectie tot gebruikersgerichte actie, en verifieer dit in je daadwerkelijke productieomgeving.

Voor een breder overzicht van AI-veiligheidspraktijken bij ISMS Copilot, zie Overzicht AI-veiligheid & Verantwoordelijk Gebruik.

Better ISMS bouwt compliance-tools voor informatiebeveiligingsteams. ISMS Copilot is onze AI-assistent voor ISO 27001, SOC 2, GDPR en aanverwante frameworks.

On this page