Pular para o conteúdo

Sandbox e segurança

Um agente lê arquivos, executa comandos de shell e chama APIs externas em seu nome. A falha com que você se importa raramente é malícia — é um agente que lê errado um caminho e reescreve o seu checkout de trabalho, ou um que faz push de um branch que você não tinha terminado de revisar. Então a pergunta que o produto precisa responder não é “o agente é confiável”, e sim “qual é o raio de impacto quando ele erra”.

O Control Center responde a isso em camadas, e as camadas são independentes de propósito. Se uma delas faltar, as outras continuam valendo. Isso importa, porque a camada mais externa — o sandbox do sistema operacional — é a única condicional: ela depende de o host oferecer um backend, e no Windows nenhum host oferece.

Cinco coisas limitam uma execução de agente em todo host, com ou sem sandbox. Nenhuma delas é uma fronteira de kernel; todas são reais. Onde o host oferece um backend, o sandbox do sistema operacional fica acima delas, e “O que o sandbox acrescenta e onde ele para”, abaixo, cobre o que isso muda.

A própria worktree copy-on-write. Cada conversa provisiona o próprio checkout de cada repositório vinculado, clonado via copy-on-write pelo nativo rift que acompanha o produto, em vez de copiado byte a byte, de modo que o isolamento é barato o suficiente para ser o padrão. O repositório registrado que você adicionou ao espaço de trabalho não é o diretório em que o agente trabalha — duas conversas que tocam o mesmo repositório não veem as edições uma da outra, e nenhuma delas está editando o seu checkout.

Vale precisar a granularidade: a worktree é por conversa, não por agente. Dois agentes na mesma conversa recebem diretórios de trabalho overlay distintos que compartilham o repos/ daquela conversa — o que é justamente o ponto, já que estão colaborando em uma única mudança —, enquanto seus diretórios privados de agente permanecem separados. O copy-on-write é o único backend onde quer que o rift seja distribuído: um sistema de arquivos que não faz reflink, ou um diretório de dados em um volume diferente do repositório, faz a provisão falhar em vez de recorrer a uma alternativa. O Windows é a exceção — lá não existe backend copy-on-write, então o git worktree é o backend, e não uma degradação silenciosa.

Confinamento de caminhos nas ferramentas de arquivo do harness embutido. As ferramentas read, write, edit, apply_patch, search, find e search_files do harness resolvem todo caminho em relação ao diretório de trabalho da execução mais as raízes compartilhadas — o diretório repos/ da conversa, onde as worktrees ficam, e o diretório de anexos do espaço, quando existe — e recusam qualquer coisa que caia fora. Caminhos absolutos só são aceitos se já estavam dentro. Um symlink dentro da worktree apontando para fora dela também é resolvido e recusado. É por isso que um agente não consegue write em ~/.ssh mesmo sem nenhum sandbox em cena.

A superfície de ferramentas do modo. O modo de uma conversa decide quais ferramentas são materializadas para a execução. Nos modos somente leitura (plan, review, orchestrate), as ferramentas de mutação simplesmente não estão no registro, e a política de comandos de shell desses modos nega de saída todo comando de mutação — git commit, git push, npm install, docker e o resto — em vez de pedir confirmação para eles. Uma ferramenta ausente não pode ser chamada, o que é uma garantia mais forte do que uma ferramenta presente e condicionada.

As barreiras de ação unificadas. Toda ferramenta de mutação declara as classes de efeito que pode produzir, e o motor de políticas resolve cada uma como permitir, pedir confirmação ou negar antes de a chamada rodar. Um prompt sem nenhum aprovador conectado resolve como negado — uma pergunta que ninguém consegue responder nunca é um sim. Consulte Barreiras para conhecer o modelo de resolução.

Credenciais intermediadas pelo broker e um ambiente higienizado. As credenciais são emitidas a cada inicialização e revogadas quando a execução termina, e o token do GitHub de uma execução pode ler, mas não fazer push — veja “Os pushes passam pelo Control Center” mais abaixo. ACP, o bash do harness e o Claude Code sob o envelope nativo recebem um ambiente sem os vetores de injeção do vinculador dinâmico e do interpretador (LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS, GIT_SSH_COMMAND e afins) antes de o processo começar. O Claude Code via NoSandboxAdapter herda o ambiente do host.

As ferramentas de arquivo do harness rodam dentro do processo do servidor, não em um processo filho, então nenhum perfil de sandbox poderia restringi-las mesmo que houvesse um anexado — só o bash passa pelo executor de comandos com sandbox. O confinamento delas é a verificação de caminho descrita acima, e essa verificação é, portanto, estrutural, e não uma segunda linha de defesa.

A ferramenta bash é o outro ponto que exige precisão. Seus comandos são avaliados em relação à política de comandos do modo e então iniciados com um ambiente higienizado e a worktree como diretório de trabalho. Onde existe um backend, esse início é envolvido, então é o sandbox que impede um comando de shell de gravar fora da worktree; onde não existe — Windows, ou Linux sem bwrap e socat —, a política de comandos é a única coisa entre o comando e o resto do disco. No modo chat, a política nega sumariamente uma lista fixa (escalada de privilégios, destruição do sistema e do disco, escape de contêiner e de namespace) e pede confirmação em mutações de estado remoto (git push, gh pr create, publicações e instalações de pacotes). Qualquer coisa que não esteja em nenhuma das duas listas roda. Pipelines, encadeamentos e subshells são analisados, e cada subcomando é avaliado separadamente, prevalecendo a decisão mais restritiva.

O Claude Code, quando usado como adaptador, é o caso em que o sandbox carrega o maior peso. O Control Center o inicia com --dangerously-skip-permissions, porque um claude -p não interativo bloquearia para sempre no próprio prompt de aprovação. Suas chamadas de leitura, escrita e edição acontecem dentro do próprio processo, e o Control Center nunca as vê. Suas chamadas Bash são enviadas, antes de rodarem, a um hook no servidor que classifica a linha de comando e pergunta à regra correspondente, e as ferramentas mcp__* que o Control Center serve passam pelo portão como sempre. Onde existe um backend, essas chamadas invisíveis ainda acontecem dentro do envelope do sandbox, então o sistema operacional é a fronteira em volta delas. Onde não existe, nada é. Cada adaptador declara isso na própria matriz de aplicação — consulte a matriz de honestidade dos adaptadores.

Não há flags de permissão por agente. Se um agente pode criar um commit, fazer push, abrir um pull request, escrever em um rastreador ou alcançar a rede é decidido pelas barreiras — permitir, perguntar primeiro ou negar —, e as mesmas regras valem qualquer que seja o adaptador que executa o agente.

O push é a ação que precisa resistir até a um agente que tenta contornar uma regra, então ele é aplicado pelo que o agente tem, e não pelo que ele executa. Com um GitHub App configurado, uma execução em uma worktree do GitHub recebe um token de instalação com escopo de repositório que pode ler conteúdo e trabalhar com pull requests, mas não fazer push. A configuração do git da execução reescreve toda URL de push do GitHub (https://github.com/, git@github.com:, ssh://git@github.com/) para o gateway de execuções de agentes, um endpoint de loopback no cc_server. O gateway lê quais refs o push atualiza, pergunta à regra Fazer push para um remoto — então uma regra limitada a branches, como “negar push para main”, se aplica — e só então encaminha o push ao GitHub com um token de escrita que ele emite para esse fim e nunca entrega ao agente. Um push recusado aparece na própria saída do git:

! [remote rejected] main -> main (Control Center policy: …)

O gateway só encaminha pushes para o repositório da própria worktree ou para um repositório do GitHub vinculado ao espaço de trabalho. Os fetches vão direto ao GitHub com o token de leitura.

Os demais comandos de shell encontram um portão mais brando. A ferramenta bash do harness e, por meio de um hook, a ferramenta Bash do Claude Code classificam cada linha de comando e perguntam à regra em que ela se encaixa: git commit, merge, rebase, cherry-pick, revert e am são Criar um commit; gh pr create é Abrir um pull request; gh pr merge, review, comment e close são Publicar uma revisão ou fazer merge; gh issue create, edit, comment e close são Escrever em um rastreador externo; instalações de pacotes são Instalar um pacote; curl e wget são Acessar a rede. Isso reconhece a grafia convencional de uma ação, e um agente determinado a escondê-la (eval, um arquivo de script, uma chamada direta à API) consegue. Os pushes não dependem disso.

Nenhuma chave de API de tickets é colocada em um sandbox. Os agentes escrevem tickets pelas ferramentas MCP do Control Center, que perguntam a Escrever em um rastreador externo.

A regra Acessar a rede é lida uma única vez, quando a execução começa. A rede de um sandbox está ligada ou desligada, então negar a desliga, enquanto permitir e perguntar primeiro a mantêm ligada; as ferramentas web_fetch e web_search do harness continuam perguntando a cada chamada. As credenciais são revogadas quando a execução é desmontada, junto com qualquer token de escrita que o gateway tenha emitido, então um identificador não sobrevive à execução.

A enumeração SandboxBackend tem três valores — native (Seatbelt no macOS, bubblewrap no Linux e no WSL2), none e microvm. O envelopamento de comandos de agentes usa apenas os dois primeiros: microvm é o backend de VM fechada para terminais interativos e rigs, não uma promoção silenciosa de um envelope de bash. Entender a forma nativa explica o que você ganha onde ela está anexada e o que ela nunca ia lhe dar.

O cc_server sonda o host uma vez na inicialização e, onde um backend responde, alimenta as duas costuras do caminho de despacho, porque os transportes não compartilham uma só. A primeira (SandboxPort) envolve a CLI do Claude Code. A segunda (SandboxManager) envolve adaptadores ACP e a ferramenta bash do harness embutido. Alimentar só uma deixaria as outras iniciando sem envelope, que é o que costumava acontecer. Em um host sem backend, ou sob --sandbox off, todas iniciam sem envelope, e o servidor diz em qual caso você está na sua linha de inicialização. ACP e o bash do harness ainda higienizam o ambiente; o Claude Code via NoSandboxAdapter herda o ambiente do host.

Uma superfície fica deliberadamente de fora: o terminal hospedado no servidor aceita um argumento de backend por sessão, e o cc_server o constrói com o padrão do shell do host, então um terminal que você abre no app roda sem sandbox, a menos que um cliente nomeie native explicitamente. Esse é um shell de verdade no host do servidor, não uma execução de agente.

No Linux, o bubblewrap monta um namespace de montagem, então o agente de fato vê apenas o que foi vinculado nele. No macOS, o sandbox-exec aplica um perfil Seatbelt que é permissivo por padrão — um perfil totalmente de negação por padrão torna um shell interativo inutilizável, porque o número de chamadas de dyld, XPC e Mach a enumerar é impraticável. O perfil do macOS, portanto, parte de (allow default) e recorta negações: leitura de caminhos secretos, uma política de escrita redefinida para negar com permissões explícitas por cima, bloqueio de unlink em todo caminho negado e em seus ancestrais, para que um rename não contorne uma negação de escrita, negações de exec para binários sempre perigosos e para qualquer coisa copiada para um diretório gravável, e rede restrita às portas do proxy de loopback mais o DNS.

A consequência prática: no Linux, a visão do agente sobre o sistema de arquivos é estreitada; no macOS, o agente ainda consegue ler a maior parte do host, e seus checkouts registrados são protegidos por negações explícitas de escrita e rename, e não por invisibilidade. Ambos são isolamento em nível de namespace, não uma fronteira de segurança do kernel — os próprios backends dizem isso nas suas notas de capacidade.

O conjunto de bind mounts é onde a fronteira entre agentes é traçada. O despacho por overlay (Claude Code e ACP) monta até quatro caminhos, resolvidos por execução: o diretório de trabalho overlay do agente em leitura e escrita, o diretório de configuração do agente em somente leitura, o diretório repos/ compartilhado da conversa em leitura e escrita quando existe, e o diretório de anexos do espaço em somente leitura quando existe. O diretório de um agente irmão nunca é montado. O envelope de bash do harness é mais estreito: ele faz bind mount somente do diretório de trabalho da execução. Essa é a mesma forma que a verificação de caminho em processo do harness aplica em software, então as duas concordam — mas só a verificação de caminho cobre as próprias ferramentas de arquivo do harness, que nunca chegam a um namespace de montagem.

Seus checkouts registrados entram no conjunto de negação de escrita por cima disso, em todo modo e nos três caminhos envolvidos (Claude Code, ACP, bash do harness), e cada regra é emitida tanto na sua forma literal quanto na sua forma resolvida por symlink. A segunda forma não é um capricho: o kernel casa uma regra de negação com o caminho resolvido, e o macOS faz da resolução o caso comum, e não o exótico — /tmp na verdade é /private/tmp, /var/folders/… na verdade é /private/var/folders/… —, então um checkout alcançado por qualquer uma das duas seria gravável enquanto o perfil parecia proibi-lo.

A rede, sob um sandbox, é uma lista de permissões de domínio por sessão, aplicada por proxies de loopback em processo. A saída é negada por padrão: com a rede desligada, os proxies ainda sobem, mas a lista de permissões deles está vazia, então toda conexão de saída é recusada. Com ela ligada, o resolvedor monta a lista de permissões a partir de uma base curada — provedores de API de LLM, hospedagem de git, registros de pacotes, o catálogo de modelos —, mais os domínios do GitHub, e compara com ela o host de destino de cada conexão. Curingas funcionam (*.example.com casa subdomínios, não o domínio raiz). Uma lista separada, sempre negada, prevalece sobre a lista de permissões e cobre endpoints de metadados de nuvem e destinos de telemetria. O que não é configurável é a própria lista: a especificação carrega um campo egressAllowlist para domínios extras, mas nada no caminho de despacho o preenche, então na prática toda execução recebe a base mais o GitHub, e nada mais.

As negações, no macOS, são observáveis: um monitor lê o log do sistema em busca de negações do Sandbox, filtra o ruído conhecido, deduplica tempestades de tentativas repetidas e expõe cada uma como um SandboxViolation, nomeando o que foi tentado e o que foi bloqueado. No Linux, o caminho do envelope analisa o stderr em busca de EPERM, Operation not permitted e Permission denied e emite o mesmo tipo de evento — não há leitura do log do sistema. Eles chegam no próprio fluxo de eventos da execução, então uma syscall negada aparece na transcrição, e não apenas em um log. A telemetria só existe enquanto um sandbox nativo está de fato envolvendo o processo — em um host sem backend, não há nada a observar. Ela também não tem relação com as barreiras: uma violação vem de uma syscall negada, nunca de uma consulta de regra.

As confirmações vêm das barreiras, não do sandbox

Seção intitulada “As confirmações vêm das barreiras, não do sandbox”

O sandbox não tem caminho de aprovação. Ele inicia, executa, emite eventos e se desmonta; não há nele nenhum lugar para uma pessoa responder a uma pergunta. Quando você vê uma confirmação em um espaço — aprove este push, aprove esta exclusão —, ela foi levantada pelo motor de barreiras (ou pela política de comandos do harness) antes de a ação sequer chegar ao runtime.

Essa ordem é deliberada. Uma aprovação que acontecesse depois da syscall seria um registro de auditoria, não um portão. É também por isso que a regra de falha fechada mora nas barreiras: sem nenhum aprovador conectado, a decisão é tomada antes de qualquer coisa rodar, e ela é uma negação.

Diagnosticar um agente é o único lugar que enuncia o que pode ser verificado à mão.

  • Rigs e recintos: o degrau acima do sandbox — quando o trabalho precisa de uma máquina descartável inteira com uma fronteira de kernel, não de um processo envolvido
  • Barreiras: a camada de políticas acima do piso do sistema operacional e de onde as aprovações de fato vêm
  • Ciclo de vida do despacho de agentes: onde o isolamento se encaixa no fluxo de despacho
  • Modos: como um modo estreita a superfície de ferramentas e a política de comandos
  • Backends de sandbox: os backends e sua configuração