Saltar para o conteúdo

Backends de sandbox

Backend Valor Plataforma Disponibilidade
VM fechada microvm macOS, Linux, Windows (hipervisor de microVM) Condicionado pela sonda ao runtime do microVM; aplica-se a terminais interativos — a shell corre dentro de um rig, não no anfitrião
Nativo native macOS (Seatbelt), Linux e WSL2 (bubblewrap) Sondado no arranque; o Linux recusa uma execução que exija negações de escrita por glob de nome de ficheiro, que o bubblewrap não consegue impor
Nenhum none Todas O predefinido de análise, o predefinido do terminal e a alternativa quando não há nenhum backend disponível ou está definido --sandbox off

SandboxBackend.fromName devolve none para um valor armazenado nulo ou não reconhecido. Um "docker" armazenado é mapeado para native.

O microvm é o único backend com uma fronteira de kernel — a carga de trabalho não corre genuinamente no anfitrião. Nunca é uma alternativa silenciosa em nenhum dos sentidos: pedi-lo explicitamente num anfitrião que não consegue iniciar uma VM falha ruidosamente («pedi uma VM e fiquei com o meu portátil» é a única degradação que o recinto não se pode permitir), e só um predefinido não especificado recua para native, com uma linha de registo a dizê-lo. O backend efetivo da sessão é comunicado de volta ao cliente e assinalado com um distintivo, pelo que um terminal nunca pode afirmar estar fechado enquanto corre no anfitrião. O ciclo de vida, as imagens e a saída da própria VM são abordados na referência de rigs.

O Windows não tem sandbox nativa. NativeSandboxAdapter.probe devolve available: false em todos os anfitriões que não sejam macOS ou Linux/WSL2. O despacho de agentes no Windows usa, por isso, none. O backend microvm é separado: é condicionado pela sonda ao runtime do microVM e aplica-se a terminais interativos e rigs, não ao envolvimento do bash no anfitrião.

É a sonda que condiciona a imposição. Corre uma única vez, no arranque do servidor.

Anfitrião available Nota / dica
macOS true sandbox-exec (Seatbelt) — isolamento de namespace, sem fronteira de kernel
Linux ou WSL2 com bwrap e socat true bubblewrap — isolamento de namespace, sem fronteira de kernel
Linux ou WSL2 sem qualquer das ferramentas false requiresInstall: true, com uma dica de instalação que nomeia os pacotes em falta
Qualquer outra plataforma false A sandbox nativa não é oferecida nesta plataforma

O servidor regista o resultado numa única linha de arranque: agent sandbox ON mais a nota, no OS-native agent sandbox on this host mais a dica, ou agent sandbox DISABLED by --sandbox off. O GET /healthz não transporta nenhum campo de sandbox.

--sandbox / CC_SERVER_SANDBOX aceita on (predefinido) ou off. É uma forma de desativar, não de ativar: on significa «envolver quando o anfitrião oferece um backend», nunca «exigir um».

Utiliza o mecanismo de sandbox Seatbelt da Apple (o mesmo sistema utilizado pela App Sandbox do macOS).

O perfil gerado é permissivo por predefinição ((allow default)) e recorta negações, porque um perfil totalmente deny-by-default torna uma shell interativa inutilizável — há demasiadas chamadas dyld, XPC e Mach para enumerar. A consequência prática é que um agente ainda consegue ler a maior parte do anfitrião; os seus checkouts registados estão protegidos por negações explícitas de escrita e de renomeação, e não por invisibilidade.

  • Leituras do sistema de ficheiros: negadas para os caminhos secretos (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh, ~/Library/Keychains e o resto do conjunto de negação de leitura)
  • Escritas no sistema de ficheiros: repostas em negação e depois autorizações explícitas por subcaminho, com os caminhos de negação obrigatória (ficheiros rc da shell, .git/hooks, configuração do Claude, checkouts protegidos) bloqueados mesmo dentro de uma raiz gravável. O file-write-unlink é negado em todos os caminhos negados e nos seus ascendentes, pelo que uma renomeação não consegue contornar uma negação de escrita
  • Processos: negações de process-exec para os binários sempre perigosos, mais um bloqueio à execução de qualquer coisa a partir de um diretório gravável
  • Rede: restrita às portas do proxy em processo mais o DNS — o loopback não é permitido de forma indiscriminada e os sockets Unix não são permitidos. Os proxies impõem a lista de permissões de domínios por sessão (ver Saída de rede)
  • Configuração: os perfis são gerados em cada lançamento a partir do SandboxSpec

Requisitos:

  • sandbox-exec (/usr/bin/sandbox-exec), parte de qualquer instalação do macOS — não são necessárias ferramentas adicionais

Utiliza o bubblewrap (bwrap) para criar namespaces de montagem e de PID.

  • Sistema de ficheiros: cria um namespace de montagem onde só os caminhos associados estão visíveis
  • Rede: corre num namespace de rede não partilhado; o tráfego chega aos proxies do anfitrião através de pontes de socket Unix do socat
  • Processos: isolamento por namespace de PID

Requisitos:

  • bubblewrap e socat instalados (sudo apt-get install bubblewrap socat no Debian/Ubuntu)
  • Suporte de namespaces no kernel (padrão no Linux moderno)

O Bubblewrap consegue ocultar um caminho existente, mas não consegue proibir um ficheiro novo .env, *.key ou *.pem dentro de uma associação gravável. A política de despacho padrão exige estas negações por glob de nome de ficheiro, pelo que o despacho nativo de agentes atualmente falha de forma fechada no Linux/WSL2 mesmo quando a sonda encontra o bubblewrap. Não corre com uma política silenciosamente mais fraca. É necessário um backend com regras de nome de ficheiro no momento da criação para restaurar o despacho nativo no Linux. Desativar o isolamento numa sandbox é uma desativação explícita e sem confinamento, não um contorno equivalente.

Sem isolamento numa sandbox. O processo corre diretamente no anfitrião, com o acesso deste ao sistema de ficheiros, à rede e aos processos. O ACP e a ferramenta bash do harness continuam a ter saneamento do ambiente e a política de comandos; o Claude Code através do NoSandboxAdapter herda o ambiente do anfitrião. É para isto que o despacho de agentes recua num anfitrião sem nenhum backend disponível ou com --sandbox off, e é o predefinido do terminal quando um cliente não indica nenhum.

O terminal é isolado numa sandbox segundo regras próprias, e não pela sonda de despacho. O defaultBackend do TerminalSessionService é none e o cc_server não o substitui, pelo que uma sessão de terminal corre a shell do anfitrião a menos que o cliente peça explicitamente native — o painel de terminal do desktop não o faz.

Quando é pedido native, a shell é envolvida através do SandboxManager sob um perfil de terminal fixo: o $HOME e o diretório de trabalho da sessão são graváveis, a rede está aberta e é negada a leitura de ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh e ~/Library/Keychains. O token de forge só de leitura e o gateway de push não se aplicam — são questões de despacho. Se o envolvimento não for suportado, a sessão falha em vez de mostrar um distintivo nativo num PTY com a shell do anfitrião.

Quando uma sandbox é lançada, o acesso de rede de saída é imposto por proxies em processo, e não por regras de firewall do kernel. Num anfitrião sem nenhum backend disponível nada disto se aplica e um processo lançado alcança a rede exatamente como o faria o utilizador do servidor.

  • O runtime da sandbox corre um proxy HTTP/HTTPS e um proxy SOCKS5 em 127.0.0.1 dentro do processo do cc_server, um par por sessão restrita (http_proxy.dart, socks_proxy.dart). As execuções simultâneas não partilham um par.
  • Os processos em sandbox recebem variáveis de ambiente de proxy (HTTP_PROXY/HTTPS_PROXY → o proxy HTTP, ALL_PROXY → o proxy SOCKS; GIT_SSH_COMMAND encaminha o git por SSH através do proxy SOCKS). No Linux, o namespace de rede não é partilhado, pelo que os proxies são a única saída; no macOS, a sandbox partilha o loopback do anfitrião e fala diretamente com eles.
  • O anfitrião de destino de cada ligação é comparado com a lista de permissões de domínios da sessão; tudo o resto é negado. Há duas formas de wildcard, cada uma com exatamente um *: um *.example.com à esquerda corresponde a qualquer subdomínio (incluindo a.b.example.com), mas não ao domínio de topo em si; um bedrock.*.amazonaws.com com o wildcard no meio corresponde exatamente a um rótulo (bedrock.us-east-1.amazonaws.com, não bedrock.evil.attacker.amazonaws.com).
  • A lista de permissões é resolvida por sessão a partir da base curada (kBaselineAllowedDomains: fornecedores de LLM, alojamento de Git, registos de pacotes), mais os domínios do GitHub (kGithubDomains), mais o SandboxSpec.egressAllowlist. O percurso de despacho nunca passa entradas egressAllowlist adicionais, pelo que, na prática, todas as execuções recebem a base mais o GitHub.
  • Uma lista fixa de negações (kBaselineDeniedDomains: pontos finais de metadados da cloud, destinos de telemetria) tem precedência sobre todas as autorizações.
  • networkEnabled: false continua a iniciar os proxies por sessão, mas a lista de permissões fica vazia, pelo que todas as ligações de saída são recusadas. O despacho define-o quando a execução arranca a partir da regra Aceder à rede: negar desliga a rede; permitir e perguntar primeiro mantêm-na ligada.

A especificação passada a SandboxPort.launch:

Campo Tipo Descrição
sessionId String Identificador estável da sessão (normalmente o id do espaço)
workspaceId String Id do espaço de trabalho proprietário
agentId String? Agente atualmente associado à sandbox, se existir
bindMounts List<SandboxBindMount> Montagens anfitrião→convidado; a sandbox só vê os caminhos do anfitrião aqui listados
networkEnabled bool (predefinido true) Definido no despacho a partir da regra de saída de rede (desligado só quando esta resolve para negar). Quando está desligado, os proxies por sessão ainda arrancam, mas a lista de permissões fica vazia, pelo que todas as ligações de saída são recusadas
egressAllowlist List<String> (predefinido []) Domínios de saída adicionais permitidos; wildcards suportados (*.example.com, mais uma forma intermédia com um rótulo). O despacho nunca preenche isto, pelo que uma execução envolvida recebe a base curada mais os domínios do GitHub
guestWorkdir String? Diretório de trabalho predefinido dentro da sandbox; cada chamada exec pode substituí-lo
mode Mode (predefinido chat) Recorta as regras de escrita no sistema de ficheiros: chat mantém as montagens bind graváveis; review, plan e orchestrate são só de leitura
protectedPaths List<String> (predefinido []) Caminhos do anfitrião que nunca podem ser graváveis dentro da sandbox, em nenhum modo
runnerStateDirs List<String> (predefinido []) Diretórios do anfitrião onde o runner guarda o seu próprio estado, graváveis em todos os modos (o atual é uma CLAUDE_CONFIG_DIR gerida pelo Control Center)
execGrantRoots List<String> (predefinido []) Árvores de diretórios aprovadas pelo operador que reabrem o bloqueio de execução em diretórios graváveis no macOS. O Linux ignora-as — o bwrap não tem nenhum bloqueio de execução no $HOME para reabrir

protectedPaths contém os checkouts originais dos repos registados, resolvidos em cada despacho a partir dos repos do espaço de trabalho. Os agentes trabalham exclusivamente em worktrees copy-on-write por conversa, pelo que os originais nunca podem ser graváveis a partir do interior da sandbox — no macOS, a autorização de escrita indiscriminada do Seatbelt sobre o $HOME deixá-los-ia, de outro modo, graváveis através do bash. Juntam-se ao conjunto de negação de escrita dos segredos em todos os modos, incluindo o chat.

Todos os caminhos de negação de leitura e de negação de escrita são emitidos tanto na sua grafia literal como na grafia resolvida por ligações simbólicas. O kernel compara uma regra de negação com o caminho resolvido, pelo que uma regra escrita contra um caminho com ligação simbólica nunca dispara — e no macOS esse é o caso comum e não o exótico, já que o /tmp resolve para /private/tmp e o /var/folders/… para /private/var/folders/…. As entradas que contêm * ou ? são tratadas como globs e deixadas como estão; um caminho que não resolve é mantido tal como escrito.

Campo Tipo Descrição
hostPath String Caminho absoluto no anfitrião
guestPath String Caminho dentro da sandbox (com a sandbox nativa, é igual a hostPath; mantido por simetria entre adaptadores)
readOnly bool (predefinido false) Monta só de leitura quando é true

Devolvido por SandboxPort.launch:

Campo Tipo Descrição
sessionId String Identificador da sessão
backend SandboxBackend Qual o backend em execução
state SandboxState Estado do ciclo de vida
error Object? Preenchido quando o state é error
details Map<String, Object?> Contabilidade privada do adaptador (diretório de trabalho, PID, caminho do perfil, etc.)
Estado Descrição
created Especificação criada, mas a VM ainda não arrancou
warm Em execução e pronta para executar comandos
active A executar um comando neste momento
suspended Em pausa / com checkpoint para o disco, para poupar RAM
destroyed Desmontada; o handle deixa de ser válido
error O lançamento falhou — ver SandboxHandle.error
Tipo de evento Descrição
stdout Saída padrão da sandbox
stderr Erro padrão da sandbox
exit O processo da sandbox terminou (transporta o exitCode)
starting A sandbox está a arrancar
ready A sandbox está pronta para aceitar chamadas exec
killed Processo terminado pelo anfitrião (OOM, tempo limite, paragem manual)
violation Foi violada uma política da sandbox (transporta um SandboxViolation)
Campo Tipo Descrição
action String O verbo negado (por exemplo, file-read*, network-outbound)
target String Caminho ou anfitrião que foi alvo da operação negada
raw String? Linha de registo em bruto, útil para depuração