Sandbox en beveiliging
Waarom isolatie hier belangrijk is
Sectie getiteld “Waarom isolatie hier belangrijk is”Een agent leest bestanden, draait shellopdrachten en roept externe API’s aan namens jou. De fout waar je je zorgen over maakt is zelden kwaadwilligheid — het is een agent die een pad verkeerd leest en je werkcheckout herschrijft, of een die een branch pusht die je nog niet klaar had gereviewd. De vraag die het product dus moet beantwoorden is niet “is de agent te vertrouwen”, maar “hoe groot is het impactbereik als hij het mis heeft”.
Control Center antwoordt daarop in lagen, en de lagen zijn bewust onafhankelijk. Ontbreekt er één, dan houden de andere nog stand. Dat is belangrijk, want de buitenste laag — de OS-sandbox — is de enige voorwaardelijke: hij hangt ervan af of de host een backend biedt, en op Windows doet geen enkele host dat.
Wat een agent daadwerkelijk beperkt
Sectie getiteld “Wat een agent daadwerkelijk beperkt”Vijf dingen begrenzen een agentrun op elke host, met of zonder sandbox. Geen ervan is een kernelgrens; alle vijf zijn ze reëel. Waar de host een backend biedt, zit de OS-sandbox daarbovenop, en “Wat de sandbox toevoegt en waar hij stopt” hieronder beschrijft wat dat verandert.
Zijn eigen copy-on-write-worktree. Elk gesprek zet zijn eigen checkout klaar
van elke gekoppelde repo, copy-on-write gekloond via de meegeleverde
rift-native in plaats van byte voor byte gekopieerd, zodat isolatie goedkoop
genoeg is om de
standaard te zijn. De geregistreerde repository die je aan de werkruimte hebt
toegevoegd, is niet de map waarin de agent werkt — twee gesprekken die dezelfde
repo aanraken, zien elkaars wijzigingen niet en geen van beide bewerkt jouw
checkout.
De korrel is het preciseeren waard: de worktree is per gesprek, niet per
agent. Twee agents in hetzelfde gesprek krijgen aparte overlay-werkmappen die
de repos/ van dat gesprek delen — wat precies de bedoeling is, want ze werken
samen aan één wijziging — terwijl hun privémappen per agent gescheiden blijven.
Copy-on-write is de enige backend overal waar rift meelevert: een
bestandssysteem dat niet kan reflinken, of een datadir op een ander volume dan de
repo, laat het klaarzetten mislukken in plaats van terug te vallen. Windows is de
uitzondering — daar is geen copy-on-write-backend, dus is git worktree de
backend, en geen stille verslechtering.
Padbeperking in de bestandstools van de ingebouwde harness. De tools read,
write, edit, apply_patch, search, find en search_files van de harness
herleiden elk pad tegen de werkmap van de run plus gedeelde rootpaden — de map
repos/ van het gesprek, waar de worktrees staan, en de bijlagenmap van de
ruimte als die bestaat — en weigeren alles wat daarbuiten uitkomt. Absolute paden
worden alleen geaccepteerd als ze al binnen lagen. Een symlink binnen de worktree
die naar buiten wijst, wordt ook herleid en geweigerd. Daarom kan een agent zelfs
zonder sandbox in beeld niet met write naar ~/.ssh schrijven.
Het tooloppervlak van de modus. De modus
van een gesprek bepaalt welke tools überhaupt voor de run worden
gematerialiseerd. In de alleen-lezen-modi (plan, review, orchestrate) staan
de muteerende tools simpelweg niet in het register en weigert het
shell-opdrachtbeleid voor die modi elke muteerende opdracht onmiddellijk — git commit,
git push, npm install, docker en de rest — in plaats van erom te vragen. Een tool die er niet is, kan niet worden aangeroepen, en dat is een
sterkere garantie dan een tool die er wel is en achter een poort zit.
De uniforme actiebeveiligingen. Elke muteerende tool verklaart welke
effectklassen hij kan veroorzaken, en de policy-engine herleidt elk daarvan naar
toestaan, eerst vragen of weigeren voordat de aanroep draait. Een prompt zonder
verbonden goedkeurder wordt geweigerd — een vraag die niemand kan beantwoorden
is nooit een ja. Zie Beveiligingen
voor het herleidingsmodel.
Inloggegevens via de broker en een gesaneerde omgeving. Inloggegevens worden per start aangemaakt en ingetrokken wanneer de run afloopt, en het GitHub-token van een run kan lezen maar niet pushen — zie “Pushes lopen via Control Center” hieronder. ACP, de bash van de harness en Claude
Code onder de native wrap krijgen een omgeving waarin de injectievectoren via de
dynamic linker en interpreters (LD_PRELOAD, DYLD_INSERT_LIBRARIES,
NODE_OPTIONS, GIT_SSH_COMMAND en hun verwanten) zijn gestript voordat het
proces start. Claude Code via NoSandboxAdapter erft de omgeving van de host.
Waar die grenzen dunner worden
Sectie getiteld “Waar die grenzen dunner worden”De bestandstools van de harness draaien in het serverproces, niet in een
kindproces, dus geen enkel sandboxprofiel zou ze kunnen beperken, ook niet als er
een zou zijn bevestigd — alleen bash gaat door de gesandboxte
opdrachtuitvoerder. Hun beperking is de padcontrole die hierboven is beschreven,
en die controle is daarom dragend in plaats van een tweede verdedigingslinie.
De bash-tool is de andere plek waar precisie loont. De opdrachten ervan worden
getoetst aan het opdrachtbeleid van de modus en daarna gestart met een gesaneerde
omgeving en de worktree als werkmap. Waar een backend bestaat, wordt die start
omhuld, dus houdt de sandbox een shellopdracht tegen die buiten de worktree wil
schrijven; waar dat niet zo is — Windows, of Linux zonder bwrap en socat — is
het opdrachtbeleid het enige tussen de opdracht en de rest van de schijf. In de
modus chat weigert het beleid hard een vaste lijst (rechtenescalatie,
vernietiging van systeem en schijf, ontsnapping uit containers en namespaces) en
vraagt het om bevestiging bij mutaties aan de remote-status (git push, gh pr create,
het publiceren en installeren van pakketten). Alles wat op geen van
beide lijsten staat, draait gewoon. Pijplijnen, ketens en subshells worden
geparsed en elke deelopdracht wordt apart getoetst, waarbij de meest beperkende
beslissing wint.
Claude Code, wanneer het als adapter wordt gebruikt, is het geval waarin de
sandbox het meeste gewicht draagt. Control Center start het met
--dangerously-skip-permissions, omdat een niet-interactieve claude -p anders
voor altijd blijft hangen op zijn eigen goedkeuringsprompt. De lees-, schrijf- en bewerkingsaanroepen ervan gebeuren binnen zijn eigen proces en Control Center ziet ze nooit. Zijn Bash-aanroepen gaan vóór uitvoering naar een hook op de server die de opdrachtregel indeelt en de bijbehorende regel raadpleegt, en de mcp__*-tools die Control Center serveert gaan zoals altijd langs de poort. Waar een backend bestaat, gebeuren die ongeziene
aanroepen nog steeds binnen de sandboxwrap, dus is het besturingssysteem de grens
eromheen. Waar dat niet zo is, is er niets. Elke adapter vermeldt dit in zijn
afdwingingsmatrix — zie de eerlijkheidsmatrix per
adapter.
Pushes lopen via Control Center
Sectie getiteld “Pushes lopen via Control Center”Er zijn geen machtigingsvlaggen per agent. Of een agent een commit mag maken, mag pushen, een pull request mag openen, naar een tracker mag schrijven of het netwerk mag bereiken, beslissen de beveiligingen — toestaan, eerst vragen of weigeren — en dezelfde regels gelden welke adapter de agent ook draait.
Een push is de actie die moet standhouden, zelfs tegen een agent die een regel
probeert te omzeilen. Daarom wordt hij afgedwongen via wat de agent bezit, niet
via wat hij uitvoert. Met een geconfigureerde GitHub App krijgt een run in een
GitHub-worktree een tot de repo beperkt installatietoken dat inhoud kan lezen en
met pull requests kan werken, maar niet kan pushen. De git-configuratie van de
run herschrijft elke GitHub-push-URL (https://github.com/,
git@github.com:, ssh://git@github.com/) naar de agentrun-gateway, een
loopback-eindpunt op cc_server. De gateway leest welke refs de push bijwerkt,
raadpleegt de regel Naar een remote pushen — een regel die tot branches is
beperkt, zoals “pushes naar main weigeren”, geldt dus ook — en stuurt de push
pas daarna naar GitHub door, met een schrijftoken dat hij daarvoor aanmaakt en
nooit aan de agent geeft. Een geweigerde push verschijnt in de eigen uitvoer van
git:
! [remote rejected] main -> main (Control Center policy: …)De gateway stuurt pushes alleen door naar de eigen repo van de worktree of naar een GitHub-repo die aan de werkruimte is gekoppeld. Fetches gaan met het leestoken rechtstreeks naar GitHub.
Andere shellopdrachten stuiten op een zachtere poort. De bash-tool van de
harness en, via een hook, de Bash-tool van Claude Code delen elke
opdrachtregel in en raadplegen de regel waaronder die valt: git commit,
merge, rebase, cherry-pick, revert en am zijn Een commit maken;
gh pr create is Een pull request openen; gh pr merge, review,
comment en close zijn Een review publiceren of mergen;
gh issue create, edit, comment en close zijn Naar een externe tracker
schrijven; pakketinstallaties zijn Een pakket installeren; curl en
wget zijn Toegang tot het netwerk. Dit herkent de gebruikelijke schrijfwijze
van een actie, en een agent die er een wil verbergen (eval, een scriptbestand,
een directe API-aanroep) kan dat. Pushes zijn er niet van afhankelijk.
In een sandbox wordt geen ticketing-API-sleutel gezet. Agenten schrijven tickets via de MCP-tools van Control Center, die Naar een externe tracker schrijven raadplegen.
De regel Toegang tot het netwerk wordt één keer gelezen, wanneer de run
start. Het netwerk van een sandbox staat aan of uit, dus weigeren zet het uit,
terwijl toestaan en eerst vragen het aan laten; de tools web_fetch en
web_search van de harness blijven bij elke aanroep vragen. Inloggegevens
worden ingetrokken wanneer de run wordt afgebroken, samen met elk schrijftoken
dat de gateway heeft aangemaakt, zodat een token de run niet overleeft.
Wat de sandbox toevoegt en waar hij stopt
Sectie getiteld “Wat de sandbox toevoegt en waar hij stopt”De enum SandboxBackend heeft drie waarden — native (Seatbelt op macOS,
bubblewrap op Linux en WSL2), none en microvm. Het omhullen van
agentopdrachten gebruikt alleen de eerste twee: microvm is de backend met een
omhulde VM voor interactieve terminals en rigs, geen stille upgrade van een
bash-wrap. Als je de native vorm begrijpt, weet je wat je wint waar er een is
bevestigd en wat die je nooit had kunnen geven.
cc_server probeert de host één keer bij het opstarten en voorziet, waar een
backend antwoordt, beide aansluitpunten die het dispatchpad heeft, omdat de
transporten er geen delen. Het eerste (SandboxPort) omhult de Claude Code CLI.
Het tweede (SandboxManager) omhult ACP-adapters en de bash-tool van de
ingebouwde harness. Slechts één ervan voeden zou de andere ongehuld laten
starten, en dat gebeurde vroeger ook. Op een host zonder backend, of onder
--sandbox off, starten ze allemaal ongehuld en zegt de server op zijn
opstartregel in welke situatie je zit. ACP en de bash van de harness saneren de
omgeving nog steeds; Claude Code via NoSandboxAdapter erft de omgeving van de
host.
Eén oppervlak stapt er bewust buiten: de door de server gehoste terminal
accepteert een backend-argument per sessie en cc_server construeert die met de
hostshell als standaard, dus een terminal die je vanuit de app opent, draait
zonder sandbox tenzij een client expliciet native noemt. Dat is een echte shell
op de serverhost, geen agentrun.
Op Linux bouwt bubblewrap een mount-namespace, dus ziet de agent echt alleen
wat erin is gebonden. Op macOS past sandbox-exec een Seatbelt-profiel toe dat
standaard toestaat — een profiel dat standaard alles weigert, maakt een
interactieve shell onbruikbaar, omdat het aantal dyld-, XPC- en Mach-aanroepen om
te enumereren onpraktisch is. Het macOS-profiel begint daarom bij (allow default)
en haalt daar weigeringen uit: het lezen van geheime paden, een
schrijfbeleid dat op weigeren wordt gezet met expliciete toestemmingen
daarbovenop, blokkering van unlink op elk geweigerd pad en zijn voorouders zodat
een rename een schrijfweigering niet kan omzeilen, exec-weigeringen voor altijd
gevaarlijke binaries en voor alles wat naar een beschrijfbare map is gekopieerd,
en een netwerk dat beperkt blijft tot de loopback-proxypoorten plus DNS.
De praktische consequentie: op Linux wordt het beeld dat de agent van het bestandssysteem heeft smaller; op macOS kan de agent nog steeds het grootste deel van de host lezen en worden je geregistreerde checkouts beschermd door expliciete schrijf- en renameweigeringen in plaats van door onzichtbaarheid. Beide zijn isolatie op namespaceniveau, geen kernelbeveiligingsgrens — de backends zeggen dat zelf in hun eigen beschrijving van hun mogelijkheden.
De set bind-mounts is waar de grens tussen agents wordt getrokken.
Overlay-dispatch (Claude Code en ACP) mount maximaal vier paden, per run bepaald:
de overlay-werkmap van de agent read-write, de configuratiemap van de agent
read-only, de gedeelde repos/-map van het gesprek read-write als die bestaat,
en de bijlagenmap van de ruimte read-only als die bestaat. De map van een
zusteragent wordt nooit gemount. De bash-wrap van de harness is smaller: die
bind-mount alleen de werkmap van de run. Dat is dezelfde vorm die de in-process
padcontrole van de harness in software afdwingt, dus de twee komen overeen — maar
alleen de padcontrole dekt de eigen bestandstools van de harness, die nooit een
mount-namespace bereiken.
Je geregistreerde checkouts komen daar bovenop in de deny-write-set, in elke
modus en op alle drie de omhulde paden (Claude Code, ACP, bash van de harness),
en elke regel wordt in zowel zijn letterlijke als zijn symlink-opgeloste spelling
uitgevaardigd. De tweede spelling is geen luxe:
de kernel matcht een weigeringsregel
tegen het opgeloste pad en macOS maakt herleiding het gewone geval in plaats van
het exotische — /tmp is in werkelijkheid /private/tmp, /var/folders/… is in
werkelijkheid /private/var/folders/… — dus een checkout die via een van beide
werd bereikt, zou beschrijfbaar zijn geweest terwijl het profiel het leek te
verbieden.
Netwerk is onder een sandbox een domein-allowlist per sessie, afgedwongen
door in-process loopbackproxies. Uitgaand verkeer is standaard geweigerd: staat
het netwerk uit, dan starten de proxies wel, maar is hun allowlist leeg, dus
wordt elke uitgaande verbinding geweigerd. Staat het aan, dan bouwt de resolver
de allowlist uit een samengestelde basis — LLM-API-providers, githosting,
pakketregisters, de modelcatalogus — plus de domeinen van GitHub, en matcht hij
de doelhost van elke verbinding daartegen. Jokers werken (*.example.com matcht
subdomeinen, niet het hoofddomein). Een aparte lijst die altijd geweigerd wordt,
wint van de allowlist en dekt cloudmetadata-eindpunten en telemetrie-sinks. Wat
niet configureerbaar is, is de lijst zelf: de spec draagt een veld
egressAllowlist voor extra domeinen, maar niets in het dispatchpad vult dat,
dus in de praktijk krijgt elke run de basis plus GitHub en niets meer.
Weigeringen zijn op macOS waarneembaar: een monitor tapt het systeemlogboek af op
Sandbox-weigeringen, filtert bekende ruis, dedupliceert herhaalstormen en brengt
elke weigering naar buiten als een SandboxViolation die noemt wat werd geprobeerd en wat werd tegengehouden. Op Linux parseert het wrap-pad
stderr op EPERM, Operation not permitted en Permission denied en zendt
hetzelfde gebeurtenistype uit — er is geen tap op het systeemlogboek. Die komen
binnen op de eigen gebeurtenisstroom van de run, dus een geweigerde
systeemaanroep verschijnt in het transcript in plaats van alleen in een log. De
telemetrie bestaat alleen zolang een native sandbox het proces daadwerkelijk
omhult — op een host zonder backend valt er niets waar te nemen. Ze staat ook los van de beveiligingen: een schending komt van een geweigerde systeemaanroep, nooit van het opzoeken van een regel.
Bevestigingen komen van de beveiligingen, niet van de sandbox
Sectie getiteld “Bevestigingen komen van de beveiligingen, niet van de sandbox”De sandbox heeft geen goedkeuringspad. Hij start, voert uit, zendt gebeurtenissen uit en breekt af; er is nergens in hem ruimte voor een mens die een vraag beantwoordt. Wanneer je een bevestiging ziet in een ruimte — keur deze push goed, keur deze verwijdering goed — dan is die opgeworpen door de beveiligingsengine (of het opdrachtbeleid van de harness) voordat de actie de runtime überhaupt bereikte.
Deze volgorde is bewust. Een goedkeuring die na de systeemaanroep plaatsvond, zou een auditregistratie zijn, geen poort. Daarom ook zit de fail-closed-regel in de beveiligingen: zonder verbonden goedkeurder wordt de beslissing genomen voordat er iets draait, en die is negatief.
Diagnostiek
Sectie getiteld “Diagnostiek”Een agent diagnosticeren is de enige plek die beschrijft wat je met de hand kunt controleren.
Gerelateerde concepten
Sectie getiteld “Gerelateerde concepten”- Rigs en omhullingen: de stap boven de sandbox — wanneer het werk een hele wegwerp-VM nodig heeft met een kernelgrens, niet een omhuld proces
- Beveiligingen: de beleidslaag boven de ondergrens van het besturingssysteem en waar goedkeuringen echt vandaan komen
- Levenscyclus van agent-dispatch: waar isolatie past in de dispatchstroom
- Modi: hoe een modus het tooloppervlak en het opdrachtbeleid verkleint
- Sandboxbackends: de backends en hun configuratie
Gerelateerde gidsen
Sectie getiteld “Gerelateerde gidsen”- Sandboxbeleid configureren: actieregels, het afdwingen van pushes en de omgang met het bestandssysteem
- Een agent diagnosticeren: wat de diagnostiek allemaal controleert