Kay van Dijk
Waarom ik Tattlr heb gebouwd
Ik gebruik codeeragents zoals veel mensen dat nu doen. Claude Code, Codex CLI, Gemini CLI, Cursor, en soms OpenClaw. Als ik ze bij elke bestandslezing laat stoppen, ben ik de middag kwijt aan goedkeuringen. Dus laat ik ze los. Op de repo staat "keep YOLO mode." Ik wilde wel een verslag van wat ze echt deden, en een manier om de aanroepen tegen te houden waarvan ik al weet dat ik ze niet wil.
De logs staan al op de machine. Claude schrijft jsonl in zijn projectmap. Codex bewaart rollout-bestanden. Gemini bewaart chats. Nieuwere OpenClaw-versies gebruiken sqlite, oudere jsonl. Cursor heeft agent-transcripts. Ik ging geen extra dienst bouwen die naar huis belt. Tattlr leest die bestanden en print een rap sheet: sessies, tool-aanroepen, welke er verdacht uitzagen, en de ergste momenten. Zit er een geheim in een commando, dan wordt dat gemaskeerd voordat er iets geprint wordt. Er gaat niets de deur uit. Geen account, geen telemetrie. Dat staat in de README omdat ik het andere soort tool zelf niet zou installeren.
Een scan is de makkelijke helft
tattlr zonder argumenten is hetzelfde als tattlr scan. Je kunt het beperken tot de laatste week, tot één agent, of tot projecten waarvan het pad een woord bevat. --json dumpt alles. --card maakt een plaatje met totalen, agentnamen en overtredingen. Geen commando's, geen paden, geen projectnamen, geen geheimen, dus dat kaartje durf ik wel te posten. Op een machine zonder agentlogs vertelt het wat het doet en wat je als eerste kunt draaien.
De regels zijn de gebruikelijke nare lijst. Geheime bestanden en een gedumpte omgeving. Destructieve commando's, code van het net, gegevens die naar buiten gaan, sudo, en wijzigingen aan de hooks van de agent zelf. Elke bevinding heeft een ernst. Punten tellen op tot een oordeel per agent, van een schone lei tot public enemy. tattlr rules print de hele lijst.
.env lezen is soms gewoon werk, dus een regel die elke keer gilt is nutteloos. Niemand heeft een geldige reden om een nepsleutel aan te raken die ik nooit gebruik. tattlr trap plant er een paar. Standaard een duidelijk gemarkeerd profiel in het AWS-credentialsbestand, en een lok-.env onder ~/.config/tattlr/decoy. De sleutels zijn willekeurig, overduidelijk nep, en werken nergens. Een bestand dat al bestaat wordt niet overschreven, en verwijderen haalt alleen bestanden weg die nog de markering bevatten. Daarna is een sessie die het bestand leest of de nepsleutel gebruikt kritiek.
Tegenhouden voordat het draait
Een scan zegt wat er al gebeurd is. tattlr protect hangt dezelfde regels in de eigen hook van de agent, zodat de aanroep gecontroleerd wordt voordat hij draait. Installeren laat een diff zien en vraagt het. Het oude bestand gaat eerst naar een backup. Er komt alleen een eigen regel bij. Tweemaal draaien verandert niets. Zonder terminal en zonder --yes weigert het.
Claude Code, Codex, Cursor, Gemini CLI en OpenClaw krijgen elk hun eigen hook. OpenClaw heeft geen hook-configuratiebestand, dus die is een kleine plugin in de repo. Codex draait een hook niet die je niet één keer hebt vertrouwd. Ask gebruikt de eigen prompt van de agent, als die er een heeft. Zo niet, dan kan tattlr ui de aanroep vasthouden in een inbox op 127.0.0.1. De UI zit in de binary, alleen localhost, met elke run een nieuw logintoken.
Het beleid is een yaml-bestand in ~/.config/tattlr. De modus is observe, protect of strict. Observe logt en blokkeert nooit. Dat is wat ik de eerste week wil. Buiten strict mode laat een crash in tattlr de aanroep door. Ik heb liever dat de agent doorwerkt dan dat mijn eigen bug hem vastzet.
De val is het deel dat ik wilde
Een kanarie lezen mag expres, zodat een agent met een prompt-injectie de val niet merkt. De sessie wordt gemarkeerd, en daarna wordt alles wat die sessie over het netwerk wil sturen geweigerd, ook een fetch die onschuldig oogt. De gif in de repo is precies dat. scripts/demo.sh draait het in een wegwerp-HOME en doet geen netwerkverkeer.
Een paar regels kun je niet uitzetten met het beleid of de allowlist. Een kanarie aanraken. Data naar buiten sturen nadat je er een hebt aangeraakt. Tattlr zelf bewerken: het beleidsbestand, de auditlog, de vallenlijst, de binary, de hooks verwijderen, en de instellingen die hooks uitzetten, zoals disableAllHooks van Claude of hooks = false bij Codex. Wil ik dat veranderen, dan doe ik het zelf in een terminal.
Elke gecontroleerde aanroep gaat naar ~/.local/state/tattlr/audit.jsonl, één JSON-regel, geheimen gemaskeerd. De volgende scan zet er een stuk bij over wat geblokkeerd of gevraagd is.
Ik heb de echte binary's van Claude Code, Codex, Gemini CLI en OpenClaw gedraaid tegen een gescript model op 127.0.0.1. Elk las een kanarie en probeerde die met curl naar een listener op localhost te sturen. De curl werd geblokkeerd. Cursor heb ik niet gedraaid, dus alleen het config-formaat is gecontroleerd. Hoe een echt model reageert als het geblokkeerd wordt, is een andere vraag. Hooks zien alleen wat de agent meldt, en de cloud-agents van Cursor draaien deze gebruikershooks niet. Ik scan de logs nog steeds. Het punt was een verslag, en een stop op de aanroepen waarvan ik al weet dat ik ze niet wil.
Het is geschreven in Go, MIT-licentie, voor macOS en Linux. brew install kaygaaf/tap/tattlr, npm i -g tattlr, go install, of het installatiescript, dat een sha256 controleert. Lees dat script voordat je het in een shell plakt.
