Sandbox și securitate
De ce contează izolarea aici
Secțiune intitulată „De ce contează izolarea aici”Un agent citește fișiere, rulează comenzi shell și apelează API-uri externe în numele tău. Eșecul care te îngrijorează este rareori rea-voință — este un agent care citește greșit o cale și îți rescrie checkout-ul de lucru, sau unul care împinge o ramură pe care nu terminai de revizuit. Așa că întrebarea la care trebuie să răspundă produsul nu este „este agentul demn de încredere”, ci „care este raza de impact atunci când greșește”.
Control Center răspunde la asta pe straturi, iar straturile sunt independente intenționat. Dacă lipsește unul, celelalte rezistă în continuare. Asta contează, pentru că stratul cel mai exterior — sandbox-ul de sistem — este singurul condiționat: depinde de faptul că gazda oferă un backend, iar pe Windows niciuna nu o face.
Ce limitează de fapt un agent
Secțiune intitulată „Ce limitează de fapt un agent”Cinci lucruri mărginesc o rulare de agent pe orice gazdă, cu sau fără sandbox. Niciunul dintre ele nu este o graniță de kernel; toate sunt reale. Acolo unde gazda oferă un backend, sandbox-ul de sistem stă deasupra lor, iar secțiunea „Ce adaugă sandbox-ul și unde se oprește” de mai jos acoperă ce schimbă asta.
Propriul worktree copy-on-write. Fiecare conversație își provisionează propriul checkout
pentru fiecare repo legat, clonat copy-on-write prin nativul rift inclus, nu copiat octet cu
octet, așa că izolarea este suficient de ieftină încât să fie varianta implicită. Repository-ul
înregistrat pe care l-ai adăugat în spațiul de lucru nu este directorul în care lucrează agentul —
două conversații care ating același repo nu pot vedea modificările celeilalte, iar niciuna nu-ți
editează checkout-ul.
Granularitatea merită precizată: worktree-ul este per conversație, nu per agent. Doi agenți
din aceeași conversație primesc directoare de lucru overlay distincte care împart directorul
repos/ al acelei conversații — ceea ce este și ideea, fiindcă ei colaborează la o singură
modificare — în timp ce directoarele lor private de agent rămân separate. Copy-on-write este
singurul backend oriunde este livrat rift: un sistem de fișiere care nu poate face reflink, sau
un director de date aflat pe alt volum decât repo-ul, face ca provisionarea să eșueze, în loc să
revină la altă variantă. Windows este excepția — acolo nu există niciun backend copy-on-write,
așa că git worktree este backendul, nu o degradare silențioasă.
Confinarea căilor în instrumentele de fișiere ale harness-ului încorporat. Instrumentele
read, write, edit, apply_patch, search, find și search_files ale harness-ului
rezolvă fiecare cale față de directorul de lucru al rulării plus rădăcinile partajate — directorul
repos/ al conversației, unde se află worktree-urile, și directorul de atașamente al spațiului,
când există — și refuză orice iese în afara lor. Căile absolute sunt acceptate doar dacă erau
deja înăuntru. Un symlink aflat în interiorul worktree-ului care indică spre exterior este
rezolvat și refuzat la fel. De aceea un agent nu poate scrie cu write în ~/.ssh nici atunci
când sandbox-ul nu intră deloc în discuție.
Suprafața de instrumente a modului. Modul unei conversații
decide ce instrumente sunt materializate pentru rulare. În modurile doar pentru citire (plan,
review, orchestrate) instrumentele care modifică starea pur și simplu nu se află în registru,
iar politica de comenzi shell a acestor moduri refuză categoric fiecare comandă care modifică
starea — git commit, git push, npm install, docker și restul — în loc să ceară aprobarea
pentru ele. Un instrument care nu este prezent nu poate fi apelat, ceea ce este o garanție mai
puternică decât un instrument prezent și filtrat.
Guardrail-urile unificate de acțiuni. Fiecare instrument care modifică starea declară clasele
de efect pe care le poate produce, iar motorul de politici rezolvă fiecare dintre ele la permite,
cere aprobare sau refuză înainte ca apelul să ruleze. Un prompt fără niciun aprobator conectat
se rezolvă la refuzat — o întrebare la care nimeni nu poate răspunde nu este niciodată un da.
Vezi Guardrail-uri pentru modelul de rezolvare.
Date de autentificare emise de broker și un mediu sanitizat. Datele de autentificare sunt emise la fiecare lansare și sunt revocate când rularea se desființează, iar tokenul GitHub pe care îl are o rulare poate citi, dar nu poate face push — vezi „Push-urile trec prin Control Center” mai jos. ACP, bash-ul harness-ului și Claude Code sub învelișul nativ
primesc un mediu curățat de vectorii de injecție prin linker dinamic și interpretor (LD_PRELOAD,
DYLD_INSERT_LIBRARIES, NODE_OPTIONS, GIT_SSH_COMMAND și rudele lor) înainte ca procesul să
pornească. Claude Code prin NoSandboxAdapter moștenește mediul gazdei.
Unde aceste limite se subțiază
Secțiune intitulată „Unde aceste limite se subțiază”Instrumentele de fișiere ale harness-ului rulează în procesul serverului, nu într-un proces
copil, așa că niciun profil de sandbox nu le-ar putea limita chiar dacă ar fi atașat unul — doar
bash trece prin executorul de comenzi sandboxat. Confinarea lor este verificarea de căi descrisă
mai sus, iar acea verificare este prin urmare esențială, nu o a doua linie de apărare.
Instrumentul bash este celălalt loc unde trebuie să fim precisi. Comenzile lui sunt evaluate
față de politica de comenzi a modului și apoi lansate cu un mediu sanitizat și cu worktree-ul ca
director de lucru. Acolo unde există un backend, acea lansare este învelită, așa că sandbox-ul
este ceea ce oprește o comandă shell să scrie în afara worktree-ului; acolo unde nu există
niciunul — Windows, sau Linux fără bwrap și socat — politica de comenzi este singurul lucru
dintre comandă și restul discului. În modul chat, politica refuză categoric o listă fixă
(escaladarea privilegiilor, distrugerea sistemului și a discului, evadarea din container și din
namespace) și cere aprobare pentru mutațiile stării de la distanță (git push, gh pr create,
publicări și instalări de pachete). Orice nu se află pe niciuna dintre liste rulează.
Pipeline-urile, lanțurile și subshell-urile sunt analizate, iar fiecare subcomandă este evaluată
separat, câștigând decizia cea mai restrictivă.
Claude Code, când este folosit ca adaptor, este cazul în care sandbox-ul are cea mai mare
greutate. Control Center îl lansează cu --dangerously-skip-permissions, pentru că un claude -p
neinteractiv s-ar bloca altfel la nesfârșit pe propriul prompt de aprobare. Apelurile sale de citire, scriere și editare au loc în interiorul propriului proces, iar Control Center nu le vede niciodată. Apelurile sale Bash sunt trimise, înainte să ruleze, unui hook de pe server care clasifică linia de comandă și întreabă regula corespunzătoare, iar instrumentele mcp__* pe care le deservește Control Center trec prin poartă ca de obicei. Acolo unde există un backend, acele apeluri
nevăzute au loc totuși în interiorul învelișului de sandbox, așa că sistemul de operare este
granița din jurul lor. Acolo unde nu există niciunul, nimic nu este. Fiecare adaptor declară
asta în matricea sa de aplicare — vezi matricea de onestitate a
adaptoarelor.
Push-urile trec prin Control Center
Secțiune intitulată „Push-urile trec prin Control Center”Nu există indicatori de permisiune per agent. Dacă un agent poate crea un commit, face push, deschide un pull request, scrie într-un tracker sau ajunge la rețea decid guardrail-urile — permite, întreabă întâi sau refuză — iar aceleași reguli se aplică indiferent de adaptorul care rulează agentul.
Un push este acțiunea care trebuie să reziste chiar și în fața unui agent care
încearcă să ocolească o regulă, așa că este impus prin ce deține agentul, nu
prin ce rulează. Cu un GitHub App configurat, o rulare într-un worktree GitHub
primește un token de instalare limitat la depozit, care poate citi conținut și
lucra cu pull request-uri, dar nu poate face push. Configurația git a rulării
rescrie fiecare URL de push GitHub (https://github.com/, git@github.com:,
ssh://git@github.com/) către gateway-ul rulărilor de agenți, un punct
final de loopback pe cc_server. Gateway-ul citește ce ref-uri actualizează
push-ul, întreabă regula Push către un remote — așa că se aplică și o regulă
limitată la ramuri, precum „refuză push-ul pe main” — și abia apoi transmite
push-ul către GitHub cu un token de scriere pe care îl emite în acest scop și nu
îl dă niciodată agentului. Un push refuzat apare chiar în ieșirea git:
! [remote rejected] main -> main (Control Center policy: …)Gateway-ul transmite push-uri doar către depozitul propriu al worktree-ului sau către un depozit GitHub legat de spațiul de lucru. Fetch-urile merg direct la GitHub cu tokenul de citire.
Celelalte comenzi de shell întâlnesc o poartă mai blândă. Instrumentul bash al
harness-ului și, printr-un hook, instrumentul Bash al Claude Code clasifică
fiecare linie de comandă și întreabă regula în care se încadrează: git commit,
merge, rebase, cherry-pick, revert și am sunt Creează un commit;
gh pr create este Deschide un pull request; gh pr merge, review,
comment și close sunt Publică o revizuire sau o fuziune;
gh issue create, edit, comment și close sunt Scrie într-un tracker
extern; instalările de pachete sunt Instalează un pachet; curl și wget
sunt Accesează rețeaua. Asta recunoaște forma obișnuită de a scrie o
acțiune, iar un agent hotărât să o ascundă (eval, un fișier de script, un apel
direct la API) poate face asta. Push-urile nu depind de asta.
Nicio cheie API de tichete nu este pusă într-un sandbox. Agenții scriu tichete prin instrumentele MCP ale Control Center, care întreabă Scrie într-un tracker extern.
Regula Accesează rețeaua este citită o singură dată, când pornește rularea.
Rețeaua unui sandbox este fie pornită, fie oprită, așa că refuzul o oprește, iar
permite și întreabă întâi o lasă pornită; instrumentele web_fetch și
web_search ale harness-ului întreabă în continuare la fiecare apel. Datele de
autentificare sunt revocate când rularea se desființează, împreună cu orice
token de scriere emis de gateway, așa că un handle nu supraviețuiește rulării.
Ce adaugă sandbox-ul și unde se oprește
Secțiune intitulată „Ce adaugă sandbox-ul și unde se oprește”Enumerația SandboxBackend are trei valori — native (Seatbelt pe macOS, bubblewrap pe Linux și
WSL2), none și microvm. Învelirea comenzilor agentului folosește doar primele două: microvm
este backendul de VM izolat pentru terminalele interactive și stații, nu o upgradare silențioasă
a unui înveliș bash. Înțelegerea formei native explică ce câștigi acolo unde este atașat unul și
ce nu ți-ar fi dat niciodată.
cc_server sondează gazda o dată la pornire, iar acolo unde un backend răspunde hrănește ambele
cusături pe care le are calea de trimitere, pentru că transporturile nu împart una singură. Prima
(SandboxPort) învelește CLI-ul Claude Code. A doua (SandboxManager) învelește adaptoarele ACP
și instrumentul bash al harness-ului încorporat. Dacă ai hrăni doar una, celelalte ar fi lansate
neînvelite, ceea ce se și întâmpla înainte. Pe o gazdă fără backend, sau sub --sandbox off,
toate sunt lansate neînvelite, iar serverul spune în ce caz te afli pe linia sa de pornire. ACP și
bash-ul harness-ului sanitizează în continuare mediul; Claude Code prin NoSandboxAdapter
moștenește mediul gazdei.
O suprafață se sustrage în mod deliberat: terminalul găzduit de server acceptă un argument de
backend per sesiune, iar cc_server îl construiește implicit cu shell-ul gazdei, așa că un
terminal pe care îl deschizi din aplicație rulează fără sandbox dacă un client nu numește explicit
native. Acela este un shell real pe gazda serverului, nu o rulare de agent.
Pe Linux, bubblewrap construiește un namespace de montare, așa că agentul vede într-adevăr doar
ce a fost legat în el. Pe macOS, sandbox-exec aplică un profil Seatbelt care este permisiv
implicit — un profil complet cu refuz implicit face un shell interactiv inutilizabil, pentru că
numărul de apeluri dyld, XPC și Mach de enumerat este nepractic. Profilul macOS pornește deci de
la (allow default) și decupează interdicții: citiri ale căilor secrete, o politică de scriere
resetată la refuz cu permisiuni explicite deasupra, blocarea ștergerii (unlink) pe fiecare cale
interzisă și pe strămoșii ei, astfel încât o redenumire să nu poată ocoli o interdicție de
scriere, interdicții de execuție pentru binarele mereu periculoase și pentru orice este copiat
într-un director inscriptibil, și rețeaua restricționată la porturile proxy de loopback plus DNS.
Consecința practică: pe Linux, vederea agentului asupra sistemului de fișiere este îngustată; pe macOS, agentul poate în continuare citi cea mai mare parte a gazdei, iar checkout-urile tale înregistrate sunt protejate prin interdicții explicite de scriere și de redenumire, nu prin invizibilitate. Ambele sunt izolare la nivel de namespace, nu o graniță de securitate a kernelului — backend-urile spun asta în propriile note de capabilități.
Setul de montări bind este locul unde este trasată granița dintre agenți. Trimiterea pe overlay
(Claude Code și ACP) montează până la patru căi, rezolvate per rulare: directorul de lucru overlay
al agentului, cu citire-scriere, directorul de configurare al agentului, cu citire doar,
directorul partajat repos/ al conversației, cu citire-scriere când există, și directorul de
atașamente al spațiului, cu citire doar când există. Directorul unui agent frate nu este niciodată
montat. Învelișul bash al harness-ului este mai îngust: montează prin bind doar directorul de
lucru al rulării. Aceasta este aceeași formă pe care o impune în software verificarea de căi în
proces a harness-ului, așa că cele două sunt de acord — dar doar verificarea de căi acoperă
instrumentele de fișiere proprii ale harness-ului, care nu ajung niciodată la un namespace de
montare.
Checkout-urile tale înregistrate se alătură setului cu scriere interzisă peste asta, în fiecare
mod și pe toate cele trei căi învelite (Claude Code, ACP, bash-ul harness-ului), iar fiecare
regulă este emisă atât în grafia ei literală, cât și în cea rezolvată prin symlink. A doua grafie
nu este un moft:
kernelul potrivește o regulă de interdicție
cu calea rezolvată, iar macOS face din rezolvare cazul obișnuit, nu cel exotic — /tmp este de
fapt /private/tmp, /var/folders/… este de fapt /private/var/folders/… — așa că un checkout
accesat prin oricare dintre ele ar fi fost inscriptibil în timp ce profilul părea să îl interzică.
Rețeaua, sub un sandbox, este o listă de permisiuni pe domenii, per sesiune, impusă de
proxy-uri de loopback în proces. Ieșirea în rețea este refuzată implicit: cu rețeaua oprită,
proxy-urile pornesc totuși, dar lista lor de permisiuni este goală, așa că fiecare conexiune de
ieșire este refuzată. Cu ea pornită, rezolvatorul construiește lista de permisiuni dintr-o bază
de referință atent selectată — furnizori de API pentru LLM-uri, găzduire git, registre de
pachete, catalogul de modele — plus domeniile GitHub, și potrivește gazda țintă a fiecărei
conexiuni cu ea. Wildcard-urile funcționează (*.example.com potrivește subdomeniile, nu
apexul). O listă separată, mereu interzisă, are prioritate în fața listei de permisiuni și
acoperă punctele finale de metadate din cloud și destinațiile de telemetrie. Ceea ce nu este
configurabil este lista în sine: specificația poartă un câmp egressAllowlist pentru domenii
suplimentare, dar nimic din calea de trimitere nu îl populează, așa că în practică fiecare
rulare primește baza de referință plus GitHub, și nimic mai mult.
Interdicțiile, pe macOS, sunt observabile: un monitor interceptează jurnalul de sistem pentru
interdicțiile Sandbox, filtrează zgomotul cunoscut, elimină duplicatele furtunilor de reîncercări
și expune fiecare interdicție ca un SandboxViolation care numește ce s-a încercat și ce a fost blocat. Pe Linux, calea de învelire analizează stderr după EPERM,
Operation not permitted și Permission denied și emite același tip de eveniment — nu există
nicio interceptare a jurnalului de sistem. Acestea ajung pe fluxul propriu de evenimente al
rulării, așa că un apel de sistem refuzat apare în transcriere, nu doar într-un jurnal. Telemetria
există doar cât timp un sandbox nativ învelește efectiv procesul — pe o gazdă fără backend nu
există nimic de observat. De asemenea, nu are legătură cu guardrail-urile: o încălcare provine dintr-un apel de sistem refuzat, niciodată dintr-o căutare de regulă.
Confirmările vin de la guardrail-uri, nu de la sandbox
Secțiune intitulată „Confirmările vin de la guardrail-uri, nu de la sandbox”Sandbox-ul nu are nicio cale de aprobare. El lansează, execută, emite evenimente și se desființează; nu există în el niciun loc în care un om să răspundă la o întrebare. Când vezi o confirmare într-un spațiu — aprobă acest push, aprobă această ștergere — ea a fost ridicată de motorul de guardrail-uri (sau de politica de comenzi a harness-ului) înainte ca acțiunea să ajungă deloc la runtime.
Această ordine este deliberată. O aprobare care s-ar produce după apelul de sistem ar fi o înregistrare de audit, nu o poartă. De aceea regula cu eșec închis stă în guardrail-uri: fără niciun aprobator conectat, decizia este luată înainte ca orice să ruleze și este un refuz.
Diagnosticare
Secțiune intitulată „Diagnosticare”Diagnostichează un agent este singurul loc care declară ce poate fi verificat manual.
Concepte conexe
Secțiune intitulată „Concepte conexe”- Stații și medii izolate: pasul de deasupra sandbox-ului — când munca are nevoie de o mașină întreagă de unică folosință cu o graniță de kernel, nu de un proces învelit
- Guardrail-uri: stratul de politici de deasupra pragului oferit de sistemul de operare și de unde vin de fapt aprobările
- Ciclul de viață al trimiterii agenților: unde se încadrează izolarea în fluxul de trimitere
- Moduri: cum îngustează un mod suprafața de instrumente și politica de comenzi
- Referință pentru backend-urile de sandbox: backend-urile și configurarea lor
Ghiduri conexe
Secțiune intitulată „Ghiduri conexe”- Configurează politicile de sandbox: regulile de acțiune, impunerea push-urilor și postura față de sistemul de fișiere
- Diagnostichează un agent: ce acoperă verificările de diagnosticare