Sari la conținut

Backend-uri sandbox

Backend Valoare Platformă Disponibilitate
VM izolat microvm macOS, Linux, Windows (microVM hypervisor) Condiționat de sondă pe runtime-ul microVM; se aplică terminalelor interactive — shell-ul rulează într-o stație, nu pe gazdă
Nativ native macOS (Seatbelt), Linux și WSL2 (bubblewrap) Sondat la pornire; Linux refuză o rulare care necesită interdicții de scriere pe globuri de nume de fișiere, pe care bubblewrap nu le poate impune
Niciunul none Toate Implicitul la parsare, implicitul terminalului și soluția de rezervă când nu este disponibil niciun backend sau este setat --sandbox off

SandboxBackend.fromName returnează none pentru o valoare stocată nulă sau nerecunoscută. O valoare stocată "docker" se mapează la native.

microvm este singurul backend cu o graniță de kernel — sarcina de lucru chiar nu rulează pe gazdă. Nu este niciodată o soluție de rezervă silențioasă în niciun sens: cererea lui explicită pe o gazdă care nu poate porni un VM eșuează cu zgomot („Am cerut un VM și am primit laptopul meu” este singura degradare pe care izolarea nu și-o poate permite), iar doar un implicit nespecificat coboară la native, cu o linie de jurnal care spune asta. Backend-ul real al sesiunii este raportat înapoi clientului și marcat cu o insignă, așa că un terminal nu poate pretinde niciodată că este izolat în timp ce rulează pe gazdă. Ciclul de viață, imaginile și ieșirea în rețea pentru VM-ul însuși sunt acoperite de referința pentru stații.

Windows nu are sandbox nativ. NativeSandboxAdapter.probe returnează available: false pe orice gazdă care nu este macOS sau Linux/WSL2. Trimiterea agenților pe Windows folosește deci none. Backend-ul microvm este separat: este condiționat de sondă pe runtime-ul microVM și se aplică terminalelor interactive și stațiilor, nu învelirii bash pe gazdă.

Sonda este cea care condiționează aplicarea. Rulează o dată, la pornirea serverului.

Gazdă available Notă / indiciu
macOS true sandbox-exec (Seatbelt) — izolare la nivel de namespace, fără graniță de kernel
Linux sau WSL2 cu bwrap și socat true bubblewrap — izolare la nivel de namespace, fără graniță de kernel
Linux sau WSL2 căruia îi lipsește oricare dintre instrumente false requiresInstall: true, cu un indiciu de instalare care numește pachetele lipsă
Orice altă platformă false Sandbox-ul nativ nu este oferit pe această platformă

Serverul înregistrează rezultatul într-o singură linie de pornire: agent sandbox ON plus nota, no OS-native agent sandbox on this host plus indiciul, sau agent sandbox DISABLED by --sandbox off. GET /healthz nu conține niciun câmp de sandbox.

--sandbox / CC_SERVER_SANDBOX acceptă on (implicit) sau off. Este o dezactivare opțională, nu o activare: on înseamnă „învelește când gazda oferă un backend”, niciodată „cere unul”.

Folosește mecanismul de sandbox Seatbelt al Apple (același sistem folosit de macOS App Sandbox).

Profilul generat este permisiv implicit ((allow default)) și decupează interdicții, pentru că un profil complet cu refuz implicit face un shell interactiv inutilizabil — sunt prea multe apeluri dyld, XPC și Mach de enumerat. Consecința practică este că un agent poate citi în continuare cea mai mare parte a gazdei; checkout-urile tale înregistrate sunt protejate prin interdicții explicite de scriere și de redenumire, nu prin invizibilitate.

  • Citirile din sistemul de fișiere: interzise pentru căile secrete (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh, ~/Library/Keychains și restul setului cu citire interzisă)
  • Scrierile în sistemul de fișiere: resetate la refuz, apoi permisiuni explicite pe subcăi, cu căile cu interdicție obligatorie (fișierele rc ale shell-ului, .git/hooks, configurația Claude, checkout-urile protejate) blocate chiar și în interiorul unei rădăcini inscriptibile. file-write-unlink este interzis pe fiecare cale interzisă și pe strămoșii ei, așa că o redenumire nu poate ocoli o interdicție de scriere
  • Procese: process-exec interzice binarele mereu periculoase, plus o blocare a execuției oricărui lucru dintr-un director inscriptibil
  • Rețea: restricționată la porturile proxy din proces plus DNS — loopback nu este permis în bloc, iar socketurile Unix nu sunt permise. Proxy-urile impun lista de permisiuni de domenii per sesiune (vezi Ieșirea în rețea)
  • Configurare: profilurile sunt generate la fiecare lansare din SandboxSpec

Cerințe:

  • sandbox-exec (/usr/bin/sandbox-exec), parte din orice instalare macOS — nu este necesar niciun instrument suplimentar

Folosește bubblewrap (bwrap) pentru a crea namespace-uri de montare și PID.

  • Sistemul de fișiere: creează un namespace de montare în care sunt vizibile doar căile legate
  • Rețea: rulează într-un namespace de rețea nepartajat; traficul ajunge la proxy-urile gazdei prin punți de socketuri Unix socat
  • Procese: izolare prin namespace PID

Cerințe:

  • bubblewrap și socat instalate (sudo apt-get install bubblewrap socat pe Debian/Ubuntu)
  • Suport pentru namespace-uri în kernel (standard pe Linux-ul modern)

Bubblewrap poate umbri o cale existentă, dar nu poate interzice un fișier .env, *.key sau *.pem nou în interiorul unei legături inscriptibile. Politica standard de trimitere cere aceste interdicții pe globuri de nume de fișiere, așa că trimiterea nativă a agenților eșuează închis pe Linux/WSL2 chiar și atunci când sonda găsește bubblewrap. Nu rulează cu o politică slăbită în tăcere. Este necesar un backend cu reguli de nume de fișiere la momentul creării pentru a restabili trimiterea nativă pe Linux. Dezactivarea sandbox-ului este o renunțare explicită la confinare, nu o soluție echivalentă.

Fără sandbox. Procesul rulează direct pe gazdă, cu accesul gazdei la sistemul de fișiere, rețea și procese. ACP și instrumentul bash al harness-ului primesc în continuare sanitizarea mediului și politica de comenzi; Claude Code prin NoSandboxAdapter moștenește mediul gazdei. La acesta se întoarce trimiterea agenților pe o gazdă fără niciun backend disponibil sau cu --sandbox off, și este implicitul terminalului atunci când un client nu numește niciunul.

Terminalul este sandboxat după propriile reguli, nu de sonda de trimitere. defaultBackend al TerminalSessionService este none, iar cc_server nu îl suprascrie, așa că o sesiune de terminal rulează shell-ul gazdei dacă clientul nu cere explicit native — panoul de terminal al aplicației desktop nu o face.

Când se cere native, shell-ul este învelit prin SandboxManager sub un profil fix de terminal: $HOME și directorul de lucru al sesiunii sunt inscriptibile, rețeaua este deschisă, iar ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh și ~/Library/Keychains au citirea interzisă. Tokenul de forge doar pentru citire și gateway-ul de push nu se aplică — acestea sunt probleme de trimitere. Dacă învelirea nu este acceptată, sesiunea eșuează în loc să afișeze o insignă de nativ pe un PTY cu shell-ul gazdei.

Când este lansat un sandbox, accesul de ieșire în rețea este impus de proxy-uri din proces, nu de reguli de firewall la nivel de kernel. Pe o gazdă fără niciun backend disponibil nimic din toate acestea nu se aplică, iar un proces pornit ajunge la rețea exact așa cum ar face-o utilizatorul serverului.

  • Runtime-ul sandbox-ului rulează un proxy HTTP/HTTPS și un proxy SOCKS5 pe 127.0.0.1 în interiorul procesului cc_server, o pereche per sesiune restricționată (http_proxy.dart, socks_proxy.dart). Rulările concurente nu împart o pereche.
  • Procesele din sandbox primesc variabile de mediu pentru proxy (HTTP_PROXY/HTTPS_PROXY → proxy-ul HTTP, ALL_PROXY → proxy-ul SOCKS; GIT_SSH_COMMAND direcționează git-over-SSH prin proxy-ul SOCKS). Pe Linux namespace-ul de rețea este nepartajat, așa că proxy-urile sunt singura cale de ieșire; pe macOS sandbox-ul împarte loopback-ul gazdei și vorbește cu ele direct.
  • Gazda țintă a fiecărei conexiuni este potrivită cu lista de permisiuni de domenii a sesiunii; orice altceva este refuzat. Două forme de wildcard, fiecare cu exact un *: un *.example.com la început potrivește orice subdomeniu (inclusiv a.b.example.com), dar nu apexul însuși; un bedrock.*.amazonaws.com cu etichetă la mijloc potrivește exact o etichetă (bedrock.us-east-1.amazonaws.com, nu bedrock.evil.attacker.amazonaws.com).
  • Lista de permisiuni este rezolvată per sesiune din baza de referință atent selectată (kBaselineAllowedDomains: furnizori de LLM, găzduire Git, registre de pachete), plus domeniile GitHub (kGithubDomains), plus SandboxSpec.egressAllowlist. Calea de trimitere nu transmite niciodată intrări suplimentare de egressAllowlist, așa că în practică fiecare rulare primește baza de referință plus GitHub.
  • O listă fixă de interdicții (kBaselineDeniedDomains: puncte finale de metadate din cloud, destinații de telemetrie) are prioritate față de toate permisiunile.
  • networkEnabled: false pornește totuși proxy-urile per sesiune, dar lista de permisiuni este goală, așa că fiecare conexiune de ieșire este refuzată. Trimiterea îl setează la pornirea rulării din regula Accesează rețeaua: refuzul oprește rețeaua, permite și întreabă întâi o lasă pornită.

Specificația transmisă către SandboxPort.launch:

Câmp Tip Descriere
sessionId String Identificator stabil de sesiune (de obicei id-ul spațiului)
workspaceId String Id-ul spațiului de lucru proprietar
agentId String? Agentul legat în prezent de sandbox, dacă există
bindMounts List<SandboxBindMount> Montări gazdă→invitat; sandbox-ul vede doar căile de gazdă listate aici
networkEnabled bool (implicit true) Setat la trimitere din regula de ieșire în rețea (oprit doar când se rezolvă la refuz). Când este oprit, proxy-urile per sesiune pornesc totuși, dar lista de permisiuni este goală, așa că fiecare conexiune de ieșire este refuzată
egressAllowlist List<String> (implicit []) Domenii de ieșire suplimentare permise; wildcard-uri acceptate (*.example.com, plus o formă de mijloc cu o singură etichetă). Trimiterea nu populează niciodată acest câmp, așa că o rulare învelită primește baza de referință atent selectată plus domeniile GitHub
guestWorkdir String? Directorul de lucru implicit din interiorul sandbox-ului; apelurile exec individuale îl pot suprascrie
mode Mode (implicit chat) Decupează regulile de scriere în sistemul de fișiere: chat păstrează montările bind inscriptibile; review, plan și orchestrate sunt doar pentru citire
protectedPaths List<String> (implicit []) Căi de gazdă care nu trebuie să fie niciodată inscriptibile în interiorul sandbox-ului, în orice mod
runnerStateDirs List<String> (implicit []) Directoare de gazdă în care runner-ul își păstrează propria stare, inscriptibile în orice mod (un CLAUDE_CONFIG_DIR gestionat de Control Center este cel actual)
execGrantRoots List<String> (implicit []) Arbori de directoare aprobați de operator care redeschid blocarea de execuție pentru directoare inscriptibile pe macOS. Linux le ignoră — bwrap nu are nicio blocare de execuție pe $HOME de redeschis

protectedPaths conține checkout-urile originale ale repo-urilor înregistrate, rezolvate la fiecare trimitere din repo-urile spațiului de lucru. Agenții lucrează exclusiv în worktree-uri copy-on-write per conversație, așa că originalele nu trebuie să fie niciodată inscriptibile din interiorul sandbox-ului — pe macOS, permisiunea generală de scriere în $HOME a Seatbelt le-ar lăsa altfel inscriptibile prin bash. Ele se alătură setului cu scriere interzisă pentru secrete în fiecare mod, inclusiv chat.

Fiecare cale cu citire interzisă și cu scriere interzisă este emisă atât în grafia ei literală, cât și în cea rezolvată prin symlink. Kernelul potrivește o regulă de interdicție cu calea rezolvată, așa că o regulă scrisă pe o cale cu symlink nu se declanșează niciodată — iar pe macOS acesta este cazul obișnuit, nu cel exotic, fiindcă /tmp se rezolvă la /private/tmp, iar /var/folders/… la /private/var/folders/…. Intrările care conțin * sau ? sunt tratate ca globuri și lăsate în pace; o cale care nu se rezolvă este păstrată așa cum a fost scrisă.

Câmp Tip Descriere
hostPath String Cale absolută pe gazdă
guestPath String Cale în interiorul sandbox-ului (cu sandbox-ul nativ aceasta este egală cu hostPath; păstrată pentru simetria adaptoarelor)
readOnly bool (implicit false) Montează doar pentru citire când este true

Returnat de SandboxPort.launch:

Câmp Tip Descriere
sessionId String Identificator de sesiune
backend SandboxBackend Ce backend rulează
state SandboxState Starea ciclului de viață
error Object? Setat când state este error
details Map<String, Object?> Evidență privată a adaptorului (director de lucru, PID, calea profilului etc.)
Stare Descriere
created Specificația a fost creată, dar VM-ul nu a pornit încă
warm Rulează și este gata să execute comenzi
active Execută în prezent o comandă
suspended Pauzat / salvat ca punct de control pe disc pentru a economisi RAM
destroyed Desființat; handle-ul nu mai este valid
error Lansarea a eșuat — vezi SandboxHandle.error
Tip de eveniment Descriere
stdout Ieșirea standard a sandbox-ului
stderr Eroarea standard a sandbox-ului
exit Procesul sandbox-ului s-a încheiat (poartă exitCode)
starting Sandbox-ul pornește
ready Sandbox-ul este gata să accepte apeluri exec
killed Procesul a fost omorât de gazdă (OOM, timeout, oprire manuală)
violation O politică de sandbox a fost încălcată (poartă un SandboxViolation)
Câmp Tip Descriere
action String Verbul refuzat (de ex. file-read*, network-outbound)
target String Calea sau gazda care a fost ținta operației refuzate
raw String? Linia brută de jurnal, utilă pentru depanare