Salta ai contenuti

Backend del sandbox

Backend Valore Piattaforma Disponibilità
VM isolata microvm macOS, Linux, Windows (hypervisor microVM) Subordinato alla sonda sul runtime microVM; si applica ai terminali interattivi — la shell viene eseguita dentro un rig, non sull’host
Nativo native macOS (Seatbelt), Linux e WSL2 (bubblewrap) Sondato all’avvio; Linux rifiuta un’esecuzione che richiede dinieghi di scrittura con glob di nomi file, che bubblewrap non può applicare
Nessuno none Tutte Il valore predefinito in fase di analisi, il valore predefinito del terminale e il ripiego quando non è disponibile alcun backend o è impostato --sandbox off

SandboxBackend.fromName restituisce none per un valore memorizzato nullo o non riconosciuto. Un valore memorizzato "docker" viene mappato su native.

microvm è l’unico backend con un confine del kernel — il carico di lavoro non viene davvero eseguito sull’host. Non è mai un ripiego silenzioso in nessuna delle due direzioni: chiederlo esplicitamente su un host che non può avviare una VM fallisce in modo rumoroso («ho chiesto una VM e mi sono ritrovato il portatile» è l’unica degradazione che il recinto non può permettersi), e solo un valore predefinito non specificato scende a native, con una riga di log che lo dichiara. Il backend effettivo della sessione viene riportato al client e contrassegnato con un badge, così un terminale non può mai dichiararsi racchiuso mentre viene eseguito sull’host. Ciclo di vita, immagini ed egress della VM stessa sono trattati nel riferimento sui rig.

Windows non ha alcun sandbox nativo. NativeSandboxAdapter.probe restituisce available: false su ogni host che non sia macOS o Linux/WSL2. L’invio degli agenti su Windows usa quindi none. Il backend microvm è separato: è subordinato alla sonda sul runtime microVM e si applica ai terminali interattivi e ai rig, non all’avvolgimento di bash sull’host.

È la sonda a governare l’applicazione. Viene eseguita una sola volta, all’avvio del server.

Host available Nota / suggerimento
macOS true sandbox-exec (Seatbelt) — isolamento a livello di namespace, nessun confine del kernel
Linux o WSL2 con bwrap e socat true bubblewrap — isolamento a livello di namespace, nessun confine del kernel
Linux o WSL2 senza uno dei due strumenti false requiresInstall: true, con un suggerimento di installazione che nomina i pacchetti mancanti
Qualsiasi altra piattaforma false Il sandbox nativo non è offerto su questa piattaforma

Il server registra l’esito in un’unica riga all’avvio: agent sandbox ON più la nota, no OS-native agent sandbox on this host più il suggerimento, oppure agent sandbox DISABLED by --sandbox off. GET /healthz non contiene alcun campo del sandbox.

--sandbox / CC_SERVER_SANDBOX accetta on (predefinito) o off. È un opt-out, non un’abilitazione: on significa «avvolgi quando l’host offre un backend», mai «esigine uno».

Usa il meccanismo di sandbox Seatbelt di Apple (lo stesso sistema usato dall’App Sandbox di macOS).

Il profilo generato è permissivo per impostazione predefinita ((allow default)) e ritaglia dei dinieghi, perché un profilo completamente deny-by-default rende inutilizzabile una shell interattiva — ci sono troppe chiamate dyld, XPC e Mach da enumerare. La conseguenza pratica è che un agente può ancora leggere gran parte dell’host; i tuoi checkout registrati sono protetti da dinieghi espliciti di scrittura e rinomina, non dall’invisibilità.

  • Letture del filesystem: negate per i percorsi segreti (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh, ~/Library/Keychains e il resto dell’insieme delle letture negate)
  • Scritture del filesystem: riportate a deny, poi consentite esplicitamente su singoli sottopercorsi, con i percorsi a diniego obbligatorio (file rc della shell, .git/hooks, configurazione di Claude, checkout protetti) bloccati anche dentro una radice scrivibile. file-write-unlink è negato su ogni percorso negato e sui suoi antenati, così una rinomina non può aggirare un diniego di scrittura
  • Processi: dinieghi process-exec per i binari sempre pericolosi, più un blocco sull’esecuzione di qualsiasi cosa da una directory scrivibile
  • Rete: limitata alle porte dei proxy in-process più il DNS — il loopback non è consentito in modo indiscriminato e i socket Unix non sono ammessi. I proxy applicano la lista di autorizzazione dei domini per sessione (vedi Egress di rete)
  • Configurazione: i profili vengono generati a ogni avvio dal SandboxSpec

Requisiti:

  • sandbox-exec (/usr/bin/sandbox-exec), presente in ogni installazione di macOS — non serve alcuno strumento aggiuntivo

Usa bubblewrap (bwrap) per creare namespace di mount e di PID.

  • Filesystem: crea un namespace di mount in cui sono visibili solo i percorsi montati
  • Rete: viene eseguito in un namespace di rete non condiviso; il traffico raggiunge i proxy dell’host tramite ponti socket Unix di socat
  • Processi: isolamento del namespace dei PID

Requisiti:

  • bubblewrap e socat installati (sudo apt-get install bubblewrap socat su Debian/Ubuntu)
  • Supporto ai namespace del kernel (standard su Linux moderno)

Bubblewrap può oscurare un percorso esistente ma non può vietare un file .env, *.key o *.pem nuovo dentro un bind scrivibile. La policy di invio standard richiede questi dinieghi basati su glob di nomi file, quindi oggi l’invio nativo degli agenti fallisce in modo chiuso su Linux/WSL2 anche quando la sonda trova bubblewrap. Non viene eseguito con una policy silenziosamente più debole. Per ripristinare l’invio nativo su Linux serve un backend con regole sui nomi file al momento della creazione. Disattivare il sandbox è una rinuncia esplicita senza confinamento, non una soluzione equivalente.

Nessun sandbox. Il processo viene eseguito direttamente sull’host, con accesso al filesystem, alla rete e ai processi dell’host. ACP e lo strumento bash dell’harness ricevono comunque la sanificazione dell’ambiente e la policy dei comandi; Claude Code tramite NoSandboxAdapter eredita l’ambiente dell’host. È ciò su cui ripiega l’invio degli agenti su un host senza alcun backend disponibile o con --sandbox off, ed è il valore predefinito del terminale quando un client non ne indica nessuno.

Il terminale viene racchiuso in un sandbox secondo regole proprie, non dalla sonda dell’invio. Il defaultBackend di TerminalSessionService è none e cc_server non lo sovrascrive, quindi una sessione di terminale esegue la shell dell’host a meno che il client non chieda esplicitamente native — il pannello del terminale del desktop non lo fa.

Quando viene richiesto native, la shell viene avvolta tramite SandboxManager con un profilo di terminale fisso: $HOME e la directory di lavoro della sessione sono scrivibili, la rete è aperta e ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh e ~/Library/Keychains sono negati in lettura. Il token del forge di sola lettura e il gateway dei push non si applicano — sono una questione di invio. Se l’avvolgimento non è supportato, la sessione fallisce invece di mostrare un badge nativo su un PTY con la shell dell’host.

Quando viene avviato un sandbox, l’accesso di rete in uscita è applicato da proxy in-process, non da regole di firewall del kernel. Su un host senza alcun backend disponibile nulla di tutto ciò si applica e un processo avviato raggiunge la rete esattamente come farebbe l’utente del server.

  • Il runtime del sandbox esegue un proxy HTTP/HTTPS e un proxy SOCKS5 su 127.0.0.1 all’interno del processo cc_server, una coppia per sessione limitata (http_proxy.dart, socks_proxy.dart). Esecuzioni concorrenti non condividono una coppia.
  • I processi racchiusi in un sandbox ricevono le variabili d’ambiente dei proxy (HTTP_PROXY/HTTPS_PROXY → il proxy HTTP, ALL_PROXY → il proxy SOCKS; GIT_SSH_COMMAND instrada git-over-SSH attraverso il proxy SOCKS). Su Linux il namespace di rete non è condiviso, quindi i proxy sono l’unica via d’uscita; su macOS il sandbox condivide il loopback dell’host e comunica direttamente con loro.
  • L’host di destinazione di ogni connessione viene confrontato con la lista di autorizzazione dei domini della sessione; qualsiasi altra cosa viene negata. Due forme con caratteri jolly, ciascuna con esattamente un *: un *.example.com iniziale corrisponde a qualsiasi sottodominio (compreso a.b.example.com) ma non al dominio radice stesso; un bedrock.*.amazonaws.com con etichetta centrale corrisponde esattamente a un’etichetta (bedrock.us-east-1.amazonaws.com, non bedrock.evil.attacker.amazonaws.com).
  • La lista di autorizzazione viene risolta per sessione dalla base di riferimento curata (kBaselineAllowedDomains: provider LLM, hosting Git, registry dei pacchetti), più i domini GitHub (kGithubDomains), più SandboxSpec.egressAllowlist. Il percorso di invio non passa mai voci egressAllowlist aggiuntive, quindi in pratica ogni esecuzione ottiene la base di riferimento più GitHub.
  • Un elenco fisso di domini negati (kBaselineDeniedDomains: endpoint di metadati cloud, destinazioni di telemetria) prevale su tutte le autorizzazioni.
  • networkEnabled: false avvia comunque i proxy per sessione, ma la lista di autorizzazione è vuota, quindi ogni connessione in uscita viene rifiutata. L’invio lo imposta all’avvio dell’esecuzione dalla regola Accedere alla rete: nega disattiva la rete, consenti e chiedi prima la lasciano attiva.

La specifica passata a SandboxPort.launch:

Campo Tipo Descrizione
sessionId String Identificatore di sessione stabile (di norma l’id dello spazio)
workspaceId String Id dello spazio di lavoro proprietario
agentId String? Agente attualmente associato al sandbox, se presente
bindMounts List<SandboxBindMount> Mount host→guest; il sandbox vede solo i percorsi dell’host elencati qui
networkEnabled bool (predefinito true) Impostato all’invio dalla regola di uscita di rete (disattivato solo quando si risolve in nega). Se disattivato, i proxy per sessione si avviano comunque ma la lista di autorizzazione è vuota, quindi ogni connessione in uscita viene rifiutata
egressAllowlist List<String> (predefinito []) Domini di egress consentiti in aggiunta; caratteri jolly supportati (*.example.com, più una forma centrale a un’etichetta). L’invio non popola mai questo campo, quindi un’esecuzione avvolta ottiene la base di riferimento curata più i domini GitHub
guestWorkdir String? Directory di lavoro predefinita dentro il sandbox; le singole chiamate exec possono sovrascriverla
mode Mode (predefinito chat) Ritaglia le regole di scrittura del filesystem: chat mantiene scrivibili i bind mount; review, plan e orchestrate sono di sola lettura
protectedPaths List<String> (predefinito []) Percorsi dell’host che non devono mai essere scrivibili dentro il sandbox, in nessuna modalità
runnerStateDirs List<String> (predefinito []) Directory dell’host in cui il runner conserva il proprio stato, scrivibili in ogni modalità (quella attuale è una CLAUDE_CONFIG_DIR gestita da Control Center)
execGrantRoots List<String> (predefinito []) Alberi di directory approvati dall’operatore che riaprono il blocco di exec delle directory scrivibili su macOS. Linux li ignora — bwrap non ha alcun blocco di exec su $HOME da riaprire

protectedPaths contiene i checkout originali dei repo registrati, risolti a ogni invio dai repo dello spazio di lavoro. Gli agenti lavorano esclusivamente in worktree copy-on-write per conversazione, quindi gli originali non devono mai essere scrivibili dall’interno del sandbox — su macOS, l’autorizzazione indiscriminata di scrittura su $HOME di Seatbelt li lascerebbe altrimenti scrivibili tramite bash. Si aggiungono all’insieme dei dinieghi di scrittura dei segreti in ogni modalità, chat compresa.

Ogni percorso di lettura negata e di scrittura negata viene emesso sia nella sua grafia letterale sia in quella risolta dai symlink. Il kernel confronta una regola di diniego con il percorso risolto, quindi una regola scritta su un percorso con symlink non scatta mai — e su macOS questo è il caso comune, non quello esotico, dato che /tmp viene risolto in /private/tmp e /var/folders/… in /private/var/folders/…. Le voci che contengono * o ? sono trattate come glob e lasciate invariate; un percorso che non viene risolto viene mantenuto così com’è scritto.

Campo Tipo Descrizione
hostPath String Percorso assoluto sull’host
guestPath String Percorso dentro il sandbox (con il sandbox nativo coincide con hostPath; mantenuto per simmetria tra gli adapter)
readOnly bool (predefinito false) Monta in sola lettura quando è true

Restituito da SandboxPort.launch:

Campo Tipo Descrizione
sessionId String Identificatore di sessione
backend SandboxBackend Quale backend è in esecuzione
state SandboxState Stato del ciclo di vita
error Object? Impostato quando state è error
details Map<String, Object?> Contabilità interna dell’adapter (directory di lavoro, PID, percorso del profilo e così via)
Stato Descrizione
created Specifica creata ma la VM non è ancora partita
warm In esecuzione e pronta a eseguire comandi
active Sta eseguendo un comando
suspended In pausa / con checkpoint su disco per risparmiare RAM
destroyed Smantellata; l’handle non è più valido
error Avvio non riuscito — vedi SandboxHandle.error
Tipo di evento Descrizione
stdout Output standard dal sandbox
stderr Errore standard dal sandbox
exit Il processo del sandbox è terminato (porta exitCode)
starting Il sandbox si sta avviando
ready Il sandbox è pronto ad accettare chiamate exec
killed Processo terminato dall’host (OOM, timeout, arresto manuale)
violation Una policy del sandbox è stata violata (porta una SandboxViolation)
Campo Tipo Descrizione
action String Il verbo negato (ad es. file-read*, network-outbound)
target String Percorso o host che era l’obiettivo dell’operazione negata
raw String? Riga di log grezza, utile per il debug