Gå til innholdet

Sandkasse og sikkerhet

En agent leser filer, kjører skallkommandoer og kaller eksterne API-er på dine vegne. Feilen du bryr deg om, er sjelden vond vilje — det er en agent som leser feil sti og skriver om arbeidskopien din, eller en som pusher en gren du ikke hadde rukket å gjennomgå. Så spørsmålet produktet må svare på, er ikke «er agenten til å stole på», men «hvor stor er skadradiusen når den tar feil».

Control Center svarer på det i lag, og lagene er uavhengige med vilje. Mangler ett, holder de andre fortsatt. Det betyr noe, for det ytterste laget — OS-sandkassen — er det eneste som er betinget: det avhenger av at verten tilbyr en backend, og på Windows gjør ingen det.

Fem ting begrenser en agentkjøring på alle verter, med eller uten sandkasse. Ingen av dem er en kjernegrense; alle er reelle. Der verten tilbyr en backend, ligger OS-sandkassen over dem, og «Hva sandkassen tilfører, og hvor den stopper» nedenfor dekker hva det endrer.

Sin egen copy-on-write-worktree. Hver samtale klargjør sin egen arbeidskopi av hvert tilkoblede repo, klonet copy-on-write gjennom den medfølgende, native rift-komponenten i stedet for kopiert byte for byte, så isolasjonen er billig nok til å være standard. Det registrerte repoet du la til i arbeidsområdet, er ikke katalogen agenten jobber i — to samtaler som berører samme repo, kan ikke se hverandres endringer, og ingen av dem redigerer arbeidskopien din.

Kornetheten er verdt å være presis om: worktreet er per samtale, ikke per agent. To agenter i samme samtale får hver sin overlay-arbeidskatalog som deler samtalens repos/ — det er hele poenget, siden de samarbeider om én endring — mens deres private agentkataloger holdes atskilt. Copy-on-write er den eneste backenden overalt der rift leveres: et filsystem som ikke støtter reflink, eller en datakatalog på et annet volum enn repoet, feiler klargjøringen i stedet for å falle tilbake. Windows er unntaket — der finnes ingen copy-on-write-backend, så git worktree er backenden, ikke en stille forringelse.

Stibegrensning i den innebygde harnessens filverktøy. Harnessens read, write, edit, apply_patch, search, find og search_files løser opp hver sti mot kjøringens arbeidskatalog pluss delte røtter — samtalens repos/-katalog, der worktreene ligger, og områdets vedleggskatalog når den finnes — og nekter alt som ender utenfor. Absolutte stier godtas bare hvis de allerede lå innenfor. En symbolsk lenke inne i worktreet som peker ut av det, blir også løst opp og avvist. Det er derfor en agent ikke kan bruke write mot ~/.ssh, selv uten en sandkasse i bildet.

Modusens verktøyflate. En samtales modus avgjør hvilke verktøy som i det hele tatt materialiseres for kjøringen. I skrivebeskyttede moduser (plan, review, orchestrate) er de muterende verktøyene rett og slett ikke i registeret, og kommandopolicyen for disse modusene nekter hver muterende kommando kontant — git commit, git push, npm install, docker og resten — i stedet for å spørre om dem. Et verktøy som ikke finnes, kan ikke kalles, og det er en sterkere garanti enn et verktøy som finnes og er underlagt en sperre.

De enhetlige tillatelsesreglene for handlinger. Hvert muterende verktøy deklarerer hvilke effektklasser det kan produsere, og policy-motoren avgjør hver av dem til tillat, spør først eller nekt før kallet kjører. Et prompt uten tilkoblet godkjenner løses til nektet — et spørsmål ingen kan svare på, er aldri et ja. Se Tillatelsesregler for hvordan regler løses.

Formidlet legitimasjon og et sanert miljø. Legitimasjon utstedes ved hver oppstart og tilbakekalles når kjøringen avsluttes, og GitHub-tokenet en kjøring har, kan lese, men ikke pushe — se «Pusher går gjennom Control Center» nedenfor. ACP, harnessens bash og Claude Code under den native innpakningen får et miljø som er tømt for injeksjonsvektorene fra dynamiske lenkelastere og tolker (LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS, GIT_SSH_COMMAND og deres slektninger) før prosessen starter. Claude Code gjennom NoSandboxAdapter arver vertens miljø.

Harnessens filverktøy kjører i serverprosessen, ikke i en barneprosess, så ingen sandkasseprofil kunne begrense dem, selv om en var festet til dem — bare bash går gjennom den sandkasseinnpakkede kommandokjøreren. Begrensningen deres er stikontrollen beskrevet ovenfor, og den kontrollen er derfor bærende og ikke en ekstra forsvarslinje.

bash-verktøyet er det andre stedet der det er verdt å være presis. Kommandoene dets vurderes mot modusens kommandopolicy og startes deretter med et sanert miljø og worktreet som arbeidskatalog. Der en backend finnes, pakkes den oppstarten inn, så sandkassen er det som hindrer en skallkommando i å skrive utenfor worktreet; der ingen finnes — Windows, eller Linux uten bwrap og socat — er kommandopolicyen det eneste som står mellom kommandoen og resten av disken. I chat-modus nekter policyen kontant en fast liste (privilegieheving, system- og diskødeleggelse, container- og navneromsflukt) og spør ved endringer i ekstern tilstand (git push, gh pr create, publisering og installasjon av pakker). Alt som ikke står på noen av listene, kjører. Pipeliner, kjeder og underskall parses, og hver underkommando vurderes separat, med den mest restriktive beslutningen som vinner.

Claude Code, når den brukes som adapter, er tilfellet der sandkassen veier tyngst. Control Center starter den med --dangerously-skip-permissions, fordi en ikke-interaktiv claude -p ellers ville blokkere for alltid på sin egen godkjenningsforespørsel. Lese-, skrive- og redigeringskallene dens skjer inne i dens egen prosess, og Control Center ser dem aldri. Bash-kallene dens sendes, før de kjører, til en hook på serveren som klassifiserer kommandolinjen og spør den tilhørende regelen, og mcp__*-verktøyene Control Center betjener, går gjennom sperren som vanlig. Der en backend finnes, skjer disse usynlige kallene fortsatt inne i sandkasseinnpakningen, så OS-et er grensen rundt dem. Der ingen finnes, er ingenting det. Hver adapter oppgir dette i sin håndhevingsmatrise — se adapter-ærlighetsmatrisen.

Det finnes ingen tillatelsesflagg per agent. Om en agent får opprette en commit, pushe, åpne en pull request, skrive til et oppfølgingssystem eller nå nettverket, avgjøres av tillatelsesreglene — tillat, spør først eller nekt — og de samme reglene gjelder uansett hvilken adapter som kjører agenten.

En push er handlingen som må holde selv mot en agent som prøver å omgå en regel, så den håndheves gjennom det agenten har, ikke det den kjører. Med en GitHub App konfigurert får en kjøring i en GitHub-worktree et repo-begrenset installasjonstoken som kan lese innhold og jobbe med pull requests, men ikke pushe. Kjøringens git-konfigurasjon skriver om hver push-URL til GitHub (https://github.com/, git@github.com:, ssh://git@github.com/) til agentkjøringsgatewayen, et loopback-endepunkt på cc_server. Gatewayen leser hvilke refs pushen oppdaterer, spør regelen Push til en remote — så en regel som er begrenset til grener, for eksempel «nekt push til main», gjelder — og sender først da pushen videre til GitHub med et skrivetoken den utsteder for formålet og aldri gir til agenten. En avvist push vises i gits egen utdata:

! [remote rejected] main -> main (Control Center policy: …)

Gatewayen sender bare pusher videre til worktreens eget repo eller til et GitHub-repo som er koblet til arbeidsområdet. Fetch går rett til GitHub med lesetokenet.

Andre skallkommandoer møter en mykere port. Harnessens bash-verktøy og, gjennom en hook, Claude Codes Bash-verktøy klassifiserer hver kommandolinje og spør regelen den hører inn under: git commit, merge, rebase, cherry-pick, revert og am er Opprett en commit; gh pr create er Åpne en pull request; gh pr merge, review, comment og close er Publiser en gjennomgang eller sammenslåing; gh issue create, edit, comment og close er Skriv til et eksternt oppfølgingssystem; pakkeinstallasjoner er Installer en pakke; curl og wget er Bruk nettverket. Dette kjenner igjen den vanlige skrivemåten for en handling, og en agent som er fast bestemt på å skjule en (eval, en skriptfil, et direkte API-kall), klarer det. Pusher er ikke avhengige av det.

Ingen API-nøkkel for saker legges i en sandkasse. Agenter skriver saker gjennom Control Centers MCP-verktøy, som spør Skriv til et eksternt oppfølgingssystem.

Regelen Bruk nettverket leses én gang, når kjøringen starter. Nettverket i en sandkasse er enten på eller av, så nekt slår det av, mens tillat og spør først lar det stå på; harnessens web_fetch- og web_search-verktøy spør fortsatt ved hvert kall. Legitimasjon tilbakekalles når kjøringen avsluttes, sammen med alle skrivetokener gatewayen har utstedt, så et håndtak overlever ikke kjøringen.

SandboxBackend-enumen har tre verdier — native (Seatbelt på macOS, bubblewrap på Linux og WSL2), none og microvm. Innpakning av agentkommandoer bruker bare de to første: microvm er den innelukkede VM-backenden for interaktive terminaler og stasjoner, ikke en stille oppgradering av en bash-innpakning. Å forstå den native formen forklarer hva du vinner der en er festet, og hva den aldri kom til å gi deg.

cc_server sonderer verten én gang ved oppstart, og der en backend svarer, fôrer den begge skjøtene i utsendingstråden, fordi transportene ikke deler én. Den første (SandboxPort) pakker inn Claude Code-CLI-et. Den andre (SandboxManager) pakker inn ACP-adaptere og den innebygde harnessens bash-verktøy. Å fôre bare én ville la de andre starte upakket, og det var det som pleide å skje. På en vert uten backend, eller under --sandbox off, starter de alle upakket, og serveren sier hvilket tilfelle du er i, på oppstartslinjen sin. ACP og harnessens bash sanerer fortsatt miljøet; Claude Code gjennom NoSandboxAdapter arver vertens miljø.

Én flate velger bevisst bort dette: terminalen som hostes av serveren, godtar et backend-argument per økt, og cc_server konstruerer det med vertens skall som standard, så en terminal du åpner fra appen, kjører uten sandkasse med mindre en klient eksplisitt angir native. Det er et ekte skall på serververten, ikke en agentkjøring.

På Linux bygger bubblewrap et mount-navnerom, så agenten faktisk bare ser det som ble bundet inn i det. På macOS bruker sandbox-exec en Seatbelt-profil som er tillatende som standard — en profil som nekter alt som standard, gjør et interaktivt skall ubrukelig, fordi antallet dyld-, XPC- og Mach-kall som må listes opp, er upraktisk. macOS-profilen starter derfor fra (allow default) og skjærer ut nekter: lesing av hemmelige stier, en skrivepolicy nullstilt til nekt med eksplisitte tillatelser på toppen, blokkering av unlink på hver nektet sti og dens overordnede, så en omdøping ikke kan omgå en skrivenekt, exec-nekt for alltid-farlige binærer og for alt som kopieres inn i en skrivbar katalog, og nettverk begrenset til loopback-proxyportene pluss DNS.

Den praktiske konsekvensen: på Linux blir agentens syn på filsystemet smalere; på macOS kan agenten fortsatt lese mesteparten av verten, og de registrerte arbeidskopiene dine beskyttes av eksplisitte skrive- og omdøpingsnekter i stedet for av usynlighet. Begge er isolasjon på navneromsnivå, ikke en sikkerhetsgrense i kjernen — backendene sier det selv i sine egne kapabilitetsnotater.

Settet med bind-monteringer er der grensen mellom agenter trekkes. Overlay-utsending (Claude Code og ACP) monterer opptil fire stier, løst opp per kjøring: agentens overlay-arbeidskatalog med lese- og skrivetilgang, agentens konfigurasjonskatalog med lesetilgang, samtalens delte repos/-katalog med lese- og skrivetilgang når den finnes, og områdets vedleggskatalog med lesetilgang når den finnes. Katalogen til en søsteragent monteres aldri. Harnessens bash-innpakning er smalere: den bind-monterer bare kjøringens arbeidskatalog. Dette er samme form som harnessens prosessinterne stikontroll håndhever i programvare, så de to er enige — men bare stikontrollen dekker harnessens egne filverktøy, som aldri når et mount-navnerom i det hele tatt.

De registrerte arbeidskopiene dine kommer i tillegg inn i settet med skrivenekter, i hver modus og på alle tre av de innpakkede stiene (Claude Code, ACP, harnessens bash), og hver regel sendes ut i både sin litterale og sin symlenke-oppløste form. Den andre formen er ikke en bagatell: kjernen matcher en nektregel mot den oppløste stien, og macOS gjør oppløsning til det vanlige snarere enn det eksotiske — /tmp er egentlig /private/tmp, /var/folders/… er egentlig /private/var/folders/… — så en arbeidskopi nådd gjennom én av dem ville ha vært skrivbar mens profilen så ut til å forby det.

Nettverk, under en sandkasse, er en tillatelsesliste over domener per økt, håndhevet av loopback-proxyer i samme prosess. Utgående trafikk er nektet som standard: med nettverket av starter proxyene fortsatt, men tillatelseslisten deres er tom, så hver utgående tilkobling avvises. Med det på bygger oppløseren tillatelseslisten fra en kuratert grunnlinje — LLM-API-leverandører, git-hosting, pakkeregistre, modellkatalogen — pluss GitHubs domener, og matcher målverten til hver tilkobling mot den. Jokertegn virker (*.example.com matcher subdomener, ikke toppdomenet). En egen liste som alltid nektes, vinner over tillatelseslisten og dekker endepunkter for skymetadata og telemetrimottak. Det som ikke er konfigurerbart, er selve listen: spesifikasjonen har et egressAllowlist-felt for ekstra domener, men ingenting i utsendingstråden fyller det ut, så i praksis får hver kjøring grunnlinjen pluss GitHub og ingenting mer.

Nekter, på macOS, er observerbare: en monitor tapper systemloggen for Sandbox-nekter, filtrerer bort kjent støy, dedupliserer stormer med nye forsøk og løfter frem hver av dem som en SandboxViolation som navngir hva som ble forsøkt, og hva som ble blokkert. På Linux parser innpakningsstien stderr etter EPERM, Operation not permitted og Permission denied og sender ut samme hendelsestype — det finnes ingen systemlogg-tapp. Disse kommer inn på kjøringens egen hendelsesstrøm, så et nektet systemkall dukker opp i transkripsjonen i stedet for bare i en logg. Telemetrien finnes bare mens en native sandkasse faktisk pakker inn prosessen — på en vert uten backend er det ingenting å observere. Den er også uavhengig av tillatelsesreglene: en overtredelse kommer fra et nektet systemkall, aldri fra et regeloppslag.

Bekreftelser kommer fra tillatelsesreglene, ikke fra sandkassen

Seksjon med tittelen «Bekreftelser kommer fra tillatelsesreglene, ikke fra sandkassen»

Sandkassen har ingen godkjenningsvei. Den starter, kjører, sender ut hendelser og rives ned; det finnes ingen plass i den der et menneske kan svare på et spørsmål. Når du ser en bekreftelse i et område — godkjenn denne pushen, godkjenn denne slettingen — ble den reist av motoren for tillatelsesregler (eller av harnessens kommandopolicy) før handlingen i det hele tatt nådde kjøretiden.

Denne rekkefølgen er bevisst. En godkjenning som skjedde etter systemkallet, ville være en revisjonspost, ikke en sperre. Det er også derfor fail-closed-regelen bor i tillatelsesreglene: uten en tilkoblet godkjenner tas beslutningen før noe kjører, og den er et nekt.

Kjør agentdiagnostikk er det ene stedet som sier hva som kan sjekkes manuelt.