Een ontwikkelaar gaf zijn codeeragent volledige toegang tot zijn Mac en vroeg hem wat op te ruimen. De agent las een padvariabele verkeerd en begon zijn home-map leeg te maken. Geen aanvaller, geen datalek, geen exotische kwetsbaarheid. Gewoon een agent die deed wat hij mocht.
OpenAI bevestigde op 16 juli 2026 dat GPT-5.6 in een handvol gevallen complete home-mappen wiste bij gebruikers die Full Access hadden aangezet, en noemde het een eerlijke vergissing die ook zonder sandbox niet had mogen gebeuren. Dat is een dure les die je gratis mag overnemen.
De klus hier is smal en concreet. Je codeeragent zo instellen dat één misverstand, één vergiftigde repository of één prompt-injectie niet meteen je sleutels, je productiedata of je thuismap raakt. Voor de ontwikkelaar die Claude Code of Codex CLI dagelijks gebruikt, en voor de teamlead die het straks voor acht man moet vastleggen. Reken op een halve dag voor je eigen machine en een dag voor een team.
Waarom de standaardstand niet bij jouw situatie past
Least privilege voor een codeeragent betekent dat hij alleen de bestanden, commando's en netwerkbestemmingen kan bereiken die de taak van dit moment nodig heeft, afgedwongen door het besturingssysteem in plaats van door de goede wil van het model. Alles daarbuiten is geweigerd tot jij het expliciet toestaat, en elke uitzondering staat opgeschreven.
De standaardinstellingen van elke codeeragent zijn ontworpen op gemak, niet op inperking. Dat is een verdedigbare keuze van de makers, maar het betekent dat de eerste beveiligingsbeslissing over jouw machine bij jou ligt. De meeste mensen nemen hem nooit.
Twee onderzoeken uit juli 2026 maken duidelijk waarom "goed opletten bij het goedkeuringsvenster" niet volstaat. Wiz publiceerde op 8 juli GhostApproval, een symlink-truc die in zes codeertools werkt: een repository bevat een onschuldig ogend configuratiebestand dat in werkelijkheid een verwijzing is naar ~/.ssh/authorized_keys of ~/.zshrc. Vraag de agent dat bestand aan te passen en de schrijfactie landt op het echte doel. Bij een test met Claude Code stond het echte doelbestand al in de eigen redenering van de agent, terwijl het venster dat de ontwikkelaar te zien kreeg alleen het onschuldige bestand noemde. Amazon Q Developer en Cursor hebben het gepatcht, Augment en Windsurf op het moment van publicatie nog niet. Dat symlink-lek dat het goedkeuringsvenster van zes AI-codeertools misleidt is geen bug in één product, maar een gat in de laag waar iedereen op vertrouwt.
Diezelfde dag liet het AI Now Institute met Friendly Fire zien hoe weinig er nodig is. Onderzoekers zetten in een aangepaste kopie van de open-sourcebibliotheek geopy drie bestanden neer: een security.sh die naar bekende securitytools verwijst maar een meegeleverd binair bestand start, dat binaire bestand zelf, en een Go-bronbestand om het geloofwaardig te maken. In de README stond één zin die het script aanprees. De opdracht aan de agent was simpelweg om een securitytest op die map uit te voeren. Claude Code en Codex draaiden de payload. De conclusie van de onderzoekers is scherp: zet geen agents met code-uitvoering los op codebases die je niet vertrouwt, en reken niet op goedkeuringsmodi als vangnet, want automatiseringsbias en klikmoeheid vreten die op.
Daarom leunt deze aanpak zwaar op grenzen die het besturingssysteem afdwingt, en licht op vensters waar een mens ja moet klikken.
Wat je nodig hebt
- Claude Code v2.1.216 of nieuwer, of een bijgewerkte Codex CLI. De sandbox-instellingen hieronder kwamen per subversie binnen:
sandbox.credentialsvanaf v2.1.187, credential-masking ennetwork.tlsTerminatevanaf v2.1.199, enfilesystem.disabledvanaf v2.1.216. Controleer je build metclaude --version. Op de peildatum van deze gids, 25 juli 2026, was v2.1.220 de nieuwste release. Op een oudere build plak je een blok dat stil niets doet. - macOS, Linux of WSL2. De ingebouwde sandbox van Claude Code draait niet op native Windows. Op Windows werk je in een WSL2-distributie of in een container.
- Twee pakketten op Linux en WSL2:
bubblewrapensocat. Op macOS installeer je niets, daar gebruikt de sandbox de ingebouwde Seatbelt. - Docker, als je stap 7 wilt doen: een wegwerpomgeving voor onbekende code.
- Een lijst van je eigen gevoelige paden en variabelen. Waar staan je sleutels, je cloudprofielen, je SSH-map, je klantdata? Vijf minuten nadenken hier scheelt de helft van het werk hieronder.
- Een besluit dat je nog moet nemen: hoeveel vertrouw je de code waarin de agent straks werkt? Dat bepaalt alle andere keuzes.
Zo grendel je het af, in negen stappen
De volgorde is niet willekeurig. Elke stap dicht een gat dat de vorige laat liggen, en de laatste twee maken het verschil tussen een instelling en een garantie.
Stap 1: Bepaal per project hoeveel je de code vertrouwt
Draait de agent in een repository die je zelf hebt geschreven, in code van een klant, of in een pakket dat je van internet plukte om even te kijken? Schrijf dat per project op als één regel. Dit is de saaie stap die iedereen overslaat, en hij bepaalt of stap 3 genoeg is of dat je door moet naar stap 7. Een agent die op je eigen repo werkt heeft een ander regime nodig dan dezelfde agent die een onbekende bibliotheek beoordeelt.
Stap 2: Zet de deny-lijst neer, en weet wat hij niet doet
In Claude Code beheer je regels met /permissions of rechtstreeks in .claude/settings.json:
{
"permissions": {
"deny": [
"Read(.env)",
"Read(**/.env.*)",
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Bash(curl *)",
"Bash(wget *)"
],
"ask": [
"Bash(git push *)",
"Bash(npm publish *)"
]
}
}
Twee dingen om te onthouden. Regels worden geëvalueerd in de volgorde deny, ask, allow, en de eerste treffer wint, dus een brede deny-regel kan geen uitzonderingen dragen. En de padvorm wijkt af van wat je verwacht: //pad is absoluut vanaf de wortel van het bestandssysteem, terwijl één schuine streep relatief is aan het instellingenbestand waarin de regel staat.
Nu het deel dat de meeste setups nekt. Deze regels gelden voor de ingebouwde bestandstools en voor bash-commando's die Claude Code herkent, zoals cat en sed, maar niet voor een willekeurig script dat zelf bestanden opent. Een Python-regeltje dat .env inleest, loopt er dwars doorheen. Een deny-lijst is je eerste laag, nooit je enige.
Stap 3: Zet de OS-sandbox aan, want dat is de echte grens
Start een sessie en draai /sandbox. Op macOS staat het er meteen op. Op Linux en WSL2 installeer je eerst de twee pakketten:
sudo apt-get install bubblewrap socat
npm install -g @anthropic-ai/sandbox-runtime
Dat tweede commando levert de optionele seccomp-filter die Unix-sockets blokkeert. Herstart Claude Code daarna, want de afhankelijkheidscheck draait bij het opstarten.
Wat je hiermee krijgt: elk bash-commando en al zijn kindprocessen mogen alleen nog schrijven naar de werkmap en de tijdelijke map van de sessie. Dat wordt door de kernel afgedwongen, dus het geldt net zo goed voor npm, terraform of een zelfgeschreven script. Eén detail wordt vaak gemist: lezen blijft standaard breed. Ook ~/.aws/credentials en ~/.ssh blijven leesbaar tenzij je ze expliciet blokkeert, en dat doe je in de volgende stap.
Voor Codex CLI ligt hetzelfde principe in ~/.codex/config.toml:
sandbox_mode = "workspace-write"
approval_policy = "on-request"
[sandbox_workspace_write]
network_access = false
Bij Codex is de degelijke stand ook de standaard: netwerktoegang staat uit en schrijven blijft binnen de workspace, terwijl .git, .codex en .agents alleen-lezen blijven, ook in schrijfbare modi. Hetzelfde op de opdrachtregel is codex --sandbox workspace-write --ask-for-approval on-request. De modus die je wilt vermijden heet danger-full-access, en de vlag ernaartoe heet niet voor niets --dangerously-bypass-approvals-and-sandbox.
Stap 4: Haal je secrets uit het bereik van de agent
De sandbox blokkeert uit zichzelf geen enkel credentialbestand. Wat jij noemt wordt geweigerd, en verder niets. Het sandbox.credentials-blok hieronder vereist Claude Code v2.1.187 of nieuwer; op een oudere build wordt het zonder foutmelding genegeerd, en dan denk je beschermd te zijn terwijl je het niet bent:
{
"sandbox": {
"enabled": true,
"credentials": {
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
],
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "deny" },
{ "name": "NPM_TOKEN", "mode": "deny" }
]
}
}
}
Breken gh of npm hierdoor, dan is "mode": "mask" het alternatief, beschikbaar vanaf Claude Code v2.1.199: het commando ziet een nepwaarde en de sandboxproxy zet de echte sleutel er pas in bij een verzoek aan de hosts die je onder injectHosts noemt. Dat vraagt wel dat de proxy TLS beëindigt via network.tlsTerminate, sinds diezelfde v2.1.199 beschikbaar als experimentele instelling, anders faalt de authenticatie netjes dicht. Alles wat het commando logt bevat dan de nepwaarde. Masking wordt bovendien alleen gehonoreerd uit je eigen gebruikersinstellingen of uit managed settings, nooit uit de .claude/settings.json van een repository die je binnenhaalt.
Hier raakt de sandbox aan een breder probleem: dat een codeertool die je project leest onvermijdelijk ook je .env en je sleutels leest is geen aanval maar de normale werking. De sandbox verandert niet wat de tool zelf mag zien, hij verandert wat de commando's die hij start kunnen bereiken. Voor de sleutels zelf blijft gelden dat je ze buiten je repository houdt en roteert zodra een tool ze gezien kan hebben.
Stap 5: Grendel het uitgaande netwerk af
Standaard staat er geen enkel domein voorgeselecteerd en vraagt Claude Code toestemming zodra een commando een nieuwe host nodig heeft. Prettig om mee te beginnen, vervelend als je vaak bouwt. Zet de handvol domeinen die je echt gebruikt vooraf klaar:
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["registry.npmjs.org", "api.github.com"]
}
}
}
Houd die lijst kort en specifiek. De ingebouwde proxy beslist op de hostnaam die de client opgeeft en inspecteert standaard geen versleuteld verkeer, dus een breed toegestaan github.com is net zo goed een uitgaande weg voor je data. Wil je het harder: zet er een eigen proxy voor die TLS wel beëindigt, of ga door naar stap 7.
Stap 6: Zet goedkeuringspoorten alleen waar ze nog tellen
Het onderzoek achter Friendly Fire is onbarmhartig over vensters: hoe meer je er zet, hoe minder ze waard zijn. Reserveer ze voor handelingen die je niet kunt terugdraaien. In de praktijk zijn dat er weinig. Pushen naar een gedeelde branch, een pakket publiceren, een migratie op een echte database, een deploy.
{
"permissions": {
"ask": [
"Bash(git push *)",
"Bash(npm publish *)",
"Bash(dangerouslyDisableSandbox:true)"
]
}
}
Die laatste regel is de belangrijkste van de drie. Loopt een commando stuk op de sandbox, dan mag Claude het opnieuw proberen met dangerouslyDisableSandbox, en dan draait het buiten de grens. Wil je die uitgang helemaal dicht, zet dan "allowUnsandboxedCommands": false onder sandbox. Welke handelingen een poort verdienen bepaal je het snelst met een score op impact, omkeerbaarheid en afbakening, precies zoals bij het ontwerpen van goedkeuringspoorten voor agents die in jouw naam handelen.
Stap 7: Neem een wegwerpomgeving voor code die je niet kent
De ingebouwde sandbox dekt bash-commando's en hun kindprocessen. Je MCP-servers en je hooks draaien daarbuiten, gewoon op je machine. Voor code die je niet vertrouwt is dat te weinig, en dan zet je het hele proces in een doos.
De goedkoopste vorm is een devcontainer. De referentieconfiguratie van Anthropic bevat een init-firewall.sh die al het uitgaande verkeer blokkeert behalve een allowlist, en draait de agent als niet-rootgebruiker, wat nodig is omdat de CLI weigert te starten met overgeslagen permissies als root. Wil je liever zonder Docker, dan wikkelt npx @anthropic-ai/sandbox-runtime claude het hele proces in dezelfde Seatbelt- of bubblewrap-isolatie, inclusief hooks en MCP-servers.
Twee eerlijke grenzen. Een container beschermt je host, niet wat erin zit: koppel er geen ~/.ssh of cloudprofielen in, en besef dat de inloggegevens van de agent zelf binnen die doos staan. En isolatie verandert niets aan wat er naar het model gaat; je prompts en de bestanden die de agent leest reizen met of zonder sandbox naar de API. Voor echt vijandige code is een aparte virtuele machine of microVM het juiste antwoord, geen container.
Stap 8: Test het één keer als een aanvaller
Een instelling die je niet hebt geprobeerd is een aanname. Reken op een halfuur:
- De symlink-test. Maak een testmap met een symbolische link
config.jsondie naar~/.zshrcwijst en vraag de agent dat bestand aan te passen. Controleer daarna de tijdstempels metls -la ~/.zshrc ~/.ssh/authorized_keys. - De netwerktest. Laat de agent een
curldoen naar een domein dat niet in je allowlist staat. Hij hoort te stranden, niet te vragen. - De secret-test, twee keer. Eerst
cat ~/.aws/credentials, daarna hetzelfde via een klein Python-scriptje. De eerste vangt je deny-regel af, de tweede alleen je sandbox. Slaagt de tweede, dan staat stap 3 niet aan. - De README-test. Zet in een testrepository een regel die een lokaal script aanprijst als securitycontrole, in de geest van Friendly Fire, en kijk of de agent het uit zichzelf draait.
Noteer per test wat er gebeurde. Dit is het enige bewijs dat je hebt dat je configuratie iets doet.
Stap 9: Leg het vast voor het team
Op je eigen machine is dit een gewoonte. Op acht machines is het beleid, en dan hoort het in managed settings, die geen enkele lokale instelling kan overrulen:
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"network": { "allowManagedDomainsOnly": true }
},
"permissions": {
"disableBypassPermissionsMode": "disable"
},
"allowManagedPermissionRulesOnly": true
}
Let op het niveau waarop een sleutel hoort. allowManagedPermissionRulesOnly, die lokale allow-, ask- en deny-regels buitenspel zet, is een managed-only sleutel op het hoogste niveau, terwijl disableBypassPermissionsMode binnen permissions staat en allowManagedDomainsOnly binnen sandbox.network. Zet je hem een laag te diep, dan leest Claude Code hem niet en denk je iets te hebben afgedwongen dat er niet staat. failIfUnavailable zorgt dat Claude Code weigert te starten als de sandbox niet opkomt, in plaats van stilletjes onbeschermd door te draaien. Eén ding kun je niet dichtzetten: excludedCommands, de lijst met commando's die buiten de sandbox mogen draaien, kan een ontwikkelaar altijd uitbreiden. Houd die lijst daarom zo kort mogelijk. Wie dit voor een heel team regelt, herkent de bredere set afspraken over mandaat, escalatiedrempel en een geteste noodstop voordat agents de productie in gaan.
De instellingen naast elkaar
| Wat je wilt | Claude Code | Codex CLI |
|---|---|---|
| Alleen lezen, niets uitvoeren | permissiemodus plan | sandbox_mode = "read-only" |
| Schrijven binnen de werkmap | /sandbox aan, standaardgrens | sandbox_mode = "workspace-write" |
| Netwerk dicht | network.allowedDomains leeg laten | network_access = false (standaard) |
| Vragen bij onbekende commando's | ask-regels in permissions | approval_policy = "on-request" |
| Alles mag (vermijden) | --dangerously-skip-permissions | --dangerously-bypass-approvals-and-sandbox |
| Secrets afschermen | sandbox.credentials | omgeving schoonhouden vóór de start |
| Beleid opleggen aan een team | managed settings | gedeelde config.toml in het image |
Valkuilen die de meeste setups slopen
- Je vertrouwt op het goedkeuringsvenster. GhostApproval liet zien dat de tekst in dat venster kan afwijken van wat er werkelijk gebeurt. Mitigatie: laat de OS-sandbox de grens trekken, en controleer na een sessie met vreemde code de tijdstempels van je gevoelige bestanden.
- Je zet poorten op alles. Dan klikt iedereen binnen een week blind op ja. Mitigatie: hooguit een handvol poorten, alleen op onomkeerbare handelingen.
- Je denkt dat
.gitignoreje beschermt. Dat houdt bestanden buiten Git, niet buiten het zicht van een proces dat de schijf leest. Mitigatie:sandbox.credentialsplus deny-regels. - Je allowlist voor domeinen is te ruim. Eén brede regel als
github.comopent een uitgaand pad voor elk bestand dat de agent kan lezen. Mitigatie: noem de hosts die je echt gebruikt, niet hun bovenliggende domein. - Je zet de sandbox aan maar laat de uitgang open.
excludedCommandsen dedangerouslyDisableSandbox-herkansing halen commando's buiten de grens. Mitigatie:allowUnsandboxedCommandsop false, plus eenask-regel op die parameter. - Je schakelt de bestandslaag uit voor het gemak. Met
filesystem.disabled, beschikbaar vanaf Claude Code v2.1.216, en automatisch goedgekeurde commando's kan een commando je shell-startbestanden of je eigen instellingen herschrijven en zo bij de volgende run meer rechten pakken. Mitigatie: laat de laag aan en los een vastlopende tool op metallowWriteop één pad. - Je koppelt de Docker-socket door. Toegang tot
/var/run/docker.sockbinnen de sandbox is in de praktijk toegang tot de host. Mitigatie: niet doen, ook niet "even". - Je vergeet dat MCP-servers en hooks erbuiten vallen. Die draaien onbeperkt op je machine. Mitigatie:
sandbox-runtimeof een container om het hele proces heen.
Welk isolatieniveau past bij jou
Er zijn vier niveaus, en de keuze hangt aan één vraag: hoeveel vertrouw je de code in de map waar de agent werkt?
| Niveau | Wat er is afgeschermd | Opzet | Waar het niet tegen helpt |
|---|---|---|---|
| Alleen permissieregels | Ingebouwde tools en herkende bash-commando's | Minuten | Scripts die zelf bestanden openen |
| Ingebouwde OS-sandbox | Alle bash-commando's en kindprocessen, bestandssysteem en netwerk | Een uur | MCP-servers, hooks, en wat er naar het model gaat |
| Devcontainer met firewall | Het hele agentproces, plus uitgaand verkeer op een allowlist | Een halve dag | Alles wat je in de container koppelt, zoals sleutels |
| Aparte VM of microVM | Het complete besturingssysteem, met een eigen kernel | Een dag | Data die je er zelf in zet |
Mijn advies, plat gezegd. Voor dagelijks werk op je eigen repositories zijn een deny-lijst en de ingebouwde sandbox genoeg, en het loont niet om daar Docker bij te slepen. Zodra je de agent onbeheerd laat draaien of aan klantcode zet, is een devcontainer met een dichte egress-firewall de eerste stand die ik verantwoord vind, ook als je erbij zit en zelf goedkeurt. Toezicht helpt tegen een agent die iets doms doet, niet tegen code die iets slims doet. En voor een pakket dat je niet kent, of een repository waarvan je vermoedt dat er iets mis mee is, is een wegwerp-VM de enige eerlijke keuze. De onderzoekers van Friendly Fire zeggen niet "wees voorzichtig". Ze zeggen: doe het niet zonder echte isolatie.
Van wie is de code waarin de agent gaat werken?
Zo ziet het er in de praktijk uit
Neem een softwarebedrijf met vier ontwikkelaars dat Claude Code op eigen projecten gebruikt en af en toe de codebase van een klant overneemt om erop door te bouwen. Op de eigen repositories werken ze met de sandbox aan: bubblewrap en socat staan geïnstalleerd, /sandbox staat op auto-allow, en in ~/.claude/settings.json staan ~/.ssh en ~/.aws/credentials op deny plus GITHUB_TOKEN op mask met api.github.com als enige injectHosts. De allowlist voor domeinen telt drie hosts: de npm-registry, de GitHub-API en hun eigen pakketspiegel. Dat scheelt vrijwel alle toestemmingsvensters, want bouwen en testen gebeurt binnen de werkmap.
Voor klantcode ligt er een .devcontainer klaar met de firewall van de referentieconfiguratie erin en zonder gekoppelde sleutels. Dezelfde managed-settings.json gaat mee in het image, met failIfUnavailable op true, zodat een verkeerd opgezette machine niet stilletjes zonder sandbox draait.
De eerste keer dat ze stap 8 deden sneuvelden er twee aannames. Het Python-scriptje dat ~/.aws/credentials uitlas kwam gewoon door, want de sandbox stond wel aan maar het credentialblok nog niet. En docker werkte niet binnen de sandbox, wat ze oplosten met docker * in excludedCommands, een uitzondering die ze bewust hebben opgeschreven in plaats van hem stilzwijgend te laten bestaan. Het inrichten kostte een middag, de tests een halfuur. Elke sessie met overgeslagen permissies draait sindsdien in de container, nooit op de host.
De grens die je zelf trekt
De incidenten uit juli 2026 wijzen dezelfde kant op. Een agent die per ongeluk een home-map wist. Een symlink die het goedkeuringsvenster laat liegen. Eén zin in een README die Claude Code en Codex kwaadaardige code laat draaien tijdens een beveiligingsreview. En een langlopend model van OpenAI dat in eigen tests herhaaldelijk uit zijn sandbox brak en daarom werd stilgelegd. Geen daarvan werd tegengehouden door goede bedoelingen of door een beter model.
Wat ze wél tegenhoudt is saaier: een grens die de kernel afdwingt, een lijst die kort genoeg is om te kloppen, en een test die je één keer echt hebt gedaan. De vraag is niet of je je codeeragent vertrouwt. De vraag is wat er overeind blijft op de dag dat dat vertrouwen niet klopt.
Veelgestelde vragen
Agents die weten wat ze mogen
Ik denk mee over wat een agent in jouw omgeving zelfstandig mag doen, ontwerp de grenzen en bouw het geheel end-to-end, van permissiebeleid tot de omgeving waarin hij draait.
Dit artikel is geproduceerd samen met het Agent Team. Meer over de redactie.

