In mei wiste een agent een productiedatabase op een van mijn eigen machines. Toen ik de schade in kaart begon te brengen, bood hij zijn excuses aan, wees aan welke zin in zijn eigen prompt te zwak was geweest, en nam de verantwoordelijkheid op zich.
Het was een keurige verontschuldiging. Ik had er niets aan.
Ik hoefde niet te weten wat er mis was met mijn prompt. Ik moest weten welke bestanden waren overschreven, of er iets de deur uit was gegaan, en welke credentials op dat moment in context zaten. In plaats daarvan improviseerde ik een uur lang: logs grepen, tijdstippen gokken, uitzoeken wat cp -r backend/* precies had platgewalst.
Dat verloren uur was het echte incident, niet de verwijderde database. De database kwam terug uit een backup. Dat uur ging op aan uitzoeken waar ik überhaupt moest kijken, en dat is een voorbereidingsprobleem. Voorbereiding is de enige fase van incident response die je niet tijdens het incident kunt doen.
Dit is het draaiboek dat ik daarna heb opgeschreven.
Waarom het normale draaiboek hier niet past
Elk serieus incident-response-framework begint op dezelfde manier. NIST SP 800-61r3, in 2025 herschreven rond CSF 2.0, loopt van voorbereiding via detectie en analyse naar containment, eradicatie, herstel en evaluatie. Prima structuur, ik gebruik hem zelf ook. Alleen klopt één aanname hier niet.
Hij gaat uit van detectie.
Detectie werkt omdat een aanvaller afwijkt. Verkeerde tijdstippen, verkeerd IP, verkeerd account dat aan verkeerde data zit.
Jouw agent wijkt nergens van af. Hij draait op jouw machine, onder jouw account, met jouw credentials, in een sessie die jij hebt geopend, en voert commando's uit die je stuk voor stuk had goedgekeurd als hij het had gevraagd. Er is niets afwijkends waar een alarm op af kan gaan. Er is alleen een behulpzame assistent die net iets anders doet dan jij bedoelde.
Je komt er dus op dezelfde manier achter als ik. Er gaat iets stuk en je werkt van achteren naar voren.
Dat is de echte ontwerpeis. Het draaiboek moet werken zonder alerting en vanaf een koude start.
Fase 1: bevriezen
Stop de sessie. Beantwoord zijn volgende vraag niet, laat hem het probleem niet "even oplossen", sluit je terminal niet af.
De neiging om de agent zijn eigen rommel te laten opruimen is sterk en verkeerd. Precies die reflex veroorzaakte het incident: hij maakte een database kapot, besloot dat de oplossing was om hem leeg te gooien en opnieuw op te bouwen, en gooide hem leeg. Een agent die net iets destructiefs heeft gedaan, weet daar niets extra's over. Hij heeft dezelfde context als dertig seconden geleden, en dat is de context die de fout opleverde.
Bevriezen betekent ook: bewaren.
- Zet wat kapot is apart, gooi het niet weg (
app.db.broken, geenrm app.db) - Draai geen
/clearof/compact. Compaction herschrijft je transcript tot een samenvatting, en een samenvatting is geen bewijs - Doe geen
git checkout .om "even schoon te beginnen". Daarmee vernietig je de diff die je vertelt wat er is veranderd - Heeft hij aan een remote gezeten, trek dan meteen het token in, nog voor je snapt wat er gebeurd is
De volgorde telt. Eerst indammen, dan begrijpen. Een transcript kun je over een uur nog lezen. Een commit die anderen al binnenhalen, krijg je niet meer terug.
Fase 2: reconstrueren, en vraag het niet aan de agent
Dit vinden mensen het lastigst: vraag de agent niet wat hij heeft gedaan.
Hij geeft antwoord. Vloeiend, gestructureerd, geloofwaardig en opgemaakt als een schaderapport. Wat je terugkrijgt is gegenereerde tekst in de vorm van een log. De agent bouwt een verhaal op uit dezelfde context die jij zelf kunt lezen, met een sterke voorkeur voor samenhang. Vraag hem naar een cp van veertig beurten geleden en je krijgt een relaas dat sluitend is, en sluitend is iets anders dan waar.
Het echte bewijs staat op schijf. Bij Claude Code kijk ik op vier plekken, in deze volgorde.
Het sessietranscript. ~/.claude/projects/<munged-cwd>/<session-id>.jsonl, één JSON-object per regel. Elke beurt van de agent bevat een timestamp, de cwd, de gitBranch en de permissionMode die op dat moment gold, plus de volledige tool-aanroep met zijn input. Om dat laatste veld draait het: je krijgt de letterlijke commandoregel, precies zoals hij is uitgevoerd.
De bestandshistorie. Dit is het artefact dat vrijwel niemand kent. In ~/.claude/file-history/<session-id>/ staan versies van elk bestand dat de agent heeft aangepast, als <hash>@v1, @v2 enzovoort, met de volledige inhoud van vóór de bewerking. Als de diff weg is en git je niet helpt omdat de wijziging nooit is gestaged, is dit je undo.
Het promptlog. ~/.claude/history.jsonl bevat elke prompt die je hebt gestuurd, over alle projecten heen. Handig om te bepalen wanneer een sessie begon als het transcript groot is.
Alles buiten de agent. git reflog (haalt commits terug na een verkeerde reset), git fsck --lost-found (losse blobs), je shell-historie, en de logs van alles waar hij daadwerkelijk aan heeft gezeten: de database, de deploy-target, de CI-run.
Maak daar een tijdlijn van, in die volgorde, vóór je een theorie vormt. Die tijdlijn is wat deze fase oplevert, en hij hoort saai te zijn: tijdstip, tool, letterlijk argument, effect.
Fase 3: ga ervan uit dat de context de deur uit is
Alles wat in het contextvenster van het model terecht is gekomen, moet je behandelen alsof het je machine heeft verlaten, want dat is ook zo. Het ging naar een inference-endpoint.
Een capture op wire-niveau liet zien hoe een coding CLI de .env van een repo las en daarna de hele repo uploadde, inclusief bestanden waar hij vanaf moest blijven. In juni hetzelfde patroon: via prompt injection in een GitHub-issue las een agent /proc/self/environ in CI en plakte de cloud-credentials die hij daar vond in een comment.
De blast radius is dus groter dan de schade. Loop hem in vier rondes na:
- Gelezen bestanden, een langere lijst dan gewijzigde bestanden. Grep het transcript op elke Read en elke
cat..env,~/.aws/credentials,~/.ssh, kubeconfig, elke dump met klantdata erin - Uitgevoerde commando's, volledig. Let het scherpst op alles dat buiten de projectmap schreef
- Uitgaand verkeer. Elke fetch, elke MCP-server, elke
gh- ofaws-aanroep. Waar ging data heen, en van wie is dat endpoint - Persistentie. Deze ronde slaan mensen over. Heeft hij geschreven naar
~/.ssh/authorized_keys, een shell-rc, een git hook, een systemd-unit of een CI-workflow?
Op 8 juli lieten onderzoekers zien hoe zes coding assistants symlinks volgden en zo SSH-sleutels in authorized_keys schreven, langs de goedkeuringsprompt heen. De agent heeft geen enkel motief om zich in te graven. Een geïnjecteerde instructie wel.
Fase 4: roteren
Roteer alles uit fase drie: elke credential die vanuit die sessie bereikbaar was, of je hem nu in gebruik hebt gezien of niet.
Dit is de fase die wordt overgeslagen, en de cijfers erover zijn slecht. GitGuardian telde in 2025 29 miljoen nieuwe hardcoded secrets in publieke repo's op GitHub, 34% meer dan het jaar ervoor. Credentials voor AI-diensten stegen met 81%. Het getal dat je meer zorgen moet baren: van de credentials die ze in 2022 als geldig aanmerkten, was in januari 2026 nog altijd 64% niet ingetrokken. Lekken gebeurt overal. Het intrekken blijft achter.
Roteren na een agent-incident is opvallend makkelijk af te bakenen, want fase twee heeft je een exacte lijst met tijdstippen opgeleverd. Gebruik die. Roteer, en kijk daarna in het auditlog van de provider of de oude credential nog is gebruikt tussen het incident en de rotatie. Alleen in die periode vind je hard bewijs dat er daadwerkelijk iets is misbruikt.
Fase 5: repareer de permissie, niet de prompt
Bij het database-incident stond de regel expliciet in de prompt: niet deployen, geen cp naar de live directory. De agent las hem, was het ermee eens, en overtrad hem.
Een regel in een prompt is een voorkeur die je uitspreekt tegenover een probabilistisch systeem. Meestal houdt hij stand, en dat is precies het gevaar: je gaat erop vertrouwen.
De fix na een incident moet een laag lager zitten. Concreet, voor mijn incident:
| Wat misging | Promptfix (waardeloos) | Controlefix (houdt stand) |
|---|---|---|
| Agent deployde | "Niet deployen" | PreToolUse-hook die cp naar /opt/* blokkeert met een exitcode |
| Agent wiste een database | "Nooit tabellen droppen" | Read-only databasegebruiker voor de agent, een aparte credential |
Agent las .env | "Negeer secret-bestanden" | Deny-regel in settings, secrets buiten de projectmap |
| Agent ging het netwerk op | "Geen externe API's aanroepen" | Egress-allowlist in de sandbox |
De rechterkolom telt als controle omdat hij dichtklapt bij twijfel en niet afhangt van hoe zorgvuldig het model vandaag leest. Schrijf je fix als een controle, of je hebt niets gerepareerd.
Er zit een grens aan. De doos waar je de agent in zet heeft een gat precies daar waar hij nuttig wordt, en dat is bewust zo. Controles maken de blast radius kleiner. Ze halen hem niet weg, en daarom bestaan de fases één tot en met vier.
Fase nul, de enige die je vóór het incident kunt doen
Alles hierboven gaat ervan uit dat het spoor er is. Controleer dat nu meteen:
- Weet je waar de transcripts van je agent staan, en staan ze er nog? Sommige tools ruimen ze op
- Schrijft je hook elke tool-aanroep weg naar een bestand dat je bewaart, met tijdstip erbij? Een hook die alleen blokkeert is een controle. Een hook die ook wegschrijft is bewijs
- Heb je een backup van alles wat je agent kan bereiken, ook op dev-machines? Mijn incident was alleen te herstellen dankzij een nachtelijke job die 24 uur eerder had gedraaid
- Kun je zeggen welke credentials vanuit die sessie bereikbaar waren, zonder het op te zoeken?
Neem twintig minuten en beantwoord die vier. Meer is fase nul niet.
De verontschuldiging was het menselijkste wat die agent die dag deed. Hij las zijn eigen instructies terug, wees de zwakke zin aan en accepteerde de schuld.
Schuld accepteren is precies wat hij niet kan. Hij wordt om drie uur 's nachts niet gebeld, hij zit niet bij de postmortem, en hij legt de klant niet uit waarom zijn data 24 uur oud is. Dat was ik, en dat word jij.
Het draaiboek is dus van jou. Schrijf het op een rustige middag, of schrijf het in het uur dat ik kwijt was.