Pular para o conteúdo

Backends de sandbox

Backend Valor Plataforma Disponibilidade
VM fechada microvm macOS, Linux, Windows (hipervisor de microVM) Condicionado à sondagem do runtime de microVM; aplica-se a terminais interativos — o shell roda dentro de um rig, não no host
Nativo native macOS (Seatbelt), Linux e WSL2 (bubblewrap) Sondado na inicialização; o Linux recusa uma execução que exija negações de escrita por glob de nome de arquivo, que o bubblewrap não consegue aplicar
Nenhum none Todos O padrão da análise, o padrão do terminal e o fallback quando nenhum backend está disponível ou --sandbox off está definido

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

microvm é o único backend com fronteira de kernel — a carga de trabalho genuinamente não roda no host. Ele nunca é um fallback silencioso em nenhuma das direções: pedi-lo explicitamente em um host que não consegue inicializar uma VM falha de forma ruidosa (“pedi uma VM e recebi meu notebook” é a única degradação que o recinto não pode se permitir), e apenas um padrão não especificado recorre a native, com uma linha de log dizendo isso. O backend real da sessão é reportado de volta ao cliente e recebe um selo, então um terminal nunca pode alegar estar fechado enquanto roda no host. Ciclo de vida, imagens e saída de rede da própria VM estão cobertos pela referência de rigs.

O Windows não tem sandbox nativo. NativeSandboxAdapter.probe retorna available: false em todo host que não seja macOS ou Linux/WSL2. O despacho de agentes no Windows, portanto, usa none. O backend microvm é separado: ele é condicionado à sondagem do runtime de microVM e se aplica a terminais interativos e rigs, não a envolver o bash no host.

A sondagem é o que condiciona a aplicação. Ela roda uma vez, na inicialização do servidor.

Host 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 uma das ferramentas false requiresInstall: true, com uma dica de instalação que nomeia os pacotes ausentes
Qualquer outra plataforma false O sandbox nativo não é oferecido nesta plataforma

O servidor registra o resultado em uma única linha de inicialização: agent sandbox ON mais a nota, no OS-native agent sandbox on this host mais a dica, ou agent sandbox DISABLED by --sandbox off. GET /healthz não carrega nenhum campo de sandbox.

--sandbox / CC_SERVER_SANDBOX aceita on (padrão) ou off. É uma desativação explícita, não uma habilitação: on significa “envolver quando o host oferecer um backend”, nunca “exigir um”.

Usa o mecanismo de sandbox Seatbelt da Apple (o mesmo sistema usado pelo App Sandbox do macOS).

O perfil gerado é permissivo por padrão ((allow default)) e recorta negações, porque um perfil totalmente de negação por padrão torna um shell interativo inutilizável — há chamadas demais de dyld, XPC e Mach para enumerar. A consequência prática é que um agente ainda consegue ler a maior parte do host; seus checkouts registrados são protegidos por negações explícitas de escrita e de renomeação, não por invisibilidade.

  • Leituras do sistema de arquivos: negadas para os caminhos secretos (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh, ~/Library/Keychains e o resto do conjunto de leitura negada)
  • Escritas no sistema de arquivos: redefinidas para negar e depois permitidas em subcaminhos explícitos, com os caminhos de negação obrigatória (arquivos rc do shell, .git/hooks, a configuração do Claude, checkouts protegidos) bloqueados mesmo dentro de uma raiz gravável. file-write-unlink é negado em todo caminho negado e em seus ancestrais, de modo que uma renomeação não pode contornar uma negação de escrita
  • Processos: negações de process-exec para os binários sempre perigosos, além de 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 irrestrita e sockets Unix não são permitidos. Os proxies aplicam a lista de permissões de domínios por sessão (veja Saída de rede)
  • Configuração: os perfis são gerados a cada inicialização a partir do SandboxSpec

Requisitos:

  • sandbox-exec (/usr/bin/sandbox-exec), presente em toda instalação do macOS — nenhuma ferramenta adicional é necessária

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

  • Sistema de arquivos: cria um namespace de montagem com apenas os caminhos vinculados visíveis
  • Rede: roda em um namespace de rede não compartilhado; o tráfego chega aos proxies do host por pontes de socket Unix do socat
  • Processos: isolamento de namespace de PID

Requisitos:

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

O bubblewrap consegue ocultar um caminho existente, mas não consegue proibir um arquivo .env, *.key ou *.pem novo dentro de um bind gravável. A política de despacho padrão exige essas negações por glob de nome de arquivo, então o despacho nativo de agentes hoje falha de forma fechada no Linux/WSL2 mesmo quando a sondagem encontra o bubblewrap. Ele não roda com uma política silenciosamente mais fraca. É preciso um backend com regras de nome de arquivo no momento da criação para restaurar o despacho nativo no Linux. Desativar o sandbox é uma desativação explícita e sem confinamento, não uma alternativa equivalente.

Nenhum sandbox. O processo roda diretamente no host, com o acesso a sistema de arquivos, rede e processos do host. O ACP e a ferramenta bash do harness ainda recebem a sanitização do ambiente e a política de comandos; o Claude Code pelo NoSandboxAdapter herda o ambiente do host. É para isso que o despacho de agentes recorre em um host sem backend disponível ou com --sandbox off, e é o padrão do terminal quando um cliente não nomeia nenhum.

O terminal roda em sandbox nos próprios termos, não pela sondagem do despacho. O defaultBackend de TerminalSessionService é none e o cc_server não o sobrescreve, então uma sessão de terminal executa o shell do host a menos que o cliente peça native explicitamente — o painel de terminal do desktop não pede.

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

Quando um sandbox é iniciado, o acesso de rede de saída é aplicado por proxies em processo, não por regras de firewall do kernel. Em um host sem backend disponível nada disso se aplica, e um processo gerado chega à rede exatamente como o próprio usuário do servidor chegaria.

  • O runtime do sandbox executa 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). Execuções simultâneas não compartilham um par.
  • 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 git-over-SSH pelo proxy SOCKS). No Linux o namespace de rede não é compartilhado, então os proxies são a única saída; no macOS o sandbox compartilha o loopback do host e fala com eles diretamente.
  • O host de destino de cada conexão é comparado com a lista de permissões de domínios da sessão; qualquer outra coisa é negada. Há duas formas de curinga, cada uma com exatamente um *: um *.example.com no início casa com qualquer subdomínio (incluindo a.b.example.com), mas não com o próprio domínio raiz; um bedrock.*.amazonaws.com com rótulo no meio casa exatamente com 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: provedores de LLM, hospedagem de Git, registros de pacotes), mais os domínios do GitHub (kGithubDomains), mais SandboxSpec.egressAllowlist. O caminho de despacho nunca passa entradas extras de egressAllowlist, então na prática toda execução recebe a base mais o GitHub.
  • Uma lista fixa de negação (kBaselineDeniedDomains: endpoints de metadados de nuvem, destinos de telemetria) tem precedência sobre todas as permissões.
  • networkEnabled: false ainda inicia os proxies por sessão, mas a lista de permissões fica vazia, então toda conexão de saída é recusada. O despacho o define quando a execução começa a partir da regra Acessar a rede: negar desliga a rede; permitir e perguntar primeiro a mantêm ligada.

A especificação passada para 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 vinculado ao sandbox, se houver
bindMounts List<SandboxBindMount> Montagens host→guest; o sandbox vê apenas os caminhos do host listados aqui
networkEnabled bool (padrão true) Definido no despacho a partir da regra de saída de rede (desligado só quando ela resolve para negar). Quando desativado, os proxies por sessão ainda iniciam, mas a lista de permissões fica vazia, então toda conexão de saída é recusada
egressAllowlist List<String> (padrão []) Domínios de saída adicionais permitidos; curingas são suportados (*.example.com, além de uma forma intermediária de um único rótulo). O despacho nunca preenche isto, então uma execução envolvida recebe a base curada mais os domínios do GitHub
guestWorkdir String? Diretório de trabalho padrão dentro do sandbox; chamadas exec individuais podem sobrescrevê-lo
mode Mode (padrão chat) Recorta as regras de escrita do sistema de arquivos: chat mantém as montagens bind graváveis; review, plan e orchestrate são somente leitura
protectedPaths List<String> (padrão []) Caminhos do host que nunca podem ser graváveis dentro do sandbox, em nenhum modo
runnerStateDirs List<String> (padrão []) Diretórios do host onde o runner mantém o próprio estado, graváveis em todos os modos (um CLAUDE_CONFIG_DIR gerenciado pelo Control Center é o atual)
execGrantRoots List<String> (padrão []) Árvores de diretórios aprovadas pelo operador que reabrem o bloqueio de exec em diretórios graváveis no macOS. O Linux as ignora — o bwrap não tem nenhum bloqueio de exec em $HOME a reabrir

protectedPaths contém os checkouts originais dos repos registrados, resolvidos a cada despacho a partir dos repos do espaço de trabalho. Os agentes trabalham exclusivamente em worktrees de copy-on-write por conversa, então os originais nunca podem ser graváveis de dentro do sandbox — no macOS, a permissão geral de escrita do Seatbelt sobre $HOME os deixaria graváveis via bash caso contrário. Eles se juntam ao conjunto de negação de escrita de segredos em todos os modos, chat incluído.

Todo caminho de leitura negada e de escrita negada é emitido tanto na sua forma literal quanto na sua forma resolvida por symlink. O kernel casa uma regra de negação com o caminho resolvido, então uma regra escrita contra um caminho com symlink nunca dispara — e no macOS esse é o caso comum, e não o exótico, já que /tmp resolve para /private/tmp e /var/folders/… para /private/var/folders/…. Entradas que contêm * ou ? são tratadas como globs e deixadas como estão; um caminho que não resolve é mantido como escrito.

Campo Tipo Descrição
hostPath String Caminho absoluto no host
guestPath String Caminho dentro do sandbox (com o sandbox nativo isto é igual a hostPath; mantido por simetria entre adaptadores)
readOnly bool (padrão false) Monta como somente leitura quando verdadeiro

Retornado por SandboxPort.launch:

Campo Tipo Descrição
sessionId String Identificador da sessão
backend SandboxBackend Qual backend está rodando
state SandboxState Estado de ciclo de vida
error Object? Definido quando state é error
details Map<String, Object?> Contabilidade interna do adaptador (diretório de trabalho, PID, caminho do perfil etc.)
Estado Descrição
created Especificação criada, mas a VM ainda não iniciou
warm Em execução e pronta para executar comandos
active Executando um comando no momento
suspended Pausado / com checkpoint em disco para economizar RAM
destroyed Desmontado; o handle não é mais válido
error A inicialização falhou — veja SandboxHandle.error
Tipo de evento Descrição
stdout Saída padrão do sandbox
stderr Erro padrão do sandbox
exit O processo do sandbox saiu (carrega exitCode)
starting O sandbox está iniciando
ready O sandbox está pronto para aceitar chamadas exec
killed Processo morto pelo host (OOM, timeout, parada manual)
violation Uma política do sandbox foi violada (carrega um SandboxViolation)
Campo Tipo Descrição
action String O verbo negado (por exemplo, file-read*, network-outbound)
target String Caminho ou host que foi alvo da operação negada
raw String? Linha de log bruta, útil para depuração