Saltar para o conteúdo

Barreiras — uma única política para todas as ações mutáveis

Os agentes do Control Center podem eliminar ficheiros, fazer push de branches, publicar pull requests e chamar APIs externas. Cada uma destas ações é um tipo de risco diferente e, durante muito tempo, só uma delas — os comandos de shell — tinha uma política. As barreiras existem para responder a uma pergunta de forma uniforme, para todas as ações mutáveis que um agente pode realizar, independentemente do executor ou da ferramenta que a propõe: isto é permitido, precisa de uma aprovação ou é recusado?

Existe exatamente um motor de políticas. É consultado em todos os pontos em que uma ação pode sair do sistema: o registo de ferramentas do harness integrado, o portão do despachante MCP do lado do servidor e as mutações de operações de repositório. Uma regra escrita uma vez aplica-se, portanto, ao executor integrado e às CLIs de agentes externos, sem distinção — não há um segundo caminho mais silencioso para lhe escapar. A antiga política de comandos, só para o bash, continua a funcionar: uma regra pode corresponder a um prefixo de comando em vez de a uma classe de ação, e as duas formas coexistem no mesmo armazenamento.

O motor não substitui, no entanto, a sandbox do sistema operativo, e a sandbox não o substitui a ele. Respondem a perguntas diferentes: as barreiras decidem se uma chamada que o Control Center consegue ver pode sequer acontecer, enquanto a sandbox delimita até onde pode chegar o processo resultante. O cc_server aplica a sandbox onde o anfitrião oferece um backend — mas no Windows não existe nenhum e o Linux precisa de bwrap e socat. Nesses anfitriões, as barreiras são a camada de aplicação mais exterior, e não uma política assente sobre um piso, o que vale a pena saber quando decide quão permissivo quer ser.

Cada ferramenta mutável declara a que ActionClasses pertence, de um conjunto fechado de treze classes de efeito. Cinco delas pedem confirmação logo à partida; as restantes permitem, e é uma regra que altera isso.

Classe Predefinição integrada O que abrange
fileDelete prompt Eliminar um ficheiro (uma escrita em forma de eliminação, rm)
gitPush prompt Fazer push para um remoto git
prCreate prompt Criar uma pull request
prPublish prompt Publicar uma revisão, fazer merge de uma PR
vendorSyncWrite prompt Escrever num fornecedor externo de tickets (sincronização com Linear, Jira, GitHub)
fileWriteOutsideWorktree allow Escrever um ficheiro fora da worktree isolada
gitCommit allow Criar um commit git
networkEgress allow Saída de rede (web fetch, HTTP arbitrário)
secretAccess allow Ler um segredo ou uma credencial
packageInstall allow Instalar um pacote (npm, pip, pub, brew)
processSpawn allow Criar um processo (a shell e tudo o que passe por uma shell)
workspaceMutation allow Modificar a estrutura do espaço de trabalho (repos, espaços, agentes)
enclosureControl allow Controlar um recinto (rig): arrancar uma VM, enviar-lhe entrada

As oito classes com allow por predefinição foram escolhidas partindo do pressuposto de que o piso da sandbox se aplica por baixo delas. Aplica-se onde o anfitrião oferece um backend e não se aplica no Windows nem numa máquina Linux sem bwrap e socat — por isso, se quiser condicionar fileWriteOutsideWorktree ou packageInstall nesses casos, tem de escrever uma regra.

O conjunto é fechado de propósito. Uma nova ferramenta mutável que não declare nenhuma classe falha o teste de catraca, pelo que a taxonomia só pode crescer através de uma decisão explícita e fundamentada — “a proliferação da taxonomia é a morte desta funcionalidade”. Treze classes são suficientes para expressar as políticas que as pessoas realmente escrevem (nunca fazer push sem perguntar, nunca eliminar fora de uma worktree, permitir sempre leituras) e poucas o bastante para que a interface de configurações se mantenha compreensível.

Uma regra mapeia (scope, ActionClass | command prefix) → allow | prompt | deny. Os âmbitos encaixam do mais específico para o menos específico:

espaço > agente > espaço de trabalho > predefinição do modo > predefinição integrada

A resolução dá prioridade à especificidade: o primeiro âmbito com uma regra correspondente decide e a resolução termina aí. A mais restritiva é apenas um critério de desempate — entre duas regras igualmente específicas dentro do mesmo âmbito e ao combinar as várias classes que uma única ação declara. Dentro de um âmbito, uma regra de comando corresponde ao prefixo de comando mais longo. O allow não se sobrepõe ao deny em lado nenhum.

Duas decisões trazem garantias firmes:

  • Confirmações em modo fechado. Um prompt sem aprovador ligado resolve-se em recusa. Uma pergunta sem resposta nunca é um sim.
  • Combinação mais restritiva. Uma ação que abranja várias classes combina-as da forma mais restritiva; quando várias classes pedem confirmação, uma única confirmação lista-as todas.

Não existe, deliberadamente, qualquer memorização por turno. Cada chamada de ferramenta volta a resolver a política de raiz, pelo que os pedidos de confirmação repetidos para a mesma ação ficam limitados pelo próprio “lembrar esta decisão” do operador e não por algo com o âmbito de um turno.

Uma regra pode restringir os ARGUMENTOS de uma ação, não apenas o seu verbo. “Pode fazer push” e “pode fazer push para feature/*” são afirmações diferentes, e só a segunda é um controlo — um portão de capacidades que autoriza apenas o verbo está na origem da maioria dos incidentes publicados com agentes.

Uma restrição pode indicar:

  • caminhos — padrões glob (repos/**, !**/.env)
  • refs — refs git, com negação ! (['**', '!main'] lê-se como “qualquer branch exceto main”)
  • hosts — destinos de rede, com carateres universais de subdomínio (*.internal)
  • comandos — prefixos de comando, comparados num limite de palavra
  • limites — maxCount / maxCents, para que “eliminar até 50 ficheiros” seja exprimível

Três propriedades decorrem de manter a gramática fechada e sem ciclos, e as três são o objetivo: cada regra é apresentada como uma frase que se pode reler, cada recusa consegue indicar a restrição que correspondeu e a política inteira pode ser enumerada — consegue ver o que um agente pode fazer antes de ele correr.

Três regras que importam:

  • Uma regra com restrições é mais específica do que uma sem restrições no mesmo âmbito, pelo que “recusar push para main” ganha a “permitir push” sem que ninguém tenha de pensar na ordenação.
  • Uma regra restritiva (deny / prompt) aplica-se quando o pedido toca em qualquer valor que ela indique; uma regra permissiva (allow) aplica-se apenas quando o pedido está inteiramente dentro dela. Ler as duas da mesma forma permitiria que um agente branqueasse um caminho proibido colocando ao lado um caminho inocente.
  • Uma regra que indique uma faceta sobre a qual o pedido nada diz não é aplicada nem ignorada: escala para uma aprovação. Ignorá-la em silêncio tornaria todas as regras de branch protegido inertes perante uma ferramenta que não comunica a sua ref; recusar em silêncio recusaria todos os pushes que o extrator não consegue descrever. Sem um aprovador ligado, a regra de falha em modo fechado já existente transforma essa escalada numa recusa.

O que uma regra de comando consegue e não consegue apanhar

Secção intitulada “O que uma regra de comando consegue e não consegue apanhar”

Uma regra commandPrefix corresponde num limite de palavra: git push abrange git push origin main e não git pushx. Não normaliza a shell. git push (dois espaços), cd sub && git push e GIT_DIR=… git push são cadeias de caracteres todas diferentes e nenhuma delas corresponde a uma regra sobre git push.

É um limite real e é dito em vez de disfarçado: as regras de comando são uma comodidade para o operador moldar o comportamento normal de um agente, não uma fronteira contra um agente adversário. As fronteiras são as classes de efeito (que uma ferramenta declara e não pode reescrever), a rede de comandos do modo por baixo do armazenamento de políticas e a sandbox do sistema operativo por baixo de ambas. Uma regra de comando que se resolve em allow nunca afrouxa nenhuma destas — a rede do modo corre depois e o seu deny é definitivo.

Responder a um pedido de confirmação com “lembrar isto” escreve uma regra de política real: com âmbito definido (espaço / agente / espaço de trabalho), restrita por argumentos (aprovar um push para feature/login concede feature/**, não main) e autorrevogável ao fim de um TTL explícito. Dizer sim durante as próximas horas não é o mesmo que reescrever discretamente a sua política permanente, e as regras que escreve ficam no mesmo armazenamento com a proveniência remembered e uma data de expiração, pelo que podem ser revogadas antecipadamente.

Cada regra tem um:

  • advisory — allow, mas regista que a regra correspondeu. Este é o caminho de adoção: implemente uma regra estrita em modo advisory, leia o registo de auditoria durante uma semana para ver exatamente o que ela teria bloqueado e só depois promova-a. Sem este nível, as políticas estritas nunca chegam a ser ativadas.
  • soft — deny, anulável por quem detenha a permissão de substituição, com uma justificação registada. A substituição é, ela própria, um evento auditado.
  • hard (a predefinição) — deny. Nada a anula.

Um operador da instalação pode fixar políticas que nenhum administrador de espaço de trabalho consegue afrouxar. As regras geridas vivem no lado do servidor e são combinadas da forma mais restritiva com a cadeia de cada espaço de trabalho — não são um âmbito à cabeça dessa cadeia, porque um allow gerido à cabeça da cadeia permitiria que a instalação se sobrepusesse a um deny de um espaço de trabalho, precisamente o contrário daquilo para que serve um mecanismo de restrição. Uma regra gerida só pode, por isso, apertar.

Definir CC_SERVER_MANAGED_POLICY para um ficheiro JSON sobrepõe-se por completo às regras armazenadas, e é isso que permite a um operador fixar uma postura que nenhuma interface de administração consegue inverter — a resposta a “posso impedir os meus programadores de desativarem os controlos de segurança?”.

O dial de autonomia é um perfil, não um sistema paralelo

Secção intitulada “O dial de autonomia é um perfil, não um sistema paralelo”

O dial de autonomia por espaço é um perfil com nome sobre este mesmo armazenamento de políticas, não um segundo mecanismo. As suas três definições são armazenadas e enviadas como proposeOnly, actWithApproval e actFreely (as formas com hífen são apenas texto da interface e autonomy.setForSpace recusa tudo o resto). Deixá-lo por definir comporta-se como actWithApproval.

O que cada uma faz a uma decisão resolvida:

  • proposeOnly recusa liminarmente todas as ferramentas condicionadas. O agente continua a poder raciocinar e responder; simplesmente não pode agir e a sua mensagem explica porquê.
  • actWithApproval (e por definir) é o portão de aprovação em modo fechado descrito acima.
  • actFreely pré-aprova tudo o que não tenha sido resolvido para um deny de nível hard.

Esta última merece ser dita sem rodeios, em vez de suavizada: com actFreely, uma decisão prompt não é escalada para um aprovador — é permitida e não se pergunta a ninguém. Só uma regra deny explícita sobrevive ao dial. Assim, actFreely é uma concessão deliberada de autonomia e não uma definição de comodidade, e a regra de falha em modo fechado protege exatamente o caso em que isso importa (actWithApproval, a predefinição).

A delegação entre agentes é protegida no ponto de estrangulamento delegate_task pelas quatro barreiras: um limite de profundidade (3 por predefinição), deteção de ciclos, um teto de autonomia (um delegado nunca pode agir com mais autonomia do que o seu delegador — os privilégios não podem ser branqueados passando trabalho a um agente mais livre) e um envelope orçamental (a delegação é debitada no orçamento restante do delegador e não pode criar mais). Cada recusa é explícita e o motivo da barreira é devolvido ao agente tal e qual.

Nem todos os executores permitem que o Control Center intercete todas as ações. As CLIs de agentes externos executam algumas ferramentas nativamente, no próprio processo, onde não pode existir nenhum portão. Em vez de fingir o contrário, cada adaptador transporta uma matriz de honestidade: por adaptador, se o Control Center filtra a superfície de ferramentas, interceta chamadas de ferramentas, observa o contrato de conclusão, consegue ver as ferramentas nativas do executor e se as ferramentas em processo são abrangidas por um perfil de sandbox.

Vale a pena ler duas entradas antes de confiar numa configuração. O harness integrado declara que as suas ferramentas de ficheiros em processo não estão em sandbox — a superfície de ferramentas e este motor de políticas são a única fronteira de sistema de ficheiros de que dispõe. O Claude Code declara que as suas próprias chamadas de ferramentas não são intercetáveis, porque é lançado com --dangerously-skip-permissions; o Control Center vê as suas chamadas mcp__* e, através de um hook, as suas linhas de comandos Bash, enquanto as suas próprias ferramentas de leitura, escrita e edição correm sem serem vistas.

O ecrã de configurações mostra esta matriz, porque a única falha imperdoável num sistema de barreiras é reivindicar uma cobertura que não existe.

As barreiras estão em Configurações → Espaço de trabalho → Permissões dos agentes (/workspaces/<id>/settings/workspace/permissions). O ecrã tem a matriz de políticas onde as regras são escritas, modelos de política, uma sonda «E se?», o registo de auditoria de autorização e a matriz de honestidade dos adaptadores.

A sonda recebe uma classe de ação (ou um comando) e um âmbito e mostra qual foi a regra que decidiu e porquê. Como a resolução é pura e determinística — mesma ação, mesmo âmbito, mesma decisão — a resposta da sonda é a resposta que o agente vai receber.