Sandbox e sicurezza
Perché l’isolamento conta qui
Sezione intitolata “Perché l’isolamento conta qui”Un agente legge file, esegue comandi shell e chiama API esterne per tuo conto. Il guasto che ti preoccupa raramente è la malizia — è un agente che fraintende un percorso e riscrive il tuo checkout di lavoro, o che esegue il push di un branch che non avevi finito di revisionare. Quindi la domanda a cui il prodotto deve rispondere non è «l’agente è affidabile?» ma «qual è il raggio d’impatto quando sbaglia?».
Control Center risponde a questa domanda per strati, e gli strati sono indipendenti di proposito. Se ne manca uno, gli altri tengono comunque. Questo conta, perché lo strato più esterno — il sandbox del sistema operativo — è l’unico condizionale: dipende dal fatto che l’host offra un backend, e su Windows nessun host lo fa.
Cosa vincola davvero un agente
Sezione intitolata “Cosa vincola davvero un agente”Cinque cose delimitano un’esecuzione di agente su ogni host, con o senza sandbox. Nessuna di esse è un confine del kernel; tutte sono reali. Dove l’host offre un backend, il sandbox del sistema operativo si colloca sopra di esse e la sezione «Cosa aggiunge il sandbox e dove si ferma» più avanti spiega cosa cambia.
Il suo worktree copy-on-write. Ogni conversazione predispone un proprio checkout
di ciascun repository collegato, clonato in copy-on-write tramite il native rift
incluso anziché copiato byte per byte, così l’isolamento è abbastanza economico da
essere il comportamento predefinito. Il repository registrato che hai aggiunto allo
spazio di lavoro non è la directory in cui lavora l’agente — due conversazioni che
toccano lo stesso repository non possono vedere le modifiche l’una dell’altra e
nessuna delle due sta modificando il tuo checkout.
Vale la pena precisare la granularità: il worktree è per conversazione, non per
agente. Due agenti nella stessa conversazione ricevono directory di lavoro overlay
distinte che condividono la repos/ di quella conversazione — che è esattamente il
punto, dato che stanno collaborando a una sola modifica — mentre le loro directory
private di agente restano separate. Il copy-on-write è l’unico backend ovunque rift
sia distribuito: un filesystem che non sa fare reflink, o una directory dati su un
volume diverso da quello del repository, fa fallire la predisposizione anziché
ripiegare. Windows è l’eccezione — lì non esiste alcun backend copy-on-write, quindi
il backend è git worktree, non un degrado silenzioso.
Il confinamento dei percorsi negli strumenti per i file dell’harness integrato.
Gli strumenti read, write, edit, apply_patch, search, find e
search_files dell’harness risolvono ogni percorso rispetto alla directory di lavoro
dell’esecuzione più le radici condivise — la directory repos/ della conversazione,
dove vivono i worktree, e la directory degli allegati dello spazio quando esiste — e
rifiutano qualsiasi cosa che finisca all’esterno. I percorsi assoluti sono accettati
solo se erano già all’interno. Anche un symlink dentro il worktree che punta fuori
viene risolto e rifiutato. Ecco perché un agente non può fare write su ~/.ssh
anche senza alcun sandbox in gioco.
La superficie degli strumenti della modalità. La
modalità di una conversazione decide quali
strumenti vengono materializzati per l’esecuzione. Nelle modalità di sola lettura
(plan, review, orchestrate) gli strumenti mutanti semplicemente non sono nel
registro e la policy dei comandi shell per quelle modalità nega del tutto ogni
comando mutante — git commit, git push, npm install, docker e gli altri —
anziché chiedere conferma. Uno strumento che non è presente non può essere chiamato,
il che è una garanzia più forte di uno strumento presente e vincolato.
I guardrail unificati delle azioni. Ogni strumento mutante dichiara le classi di effetto che può produrre e il motore di policy risolve ciascuna in allow, prompt o deny prima che la chiamata venga eseguita. Un prompt senza alcun approvatore collegato si risolve in denied — una domanda a cui nessuno può rispondere non è mai un sì. Vedi Guardrail per il modello di risoluzione.
Credenziali del broker e ambiente ripulito. Le credenziali vengono coniate a ogni avvio e revocate alla fine dell’esecuzione, e il token GitHub di un’esecuzione può leggere ma non eseguire push — vedi «I push passano da Control Center» più sotto. ACP, il bash dell’harness e Claude Code sotto l’incapsulamento
nativo ricevono un ambiente ripulito dai vettori di iniezione del linker dinamico e
dell’interprete (LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS,
GIT_SSH_COMMAND e simili) prima che il processo parta. Claude Code tramite
NoSandboxAdapter eredita l’ambiente dell’host.
Dove quei confini si assottigliano
Sezione intitolata “Dove quei confini si assottigliano”Gli strumenti per i file dell’harness vengono eseguiti nel processo del server, non
in un processo figlio, quindi nessun profilo di sandbox potrebbe vincolarli anche se
ne fosse applicato uno — solo bash passa dall’esecutore di comandi nel sandbox. Il
loro confinamento è il controllo dei percorsi descritto sopra, e quel controllo è
quindi portante anziché una seconda linea di difesa.
Lo strumento bash è l’altro punto in cui essere precisi. I suoi comandi vengono
valutati rispetto alla policy dei comandi della modalità e poi avviati con un ambiente
ripulito e il worktree come directory di lavoro. Dove esiste un backend, quell’avvio è
incapsulato, quindi è il sandbox che impedisce a un comando shell di scrivere fuori
dal worktree; dove non ne esiste alcuno — Windows, o Linux senza bwrap e socat —
la policy dei comandi è l’unica cosa tra il comando e il resto del disco. In modalità
chat la policy nega in modo rigido un elenco fisso (escalation di privilegi,
distruzione di sistema e disco, fuga da container e namespace) e chiede conferma per
le mutazioni dello stato remoto (git push, gh pr create, pubblicazioni e
installazioni di pacchetti). Qualsiasi cosa non presente in nessuno dei due elenchi
viene eseguita. Le pipeline, le catene e le subshell vengono analizzate e ciascun
sotto-comando valutato separatamente, con la decisione più restrittiva che vince.
Claude Code, quando è usato come adapter, è il caso in cui il sandbox porta il peso
maggiore. Control Center lo avvia con --dangerously-skip-permissions, perché un
claude -p non interattivo si bloccherebbe altrimenti per sempre sul proprio prompt
di approvazione. Le sue chiamate di lettura, scrittura e modifica avvengono dentro il suo processo e Control Center non le vede mai. Le sue chiamate Bash vengono inviate, prima dell’esecuzione, a un hook sul server che classifica la riga di comando e interroga la regola corrispondente, e gli strumenti mcp__* serviti da Control Center passano dal varco come sempre.
Dove esiste un backend, quelle chiamate invisibili avvengono comunque dentro
l’incapsulamento del sandbox, quindi il sistema operativo è il confine che le
circonda. Dove non ne esiste alcuno, non c’è nulla. Ogni adapter lo dichiara nella
propria matrice di applicazione — vedi la matrice di onestà degli
adapter.
I push passano da Control Center
Sezione intitolata “I push passano da Control Center”Non esistono flag di permesso per agente. Se un agente può creare un commit, eseguire un push, aprire una pull request, scrivere su un tracker o raggiungere la rete lo decidono i guardrail — consenti, chiedi prima o nega — e le stesse regole valgono qualunque sia l’adapter che esegue l’agente.
Il push è l’azione che deve reggere anche contro un agente che prova ad aggirare
una regola, quindi viene applicato in base a ciò che l’agente possiede, non a
ciò che esegue. Con una GitHub App configurata, un’esecuzione in un worktree
GitHub riceve un token di installazione limitato al repo che può leggere i
contenuti e lavorare con le pull request ma non eseguire push. La
configurazione git dell’esecuzione riscrive ogni URL di push GitHub
(https://github.com/, git@github.com:, ssh://git@github.com/) verso il
gateway delle esecuzioni degli agenti, un endpoint di loopback su
cc_server. Il gateway legge quali ref il push aggiorna, interroga la regola
Eseguire il push a un remoto — quindi si applica anche una regola limitata ai
branch, come «nega i push su main» — e solo allora inoltra il push a GitHub
con un token di scrittura che conia apposta e non consegna mai all’agente. Un
push rifiutato compare nell’output di git stesso:
! [remote rejected] main -> main (Control Center policy: …)Il gateway inoltra i push solo al repo del worktree o a un repo GitHub collegato allo spazio di lavoro. I fetch vanno direttamente a GitHub con il token di lettura.
Gli altri comandi di shell incontrano un varco più morbido. Lo strumento bash
dell’harness e, tramite un hook, lo strumento Bash di Claude Code classificano
ogni riga di comando e interrogano la regola a cui appartiene: git commit,
merge, rebase, cherry-pick, revert e am sono Creare un commit;
gh pr create è Aprire una pull request; gh pr merge, review,
comment e close sono Pubblicare una revisione o unire;
gh issue create, edit, comment e close sono Scrivere su un tracker
esterno; le installazioni di pacchetti sono Installare un pacchetto;
curl e wget sono Accedere alla rete. Questo riconosce il modo
convenzionale di scrivere un’azione, e un agente deciso a nasconderne una
(eval, un file di script, una chiamata API diretta) ci riesce. I push non
dipendono da questo.
In un sandbox non viene messa alcuna chiave API di ticketing. Gli agenti scrivono i ticket tramite gli strumenti MCP di Control Center, che interrogano Scrivere su un tracker esterno.
La regola Accedere alla rete viene letta una sola volta, all’avvio
dell’esecuzione. La rete di un sandbox è attiva o disattivata, quindi nega la
disattiva, mentre consenti e chiedi prima la lasciano attiva; gli strumenti
web_fetch e web_search dell’harness continuano a chiedere a ogni chiamata.
Le credenziali vengono revocate quando l’esecuzione viene smantellata, insieme a
ogni token di scrittura coniato dal gateway, quindi un handle non sopravvive
all’esecuzione.
Cosa aggiunge il sandbox e dove si ferma
Sezione intitolata “Cosa aggiunge il sandbox e dove si ferma”L’enum SandboxBackend ha tre valori — native (Seatbelt su macOS, bubblewrap su
Linux e WSL2), none e microvm. L’incapsulamento dei comandi dell’agente usa solo i
primi due: microvm è il backend a VM isolata per terminali interattivi e rig, non un
aggiornamento silenzioso di un incapsulamento di bash. Capire la forma del native
spiega cosa guadagni dove ne è applicato uno e cosa non ti avrebbe mai dato.
cc_server sonda l’host una volta all’avvio e, dove un backend risponde, alimenta
entrambi i punti di innesto del percorso di invio, perché i trasporti non ne
condividono uno. Il primo (SandboxPort) incapsula la CLI di Claude Code. Il secondo
(SandboxManager) incapsula gli adapter ACP e lo strumento bash dell’harness
integrato. Alimentarne solo uno lascerebbe gli altri ad avviarsi senza incapsulamento,
che è quello che succedeva prima. Su un host senza backend, o con --sandbox off, si
avviano tutti senza incapsulamento e il server dice in quale caso ti trovi sulla
propria riga di avvio. ACP e il bash dell’harness ripuliscono comunque l’ambiente;
Claude Code tramite NoSandboxAdapter eredita l’ambiente dell’host.
Una superficie si esclude deliberatamente: il terminale ospitato sul server accetta un
argomento di backend per sessione e cc_server lo costruisce con il valore predefinito
della shell dell’host, quindi un terminale che apri dall’app viene eseguito senza
sandbox a meno che un client non indichi esplicitamente native. Quella è una vera
shell sull’host del server, non un’esecuzione di agente.
Su Linux, bubblewrap costruisce un namespace di mount, quindi l’agente vede davvero
solo ciò che è stato montato al suo interno. Su macOS, sandbox-exec applica un
profilo Seatbelt permissivo per impostazione predefinita — un profilo completamente
deny-by-default rende inutilizzabile una shell interattiva, perché il numero di
chiamate dyld, XPC e Mach da enumerare è impraticabile. Il profilo macOS parte quindi
da (allow default) e ritaglia dei dinieghi: letture di percorsi segreti, una policy
di scrittura riportata a deny con allow espliciti in aggiunta, blocco di unlink su ogni
percorso negato e sui suoi antenati così che una rinomina non possa aggirare un diniego
di scrittura, dinieghi di exec per binari sempre pericolosi e per qualsiasi cosa
copiata in una directory scrivibile, e rete limitata alle porte proxy di loopback più
il DNS.
La conseguenza pratica: su Linux la visione del filesystem da parte dell’agente è ristretta; su macOS l’agente può ancora leggere gran parte dell’host e i tuoi checkout registrati sono protetti da dinieghi espliciti di scrittura e rinomina anziché dall’invisibilità. Entrambi sono isolamento a livello di namespace, non un confine di sicurezza del kernel — i backend lo dichiarano nelle proprie note sulle capacità.
Il set di bind mount è dove viene tracciato il confine tra agenti. L’invio overlay
(Claude Code e ACP) monta fino a quattro percorsi, risolti per esecuzione: la
directory di lavoro overlay dell’agente in lettura-scrittura, la directory di
configurazione dell’agente in sola lettura, la directory repos/ condivisa della
conversazione in lettura-scrittura quando esiste, e la directory degli allegati dello
spazio in sola lettura quando esiste. La directory di un agente fratello non viene mai
montata. L’incapsulamento di bash dell’harness è più ristretto: monta in bind solo la
directory di lavoro dell’esecuzione. Questa è la stessa forma che il controllo dei
percorsi in-process dell’harness applica via software, quindi i due concordano — ma
solo il controllo dei percorsi copre gli strumenti per i file dell’harness stesso, che
non raggiungono mai un namespace di mount.
I tuoi checkout registrati si aggiungono all’insieme di diniego della scrittura su
tutto questo, in ogni modalità e su tutti e tre i percorsi incapsulati (Claude Code,
ACP, bash dell’harness), e ogni regola viene emessa sia nella sua grafia letterale
sia in quella risolta dai symlink. La seconda grafia non è un vezzo:
il kernel confronta una regola di diniego
con il percorso risolto e macOS rende la risoluzione il caso comune anziché quello
esotico — /tmp in realtà è /private/tmp, /var/folders/… in realtà è
/private/var/folders/… — quindi un checkout raggiunto attraverso l’una o l’altra
sarebbe stato scrivibile mentre il profilo sembrava proibirlo.
La rete, sotto un sandbox, è una lista di autorizzazione per dominio per sessione,
applicata da proxy di loopback in-process. L’uscita è negata per impostazione
predefinita: con la rete disattivata, i proxy si avviano comunque ma la loro
lista di autorizzazione è vuota, quindi ogni connessione in uscita viene
rifiutata. Con la rete attiva, il resolver costruisce la lista di autorizzazione
da una base di riferimento curata — provider di API LLM, hosting git, registry
dei pacchetti, il catalogo dei modelli — più i domini di GitHub, e confronta con
essa l’host di destinazione di ogni connessione. I caratteri jolly funzionano
(*.example.com corrisponde ai sottodomini, non al dominio radice). Un elenco
separato, sempre negato, prevale sulla lista di autorizzazione e copre gli
endpoint di metadati cloud e le destinazioni di telemetria. Ciò che non è
configurabile è la lista stessa: la specifica porta un campo egressAllowlist
per domini aggiuntivi, ma nulla nel percorso di invio lo popola, quindi in
pratica ogni esecuzione ottiene la base di riferimento più GitHub e nulla di
più.
Le negazioni, su macOS, sono osservabili: un monitor intercetta il log di sistema alla
ricerca dei dinieghi del Sandbox, filtra il rumore noto, deduplica le tempeste di
tentativi e le presenta ciascuna come una SandboxViolation che nomina ciò che è stato tentato e ciò che è stato bloccato. Su Linux il percorso di incapsulamento
analizza stderr alla ricerca di EPERM, Operation not permitted e
Permission denied ed emette lo stesso tipo di evento — non c’è alcuna intercettazione
del log di sistema. Quelli arrivano sul flusso di eventi dell’esecuzione stessa, quindi
una syscall negata compare nella trascrizione anziché solo in un log. La telemetria
esiste solo mentre un sandbox nativo sta effettivamente incapsulando il processo — su
un host senza backend non c’è nulla da osservare. È anche estranea ai guardrail: una violazione deriva da una syscall negata, mai dalla consultazione di una regola.
Le conferme vengono dai guardrail, non dal sandbox
Sezione intitolata “Le conferme vengono dai guardrail, non dal sandbox”Il sandbox non ha alcun percorso di approvazione. Si avvia, esegue, emette eventi e si smantella; non c’è posto al suo interno perché un umano risponda a una domanda. Quando vedi una conferma in uno spazio — approva questo push, approva questa eliminazione — è stata sollevata dal motore dei guardrail (o dalla policy dei comandi dell’harness) prima che l’azione raggiungesse del tutto il runtime.
Questo ordine è deliberato. Un’approvazione che avvenisse dopo la syscall sarebbe un record di controllo, non un varco. È anche il motivo per cui la regola fail-closed vive nei guardrail: senza alcun approvatore collegato, la decisione viene presa prima che qualsiasi cosa venga eseguita ed è un diniego.
Diagnostica
Sezione intitolata “Diagnostica”Esegui la diagnostica di un agente è l’unico posto che dichiara cosa si può controllare a mano.
Concetti correlati
Sezione intitolata “Concetti correlati”- Rig e recinti: il gradino sopra il sandbox — quando il lavoro richiede un’intera macchina usa e getta con un confine del kernel, non un processo incapsulato
- Guardrail: lo strato di policy sopra il pavimento del sistema operativo e da dove vengono davvero le approvazioni
- Ciclo di vita dell’invio degli agenti: dove si colloca l’isolamento nel flusso di invio
- Modalità: come una modalità restringe la superficie degli strumenti e la policy dei comandi
- Riferimento sui backend del sandbox: i backend e la loro configurazione
Guide correlate
Sezione intitolata “Guide correlate”- Configura le policy del sandbox: regole delle azioni, applicazione dei push e postura del filesystem
- Esegui la diagnostica di un agente: cosa coprono i controlli diagnostici