Přeskočit na obsah

Sandbox a zabezpečení

Proč tu na izolaci záleží

Sekce “Proč tu na izolaci záleží”

Agent čte soubory, spouští příkazy shellu a volá externí API za tebe. Selhání, na kterém záleží, málokdy je zlý úmysl. Je to agent, který špatně přečte cestu a přepíše tvůj pracovní checkout, nebo který odešle větev, kterou jsi ještě neměl zkontrolovanou. Otázka, na kterou produkt musí odpovědět, tedy není „je agent důvěryhodný“, ale „jaký je dosah, když se splete“.

Control Center na to odpovídá vrstvami a vrstvy jsou nezávislé schválně. Když jedna chybí, ostatní pořád drží. Na tom záleží, protože vnější vrstva, sandbox operačního systému, je jediná podmíněná: záleží na tom, jestli hostitel backend nabídne, a na Windows ho nenabídne žádný.

Co agenta skutečně omezuje

Sekce “Co agenta skutečně omezuje”

Pět věcí ohraničuje běh agenta na každém hostiteli, se sandboxem i bez něj. Žádná není hranice jádra a všechny jsou skutečné. Kde hostitel nabídne backend, sedí nad nimi sandbox operačního systému a „Co sandbox přidává a kde končí“ níže popisuje, co se tím změní.

Vlastní copy-on-write worktree. Každá konverzace si založí vlastní checkout každého propojeného repozitáře, klonovaný copy-on-write přes přibalené nativní rift, ne kopírovaný bajt po bajtu, takže izolace je dost levná na to, aby byla výchozí. Registrovaný repozitář, který jsi přidal do pracovního prostoru, není adresář, ve kterém agent pracuje. Dvě konverzace nad stejným repozitářem si navzájem nevidí úpravy a ani jedna neupravuje tvůj checkout.

Rozlišení stojí za přesnost: worktree je na konverzaci, ne na agenta. Dva agenti v jedné konverzaci dostanou odlišné overlay pracovní adresáře, které sdílejí repos/ té konverzace. O to jde, spolupracují na jedné změně, zatímco jejich soukromé adresáře agentů zůstanou oddělené. Copy-on-write je jediný backend všude, kde se dodává rift. Když souborový systém neumí reflink, nebo když datový adresář leží na jiném svazku než repozitář, provisioning selže, místo aby spadl na náhradní cestu. Windows je výjimka: copy-on-write backend tam není, takže backendem je git worktree, a nejde o tiché zhoršení.

Omezení cest v souborových nástrojích vestavěného harnessu. Nástroje harnessu read, write, edit, apply_patch, search, find a search_files rozřeší každou cestu vůči pracovnímu adresáři běhu a vůči sdíleným kořenům, adresáři repos/ konverzace, kde leží worktree, a adresáři příloh prostoru, když existuje, a odmítnou všechno, co skončí mimo. Absolutní cesta projde, jen když už uvnitř byla. Symlink uvnitř worktree, který z něj ukazuje ven, se rozřeší a taky odmítne. Proto agent neumí write do ~/.ssh, ani když tu žádný sandbox není.

Plocha nástrojů režimu. Režim konverzace rozhoduje, které nástroje se pro běh vůbec sestaví. V režimech jen pro čtení (plan, review, orchestrate) mutující nástroje v registru prostě nejsou a politika příkazů shellu v těchto režimech každý mutující příkaz rovnou zamítne, git commit, git push, npm install, docker a zbytek, a neptá se na ně. Nástroj, který tam není, se zavolat nedá, a to je silnější záruka než nástroj, který tam je a jen se hlídá.

Sjednocené guardraily akcí. Každý mutující nástroj deklaruje třídy efektů, které umí způsobit, a motor politik každou vyhodnotí na allow, prompt nebo deny, než se volání spustí. prompt bez připojeného schvalovatele se vyhodnotí jako denied: otázka, na kterou nikdo nemůže odpovědět, není nikdy ano. Viz Guardraily pro model rozhodování.

Pověření od brokera a očištěné prostředí. Pověření se vystaví při každém spuštění a odeberou se, když běh skončí, a token GitHub, který běh drží, umí číst, ale ne pushovat — viz „Push jde přes Control Center“ níže. ACP, bash harnessu a Claude Code pod nativním obalem dostanou prostředí očištěné od vektorů injekce dynamického linkeru a interpretu (LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS, GIT_SSH_COMMAND a příbuzné), než se proces spustí. Claude Code přes NoSandboxAdapter dědí prostředí hostitele.

Souborové nástroje harnessu běží v procesu serveru, ne v podřízeném procesu, takže by je neomezil žádný profil sandboxu, ani kdyby k nim byl připojený. Sandboxovaným runnerem příkazů projde jen bash. Jejich omezení je kontrola cest popsaná výš a ta kontrola je proto nosná, ne druhá linie obrany.

U nástroje bash je třeba být stejně přesný. Jeho příkazy se vyhodnotí proti politice příkazů režimu a pak se spustí s očištěným prostředím a s worktree jako pracovním adresářem. Kde backend je, ten start se obalí, takže zápis příkazu shellu mimo worktree zastaví sandbox. Kde není, na Windows nebo na Linuxu bez bwrap a socat, je politika příkazů jediné, co stojí mezi příkazem a zbytkem disku. V režimu chat politika natvrdo zamítá pevný seznam (eskalace oprávnění, destrukce systému a disku, únik z kontejneru a jmenného prostoru) a u změn vzdáleného stavu se ptá (git push, gh pr create, publikace a instalace balíčků). Co není na žádném ze seznamů, běží. Roury, řetězy a subshelly se rozloží a každý dílčí příkaz se vyhodnotí zvlášť. Vyhraje nejpřísnější rozhodnutí.

Claude Code použitý jako adaptér je případ, kde sandbox nese největší váhu. Control Center ho spouští s --dangerously-skip-permissions, protože neinteraktivní claude -p by jinak navždy čekalo na vlastní výzvu ke schválení. Jeho čtení, zápis a úpravy probíhají v jeho vlastním procesu a Control Center je nikdy nevidí. Jeho volání Bash jdou před spuštěním na hook na serveru, který zařadí příkazový řádek a zeptá se odpovídajícího pravidla, a nástroje mcp__*, které obsluhuje Control Center, procházejí bránou jako obvykle. Kde backend je, ta neviděná volání pořád probíhají uvnitř obalu sandboxu, takže hranicí kolem nich je operační systém. Kde není, není tam nic. Každý adaptér to uvádí ve své matici vynucení. Viz matice upřímnosti adaptéru.

Push jde přes Control Center

Sekce “Push jde přes Control Center”

Příznaky oprávnění pro jednotlivé agenty neexistují. Jestli agent smí vytvořit commit, pushovat, otevřít pull request, zapisovat do trackeru nebo sahat na síť, rozhodují guardraily — povolit, nejdřív se zeptat nebo zakázat — a stejná pravidla platí bez ohledu na to, který adaptér agenta spouští.

Push je akce, která musí vydržet i proti agentovi, který se snaží pravidlo obejít, takže se vynucuje tím, co agent drží, ne tím, co spouští. S nakonfigurovanou GitHub App dostane běh ve worktree GitHubu instalační token vázaný na repozitář, který umí číst obsah a pracovat s pull requesty, ale neumí pushovat. Konfigurace gitu běhu přepíše každou URL pro push na GitHub (https://github.com/, git@github.com:, ssh://git@github.com/) na bránu běhů agentů, loopback endpoint na cc_server. Brána přečte, které refy push aktualizuje, zeptá se pravidla Odeslat na remote — takže platí i pravidlo omezené na větve, třeba „zakázat push na main“ — a teprve pak push předá na GitHub se zapisovacím tokenem, který si pro ten účel vystaví a agentovi ho nikdy nedá. Odmítnutý push se ukáže přímo ve výstupu gitu:

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

Brána předává push jen do vlastního repozitáře worktree nebo do repozitáře na GitHubu propojeného s pracovním prostorem. Fetch jde přímo na GitHub se čtecím tokenem.

Ostatní příkazy shellu narazí na měkčí bránu. Nástroj bash harnessu a přes hook i nástroj Bash v Claude Code zařadí každý příkazový řádek a zeptají se pravidla, pod které spadá: git commit, merge, rebase, cherry-pick, revert a am jsou Vytvořit commit; gh pr create je Otevřít pull request; gh pr merge, review, comment a close jsou Zveřejnit kontrolu nebo sloučení; gh issue create, edit, comment a close jsou Zápis do externího trackeru; instalace balíčků jsou Nainstalovat balíček; curl a wget jsou Přístup k síti. Pozná to běžný zápis akce a agent odhodlaný ji schovat (eval, soubor se skriptem, přímé volání API) to dokáže. Push na tom nezávisí.

Do sandboxu se nevkládá žádný API klíč ticketingu. Agenti zapisují tickety přes nástroje MCP v Control Center, které se ptají pravidla Zápis do externího trackeru.

Pravidlo Přístup k síti se přečte jednou, při startu běhu. Síť sandboxu je buď zapnutá, nebo vypnutá, takže zakázat ji vypne, zatímco povolit a nejdřív se zeptat ji nechají zapnutou; nástroje harnessu web_fetch a web_search se pořád ptají při každém volání. Při úklidu běhu se pověření odeberou spolu s každým zapisovacím tokenem, který brána vystavila, takže získaný údaj běh nepřežije.

Co sandbox přidává a kde končí

Sekce “Co sandbox přidává a kde končí”

Enum SandboxBackend má tři hodnoty: native (Seatbelt na macOS, bubblewrap na Linuxu a na WSL2), none a microvm. Obal příkazů agenta používá jen první dvě. microvm je backend uzavřeného virtuálního stroje pro interaktivní terminály a stanice, ne tiché povýšení obalu bash. Tvar nativního backendu vysvětluje, co získáš tam, kde je připojený, a co ti nikdy dát neměl.

cc_server hostitele při startu jednou osondá a kde backend odpoví, napojí oba švy cesty odeslání, protože transporty jeden nesdílejí. První (SandboxPort) obalí CLI Claude Code. Druhý (SandboxManager) obalí adaptéry ACP a nástroj bash vestavěného harnessu. Napojit jen jeden by nechal ostatní startovat bez obalu, a přesně to se dřív dělo. Na hostiteli bez backendu, nebo pod --sandbox off, startují všechny bez obalu a server na startovním řádku řekne, ve kterém případě jsi. ACP a bash harnessu prostředí pořád čistí. Claude Code přes NoSandboxAdapter dědí prostředí hostitele.

Jedna plocha se záměrně vynechává. Terminál hostovaný na serveru bere argument backendu pro každou relaci a cc_server ho sestaví s výchozím shellem hostitele, takže terminál, který z aplikace otevřeš, běží bez sandboxu, dokud klient výslovně neuvede native. Je to skutečný shell na hostiteli serveru, ne běh agenta.

Na Linuxu bubblewrap postaví jmenný prostor připojení, takže agent opravdu vidí jen to, co se do něj navázalo. Na macOS sandbox-exec aplikuje profil Seatbelt, který je ve výchozím stavu permisivní. Profil, který ve výchozím stavu všechno zakáže, udělá interaktivní shell nepoužitelný, protože vypsat všechna volání dyld, XPC a Mach je nepraktické. Profil na macOS proto vychází z (allow default) a vykrajuje zákazy: čtení tajných cest, politiku zápisu vrácenou na zákaz s výslovnými povoleními nad ní, blokování unlink na každé zakázané cestě a jejích předcích, aby přejmenování neobešlo zákaz zápisu, zákazy exec pro binárky, které jsou vždy nebezpečné, a pro cokoli zkopírované do zapisovatelného adresáře, a síť omezenou na porty loopback proxy plus DNS.

Praktický důsledek: na Linuxu je pohled agenta na souborový systém zúžený, na macOS agent pořád může číst většinu hostitele a tvoje registrované checkouty chrání výslovné zákazy zápisu a přejmenování, ne neviditelnost. Obojí je izolace na úrovni jmenného prostoru, ne bezpečnostní hranice jádra. Backendy to samy říkají ve svých poznámkách ke schopnostem.

Sada bind mountů je místo, kudy vede hranice mezi agenty. Overlay odeslání (Claude Code a ACP) připojí až čtyři cesty, rozřešené pro každý běh: overlay pracovní adresář agenta pro čtení i zápis, konfigurační adresář agenta jen pro čtení, sdílený adresář repos/ konverzace pro čtení i zápis, když existuje, a adresář příloh prostoru jen pro čtení, když existuje. Adresář sousedního agenta se nikdy nepřipojí. Obal bash harnessu je užší: bind mountuje jen pracovní adresář běhu. Stejný tvar v softwaru vynucuje kontrola cest harnessu v procesu, takže se shodují. Jen kontrola cest ale pokrývá vlastní souborové nástroje harnessu, které se k jmennému prostoru připojení nikdy nedostanou.

Tvoje registrované checkouty se nad to přidají do sady zákazů zápisu, v každém režimu a na všech třech obalených cestách (Claude Code, ACP, bash harnessu). Každé pravidlo se vypíše v doslovném zápisu i v zápisu po rozřešení symlinku. Druhý zápis není ozdoba. Jádro porovnává zákaz s rozřešenou cestou a macOS dělá z rozřešení běžný případ, ne výjimku: /tmp je ve skutečnosti /private/tmp a /var/folders/… je ve skutečnosti /private/var/folders/…. Checkout dosažený kterýmkoli zápisem by šel zapisovat, a profil by přitom vypadal, že to zakazuje.

Síť v sandboxu je seznam povolených domén pro každou relaci a vynucují ho loopback proxy v procesu. Odchozí provoz je ve výchozím stavu zakázaný. Když je síť vypnutá, proxy se pořád spustí, ale seznam povolených je prázdný, takže se odmítne každé odchozí spojení. Když je zapnutá, resolver sestaví seznam povolených z vybraného základu (poskytovatelé LLM API, hosting gitu, registry balíčků, katalog modelů) a z domén GitHubu a porovná s ním cílového hostitele každého spojení. Zástupné znaky fungují (*.example.com odpovídá subdoménám, ne apexu). Samostatný seznam vždy zamítnutých má přednost před seznamem povolených a pokrývá endpointy metadat cloudu a cíle telemetrie. Co se nastavit nedá, je samotný seznam. Specifikace nese pole egressAllowlist pro další domény, ale cesta odeslání ho nenaplní, takže v praxi každý běh dostane základ plus GitHub a nic navíc.

Zamítnutí jsou na macOS pozorovatelná. Monitor čte systémový log a hledá zamítnutí Sandboxu, odfiltruje známý šum, sloučí vlny opakovaných pokusů a každé vynese jako SandboxViolation s tím, co se zkoušelo a co se zablokovalo. Na Linuxu cesta obalu parsuje stderr a hledá EPERM, Operation not permitted a Permission denied a vypíše stejný typ události. Odposlech systémového logu tam není. Dorazí na vlastní proud událostí běhu, takže se odepřené systémové volání ukáže v přepisu, ne jen v logu. Telemetrie existuje jen dokud nativní sandbox proces opravdu obaluje. Na hostiteli bez backendu není co pozorovat. S guardraily to taky nesouvisí. Porušení pochází z odepřeného systémového volání, nikdy z vyhledání pravidla.

Potvrzení přicházejí z guardrailů, ne ze sandboxu

Sekce “Potvrzení přicházejí z guardrailů, ne ze sandboxu”

Sandbox nemá cestu schválení. Spustí se, provede práci, vypíše události a skončí. Není v něm místo, kde by člověk odpověděl na otázku. Když v prostoru uvidíš potvrzení, schválit tenhle push nebo schválit tohle smazání, vyvolal ho motor guardrailů (nebo politika příkazů harnessu) ještě předtím, než akce vůbec došla k běhu.

Tohle pořadí je záměr. Schválení až po systémovém volání by bylo záznamem auditu, ne bránou. Proto taky pravidlo zavření při selhání žije v guardrailech. Když není připojený schvalovatel, rozhodnutí padne dřív, než cokoli poběží, a je to zamítnutí.

Diagnostikujte agenta je jediné místo, které říká, co se dá zkontrolovat ručně.