Naar de inhoud
NLEN
Illustratie: Prompt injection blokkeren via streaming AST-filters

Prompt injection blokkeren via streaming AST-filters

Door Ivo Donker — samengesteld met AI-ondersteuning (Claude & Gemini)

Bij het bouwen van autonome agents en integraties met grote taalmodellen vormt prompt injection een hardnekkig structureel risico. Zodra een model externe data verwerkt via Retrieval-Augmented Generation of binnenkomende payloads leest, kunnen kwaadaardige instructies de controle over de uitvoeringsflow overnemen. Klassieke oplossingen zoals statische regex-filters of post-generation evaluaties schieten tekort zodra applicaties leunen op streaming responses en directe tool execution.

Wanneer een agent gestreamde tokens direct vertaalt naar functie-aanroepen, kan een enkele kwaadaardige payload al schade aanrichten nog voordat de volledige API-respons is afgerond. In dit artikel bekijken we de architectuur van streaming Abstract Syntax Tree (AST) filters. We analyseren hoe incrementele parsers verdachte syntaxis en payload-kapingen isoleren tijdens het generatieproces, zonder dat de time-to-first-token onacceptabel oploopt.

De fundamentele kwetsbaarheid van streaming tool-executie

Klassieke webapplicaties hanteren een strikte scheiding tussen programmacode en gebruikersdata. Binnen LLM-gebaseerde systemen lopen instructies en data echter door dezelfde semantische context. Om de theoretische fundamenten achter dit scheidingsprobleem te begrijpen, biedt het artikel over instructies gescheiden van data als basis van promptbeveiliging een grondige conceptuele introductie. Zodra een model via tool calling of structured JSON-outputs communiceert met externe systemen, kan een indirecte instructie de structuur van het antwoord manipuleren. Als een aanvaller erin slaagt om via opgehaalde websitedata een ontsnapping uit de data-context te forceren, genereert het model valide syntax voor een gevaarlijke tool-aanroep, zoals het overschrijven van configuratiebestanden of het versturen van interne sessietokens naar een extern IP-adres.

Het verdedigingsprobleem escaleert wanneer systemen worden geoptimaliseerd voor minimale latency. Bij streaming responses worden JSON-fragmenten token voor token geëvalueerd en naar de client of runtime doorgestuurd. Een traditionele guardrail controleert de complete payload pas nadat het stop_token is ontvangen. Bij streaming agent-executie leidt dit tot een onmogelijke trade-off: ofwel de latency verdubbelt doordat de stream moet worden gebufferd, ofwel de runtime voert de tool call al deels uit terwijl de payload nog binnenstroomt. Wie dieper wil duiken in hoe applicaties haperen wanneer streams halverwege worden onderbroken, kan de analyse over streaming met tool calls en afgebroken API-calls raadplegen.

Signaal: Toenemende uitbuiting van parser-mismatches tussen LLM-tokenizers en backend-JSON-interpreters.
Actie: Implementeer incrementele streaming validatie direct op het protocolniveau van de modelgateway.

Waarom reguliere expressies en statische guards falen

Veel ontwikkelteams proberen token streams te beveiligen met reguliere expressies op binnenkomende chunks. Deze aanpak is fundamenteel fragiel. Reguliere expressies begrijpen geen recursieve grammatica's en zijn blind voor semantische context. Een aanvaller kan strings opdelen over meerdere tokens, hexadecimale encodings injecteren of JSON-sleutels nesten waardoor regex-patronen geen match detecteren.

Bovendien genereren LLM's tokens in variabele groottes. Een regex die zoekt naar rm -rf mist de injectie wanneer de tokenizer de invoer opknipt in r, m, - en rf over opeenvolgende server-sent events. Tegen de tijd dat een naïeve buffer het commando herkent, is de argumentenbuffer in de runtime al gevuld. In de bredere context van systeemintegraties zagen we vergelijkbare patronen terug in het dossier over agent-security-incidenten en geleerde lessen, waarin ongevalideerde argumentstromen leidden tot ongeautoriseerde data-extractie.

Filtermechanisme Verwerkingstype Latency Overhead Weerstand tegen splitsing Grammaticale context
Reguliere Expressie Chunk-gebaseerd < 1 ms Zeer laag (faalt op token boundaries) Geen
Secundair Guard-model Post-evaluatie 300 - 800 ms Hoog Semantisch, niet syntactisch
Volledige JSON-validatie Gebufferd (einde stream) Gelijk aan totale generatieduur Hoog Volledig statisch
Streaming AST-parser Incrementeel (per token) 1 - 4 ms per token Hoog (node-gebaseerde validatie) Volledig deterministisch

Architectuur van een Streaming AST-filter

Een streaming AST-filter plaatst zich direct tussen de HTTP-transportlaag van de LLM-aanbieder en de runtime-dispatcher van de agent. In plaats van tekstuele string matching bouwt het filter incrementeel een syntaxisboom op terwijl de tokens binnendruppelen. Zodra een nieuw token arriveert, voert de lexer een state transition uit in een Pushdown Automaton (PDA). Hierdoor weet het systeem op elk moment exact in welke syntactische context het token valt: binnen een functienaam, binnen een parameter-sleutel, of diep genest in een stringwaarde.

Deze architectuur maakt structurele validatie mogelijk vóórdat de volledige boom gereed is. Als een model een functienaam probeert aan te roepen die niet voorkomt in het toegekende JSON-schema, of als een parameterwaarde een ontsnappingssequentie bevat die een geneste expressie probeert te injecteren, breekt de parser de verbinding direct af. De stream wordt afgekapt met een TCP RST of een gecontroleerd protocolbericht, nog voordat de applicatielaag de payload kan doorsluizen naar een shell of database.

Voor wie agent-architecturen op eigen servers draait, sluit dit direct aan op de methodieken beschreven in het overzicht over agent-runtime-security voor productieomgevingen, waarin defensieve isolatie op procesniveau centraal staat.

Incrementele JSON- en BNF-state machines in de praktijk

Om te begrijpen hoe een streaming AST werkt, kijken we naar de state transitions van een incrementele JSON-parser. Bij het ontvangen van gestreamde tool calls moet de parser omgaan met onvolledige toestanden (zoals niet-afgesloten aanhalingstekens of ontbrekende sluitaccolades) zonder in een fatale parsing error te schieten. De parser hanteert een overgangstabel die specifieke grammar-states bijhoudt:

// Pseudocode voor een incrementele streaming AST node inspector
interface ASTNode {
  type: 'Program' | 'FunctionCall' | 'Identifier' | 'Literal' | 'Object' | 'Array';
  name?: string;
  value?: any;
  parent?: ASTNode;
  children: ASTNode[];
  isComplete: boolean;
}

class StreamingASTFilter {
  private state: 'IDLE' | 'IN_FUNCTION' | 'IN_PARAMS' | 'IN_STRING' | 'BLOCKED' = 'IDLE';
  private currentPath: string[] = [];
  private tokenBuffer: string = '';

  public processChunk(tokenChunk: string): boolean {
    this.tokenBuffer += tokenChunk;
    
    // Voer lexicale tokenisatie uit op de openstaande buffer
    const transition = this.evaluateNextState(tokenChunk);
    
    if (!this.isValidTransition(transition)) {
      this.state = 'BLOCKED';
      return false; // Signaleer de gateway om de stream onmiddellijk af te breken
    }

    // Inspecteer structurele knooppunten
    if (this.detectInjectionPattern(this.currentPath, tokenChunk)) {
      this.state = 'BLOCKED';
      return false;
    }

    return true;
  }

  private isValidTransition(nextState: string): boolean {
    // Valideer of de transitie binnen de toegestane BNF-grammatica valt
    return nextState !== 'INVALID_SYNTAX';
  }

  private detectInjectionPattern(path: string[], rawToken: string): boolean {
    // Blokkeer pogingen om shell-karakters of controle-instructies in specifieke velden te stoppen
    if (path.includes('arguments') && path.includes('command')) {
      const dangerousPatterns = [';', '&&', '||', '`', '$(', '\n'];
      return dangerousPatterns.some(char => rawToken.includes(char));
    }
    return false;
  }
}

De parser bouwt de boom recursief op. Zodra het knooppunt FunctionCall.arguments.query wordt geopend, kent het filter de veiligheidspolicy voor dat specifieke veld. Een zoekopdracht mag vrije tekst bevatten, maar mag geen geneste structuren bevatten die SQL-tokens simuleren binnen een JSON-waarde. Deze fijnmazige controle per AST-node voorkomt valse positieven in onschuldige tekststromen.

Detectiemechanismen voor syntax-escape en context-kaping

Aanvallers die prompt injection toepassen op agents proberen doorgaans twee dingen te bereiken: instructie-kaping (het model negeert de system prompt) of structuur-kaping (het model forceert een syntax-escape uit de data-string). Bij structuur-kaping injecteert de payload specifieke sequence delimiters, zoals ongeëscapede quotes gevolgd door JSON-structuren.

Een streaming AST-filter herkent dit doordat de state machine een ongeoorloofde toestandswijziging registreert. Als een dataveld plotseling overgaat in een nieuwe sleutel-waarde structuur zonder dat de juiste ontsnappingskarakters aanwezig zijn in de lexer-context, signaleert het filter een anomalie. Het filter inspecteert niet alleen de statische string, maar controleert of de diepte van de AST overeenkomt met het verwachte functieschema.

Om dergelijke kwetsbaarheden meetbaar in kaart te brengen en te valideren hoe effectief een filter presteert tegen geavanceerde aanvallen, is het raadzaam om de methodologie te bestuderen op indirecte prompt injection weerbaarheid systematisch meten. Zonder gestandaardiseerde evaluatiesets blijft het beveiligen van parser-logica immers gissen.

Formele grammatica's en determinisme bij agentic pipelines

Naast het blokkeren van bekende exploitpatronen helpt een AST-filter ook om structurele chaos bij codegeneratie te voorkomen. Wanneer een agent autonoom scripts produceert of patchbestanden aanlevert, leidt een hallucinerende of geïnjecteerde afsluiting vaak tot corrupte bestanden. Door tijdens de inferentiestroom formele grammatica's (zoals GBNF of JSON Schema regexes) direct af te dwingen op de logits, ontstaat een wiskundig sluitend korset.

Hoe je dergelijke formele beperkingen inricht om te garanderen dat gegenereerde codeblokken syntactisch foutloos blijven, staat uitvoerig toegelicht in de gids over determinisme afdwingen bij agentic code-generatie. Dit determinisme vermindert de kans dat een injectieaanval het model verleidt tot het produceren van onverwachte besturingscommando's aanzienlijk.

Latency, TTFT en resource-impact in edge-omgevingen

Een van de grootste bezwaren tegen zware beveiligingslagen rondom LLM's is de toename van de Time-To-First-Token (TTFT) en het totale CPU-verbruik. Het controleren van een gestreamde respons mag geen bottleneck vormen in de latency-gevoelige inferentieketen. Gelukkig zijn deterministische AST-state machines computationeel extreem licht vergeleken met het draaien van secundaire neurale netwerken voor content moderation.

Infrastructuur Geheugen per stream Parser-overhead per token Max throughput per core
Node.js Worker ~ 120 KB 1.2 - 2.8 ms ~ 1.400 req/s
Rust / WASM (Cloudflare Worker) ~ 18 KB 0.08 - 0.25 ms ~ 18.000 req/s
Go Reverse Proxy ~ 32 KB 0.15 - 0.40 ms ~ 12.500 req/s
Python (Asyncio ASGI) ~ 450 KB 3.5 - 7.0 ms ~ 450 req/s

De tabel toont aan dat implementaties in Rust of Go een verwaarloosbare impact hebben op de totale responsietijd. Omdat de parser incrementeel werkt en de status in het geheugen vasthoudt via een compacte pointer-structuur, is er geen herhaalde allocatie nodig. Tokens worden direct na inspectie doorgestuurd naar de client-socket, tenzij het filter een blokkade activeert.

Integratie met runtime sandboxing en permissiemodellen

Een streaming AST-filter vormt de eerste verdedigingslinie, maar lost niet elk risico op. Als een aanval syntactisch valide argumenten genereert die binnen het schema passen maar semantisch kwaadaardig zijn (bijvoorbeeld het uitlezen van een bestand dat binnen de geldige bestandsstructuur valt maar gevoelige data bevat), moet de verdediging terugvallen op de applicatieruntime.

De meest effectieve architectuur combineert het streaming filter met strikte containerisatie en procesisolatie. Zodra het AST-filter de syntaxis goedkeurt en het JSON-payload valideert, wordt de tool uitgevoerd binnen een kortstondige sandbox met minimale rechten. In het praktijkdossier over sandboxing van LLM-tools met Docker isolatie wordt stap voor stap uitgewerkt hoe netwerkbeperkingen en read-only mounts voorkomen dat een doorgebroken payload alsnog laterale schade toebrengt aan het hostsysteem.

Implementatievalkuilen en blinde vlekken

Bij het uitrollen van streaming AST-inspectie lopen ontwikkelaars tegen een aantal specifieke technische uitdagingen aan. We zetten de belangrijkste valkuilen op een rij:

Ten eerste: Unicode- en multibyte-splitsing. Een LLM kan een multibyte UTF-8 karakter opsplitsen over twee afzonderlijke streaming chunks. Als de lexer dit karakter probeert te decoderen op de grens van de buffer, kan de parser crashen of het token verkeerd interpreteren. Een robuuste lexer moet incomplete byte-sequences vasthouden in een sliding window totdat de codepoint compleet is.

Ten tweede: Syntactische dubbelzinnigheid in JSON-streaming. Zolang een string niet is afgesloten met een quote, kan een parser niet met zekerheid vaststellen of de string compleet is. Pogingen om tussentijdse evaluaties uit te voeren op onvolledige strings vereisen heuristieken die tolerant zijn voor ontbrekende terminators, zonder dat dit de validatiekracht ondermijnt.

Ten derde: State exhaustie door geheugenaanvallen. Als een model door een aanval wordt gedwongen om oneindig lange JSON-sleutels of diep geneste objecten te genereren, kan de parser bezwijken onder een resource exhaustion aanval. Een streaming AST-filter moet harde limieten handhaven op boomdiepte (maximaal 8 niveaus) en individuele tokenlengte (maximaal 4.096 bytes per stringwaarde).

De balans tussen deterministische inspectie en flexibiliteit

Streaming AST-filters bieden een wiskundig verifieerbare en deterministische methode om gestreamde payloads van taalmodellen te controleren. Door de controle te verplaatsen van trage, onvoorspelbare LLM-guardrails naar snelle, incrementele parsers op protocolniveau, blijft de gebruikerservaring snel terwijl gevaarlijke tool execution direct in de kiem wordt gesmoord.

Voor wie serieuze agentic systemen ontwikkelt, is het filteren van streams geen optionele luxe meer maar een fundamenteel onderdeel van de gateway-infrastructuur. In combinatie met formele JSON-schema's, runtime-sandboxes en strikte permissiemodellen vormt het streaming AST-filter de noodzakelijke buffer tussen de onvoorspelbaarheid van probabilistische modellen en de harde veiligheidseisen van moderne productiesystemen.