# Autorização de processos de agentes: revogue o acesso bifurcado

Agentes autônomos raramente param de forma tão limpa quanto seus registros de tarefas sugerem. Um agente pode concluir uma solicitação de programação, exibir uma alegre mensagem de conclusão e deixar para trás um formatador, executor de testes, túnel, shell ou processo auxiliar que continua em execução. Se esse processo deixado para trás ainda puder chamar APIs de produção ou abrir sessões SSH, a tarefa não terminou de fato onde a interface diz que terminou.

A solução não é ficar obcecado em encerrar processos. A limpeza de processos é necessária, mas não é um sistema de autorização. Defina a duração da autoridade separadamente da duração dos processos Unix, torne esse limite observável e revogue o acesso antes de começar a procurar descendentes. Já vi equipes tratarem um PID pai como prova de contenção. Isso não prova muita coisa quando um agente pode invocar um shell.

## Uma árvore de processos não define um limite de autorização

A relação entre pai e filho informa quem criou um processo. Ela não informa se esse processo deve manter autoridade depois que o pai termina. São perguntas diferentes, e misturá-las produz a falha conhecida: alguém cancela uma tarefa do agente, vê o pai desaparecer e depois descobre que um filho ainda faz solicitações cinco minutos mais tarde.

O Unix oferece aos processos várias formas de escapar do formato esperado. Um filho pode criar outro processo. Pode criar uma nova sessão com `setsid`. Pode pedir que um gerenciador de serviços o supervisione. Pode deixar um pipeline de shell em que apenas um membro recebe o sinal. Também pode simplesmente continuar vivo porque o pai não enviou sinal algum.

Um processo carrega mais do que sua linha de comando. Ele pode herdar variáveis de ambiente, diretório de trabalho atual, arquivos abertos, pipes, sockets e descritores de arquivo. Se o pai mantém um token de portador em uma variável de ambiente, qualquer filho que herde esse ambiente terá o token. Encerrar o filho depois não coloca o segredo de volta no cofre nem desfaz as solicitações já feitas.

Por isso, um limite de autorização deve responder a uma pergunta específica: qual execução de processo ativa pode solicitar ações privilegiadas, até quando e como podemos negá-las imediatamente? A resposta não deve ser «qualquer coisa descendente do terminal que iniciou a tarefa». Isso é um acidente do layout de processos, não uma decisão de segurança.

Para um agente que precisa de acesso HTTP ou SSH, mantenha as credenciais fora do processo do agente. Deixe um componente que possui as credenciais executar a ação depois de reconhecer um solicitante aprovado. Esse desenho transforma a limpeza de uma tentativa desesperada de apagar autoridade vazada em uma rotina operacional.

## Defina a duração antes de iniciar a tarefa

Uma execução de agente precisa de uma condição de início, uma condição de término e uma condição de revogação claramente definidas. Escreva isso antes de decidir se um clique na interface, um grupo de processos ou um wrapper de shell conta como controle.

Para trabalho interativo, o padrão mais sensato costuma ser uma autorização para um único processo raiz do agente. A autorização começa quando você aprova aquela execução específica e termina quando o processo raiz sai ou quando você a revoga. Uma nova chamada do agente recebe uma nova decisão, mesmo que use o mesmo binário no mesmo diretório.

Para trabalho sem supervisão, não prolongue silenciosamente essa aprovação pelo dia inteiro só porque solicitações repetidas incomodam as pessoas. Dê ao trabalho um responsável identificado, uma duração finita, um conjunto limitado de destinos e um caminho de cancelamento que alguém possa usar enquanto ele ainda estiver em execução. Se o trabalho precisar continuar depois do fim da tarefa pai, trate-o como um trabalho separado e faça com que solicite uma autorização própria.

Três perguntas expõem rapidamente desenhos imprecisos:

- Qual executável e qual instância de processo o usuário aprovou?
- Que evento encerra sua autoridade se o processo nunca enviar um sinal de conclusão limpo?
- Um descendente pode obter uma nova ação privilegiada depois desse evento?

Se a última resposta for sim porque o descendente herdou um token, você delegou autoridade sem registrar a delegação. Se a resposta for sim porque o gateway ainda considera todo descendente confiável, você transformou a ancestralidade de processos na linguagem da sua política. As duas escolhas são difíceis de explicar durante um incidente.

Não confunda conveniência com um limite. Uma aba do terminal, um diretório de projeto, uma conta de agente e uma identidade de assinatura de código são contextos úteis. Nenhum deles identifica sozinho uma execução. Uma identidade de assinatura de código informa quem assinou um executável. Ela não informa se o executável iniciou um auxiliar esperado, uma cópia antiga de um auxiliar ou um filho que se desanexou depois do cancelamento.

## Inspecione a árvore ativa antes de encerrar qualquer coisa

No macOS, comece pelos fatos do processo expostos pelo kernel, em vez de adivinhar a partir do rótulo de uma tarefa. O manual de `ps` documenta `pid`, `ppid`, `pgid` e `sid` como campos distintos. Eles mostram a ancestralidade, a participação em grupos de processos e a participação em sessões. Você precisa de todos quando um agente pode invocar shells e ferramentas.

Execute este comando e salve a saída ao investigar uma execução:

```sh
ps -axo pid,ppid,pgid,sid,stat,etime,command
```

O formato útil é parecido com este:

```text
  PID  PPID  PGID   SID STAT ELAPSED COMMAND
48102 47790 48102 48102 S    00:18:04 agent-cli run build
48131 48102 48102 48102 S    00:17:59 /bin/sh -c make test
48144 48131 48102 48102 S    00:17:56 test-runner --watch
48209     1 48209 48209 S    00:16:02 helper --upload-results
```

Os três primeiros processos compartilham um grupo e uma sessão. O último tem PPID 1 e um grupo e uma sessão diferentes. Ele pode ter se desanexado ou estar agora sob controle de um inicializador. De qualquer forma, enviar um sinal apenas para o PID 48102 não o interromperá.

Para encontrar os filhos diretos de um PID raiz conhecido, use:

```sh
pgrep -P 48102 -alf
```

Esse comando encontra apenas uma geração. Repita-o para cada filho se precisar de uma verificação manual rápida. Em um registro de incidente, capture a saída de `ps` antes e depois da revogação e registre o PID raiz exato, horário de início, comando, grupo de processos e sessão. Um simples nome de processo é uma evidência fraca, pois os nomes se repetem e as linhas de comando mudam.

Verifique as conexões de rede abertas quando o risco envolve chamadas externas. No macOS, `lsof` pode mostrar os arquivos de rede de um processo:

```sh
lsof -nP -p 48209 -i
```

Um socket em escuta, uma conexão externa estabelecida ou um transporte SSH de longa duração aumenta a urgência. Isso não prova comportamento malicioso. Prova que um processo que você julgava encerrado ainda tem um canal que precisa ser entendido.

Não crie um controle de segurança que dependa de analisar a saída legível de `ps` em produção. Use-a para investigação e testes. Um inicializador real deve registrar identificadores no momento do lançamento e manter um recurso direto de revogação no gateway de autorização.

## Grupos de processos ajudam, mas filhos desanexados escapam

Um grupo de processos dedicado oferece ao inicializador uma forma prática de cancelar uma árvore de tarefas normal. Crie o grupo antes de iniciar o agente, mantenha a raiz como líder do grupo e envie sinais ao grupo, em vez de enviá-los apenas ao processo raiz. Isso cobre o caso comum de shells, compiladores, executores de testes e pipelines que permanecem no mesmo grupo.

Nos sistemas que aceitam a sintaxe usual de sinais, um ID de grupo negativo direciona o sinal ao grupo de processos:

```sh
kill -TERM -48102
sleep 3
kill -KILL -48102 2>/dev/null || true
```

O sinal `TERM` dá às ferramentas comuns a oportunidade de fechar arquivos e informar o cancelamento. O sinal `KILL` posterior lida com processos que se recusam ou não conseguem sair. Não copie isso para uma automação antes de verificar se 48102 é o grupo de processos pretendido. Um ID incorreto pode encerrar seu próprio shell ou um trabalho sem relação.

Esse método tem limites. Um filho pode chamar `setsid`, criando uma nova sessão e, em geral, um novo grupo de processos. Uma tarefa pode enviar trabalho a um serviço local, a um sistema de compilação remoto ou a uma fila. Um shell pode iniciar um processo em segundo plano fora do grupo. Quando isso acontece, encerrar o grupo vira limpeza, não contenção.

No Linux, um gerenciador de serviços pode colocar uma tarefa em um cgroup dedicado e encerrar o cgroup como uma unidade. Isso costuma ser mais forte do que a limpeza de um grupo de processos, porque o kernel acompanha a participação além da ancestralidade comum. Não presuma que um aplicativo de barra de menus do macOS tenha esse controle. macOS e Linux têm modelos diferentes de supervisão de processos, e um desenho portátil de agente não deve fingir o contrário.

A recomendação de «simplesmente encerrar a árvore de processos» continua popular porque funciona em demonstrações. Ela falha justamente nas condições que tornam o acesso de agentes arriscado: trabalhos longos, auxiliares em segundo plano, wrappers e cancelamento parcial. Use grupos de processos porque eles reduzem os resíduos. Não os use como único mecanismo de revogação.

## Mantenha os segredos completamente fora da árvore

O processo filho mais seguro ainda é aquele que não consegue ler uma credencial. Passar um token por uma variável de ambiente o disponibiliza a todos os descendentes que herdarem o ambiente e também pode expô-lo a diagnósticos, relatórios de falha ou registros descuidados. Passar um arquivo temporário é apenas um pouco menos ruim se o filho puder copiá-lo antes que você o remova.

Evite estes padrões para comandos iniciados por agentes:

```sh
export DEPLOY_TOKEN='token-value'
agent-cli run deploy
```

```sh
agent-cli run deploy --token "$(cat ~/.config/deploy-token)"
```

Os dois colocam autoridade bruta no ambiente de execução do agente. O segundo também pode colocá-la nos argumentos do processo, no histórico do shell ou nos registros. Talvez seja necessário trocar o token depois de um problema, mas a troca é uma ação de recuperação, não um caminho normal de cancelamento.

Use um gateway de ações local. O agente deve solicitar uma operação, como uma solicitação HTTP a um endpoint aprovado ou um comando SSH, sem jamais receber o material da credencial. O gateway injeta a credencial relevante, executa a ação e devolve o resultado. Assim, você tem um ponto onde pode negar a próxima chamada mesmo que um filho perdido continue em execução.

O Sallyport adota essa abordagem para APIs HTTP e SSH: seu cofre criptografado permanece no aplicativo, enquanto o agente se conecta pelo shim `sp mcp` e recebe resultados das ações, não segredos. Isso importa mais do que qualquer script engenhoso para encerrar processos, pois um filho não pode herdar um token que nunca teve.

Não exagere esse benefício. Um processo com uma sessão aprovada no gateway ainda pode solicitar ações até que a sessão termine ou seja revogada. Manter segredos fora da árvore de processos limita o roubo de credenciais, mas não torna inofensivo um agente aprovado.

## A aprovação deve estar ligada a uma execução, não a um nome de família

A autorização baseada apenas no nome de um executável é fraca. Qualquer pessoa pode copiar um binário para outro caminho, envolvê-lo em um script de shell ou executar outra instância mais tarde. A autorização baseada apenas no assinante é melhor para atribuição, mas ainda é ampla demais se aprovar silenciosamente todas as execuções futuras assinadas pela mesma parte.

Um cartão de aprovação útil deve identificar o processo solicitante com informações que uma pessoa possa conferir: autoridade de assinatura de código, caminho do executável, PID raiz e horário de início. A decisão deve valer para aquela execução específica. Um processo filho não deve obter um direito interminável de agir apenas por ter um ancestral aprovado em algum ponto da sua história.

Há dois modelos razoáveis para descendentes. O modelo mais rigoroso pede aprovação para cada solicitante distinto. O modelo prático permite chamadas feitas durante uma única execução raiz aprovada e rejeita todas as novas chamadas quando essa execução termina. O segundo funciona bem para agentes que legitimamente iniciam ferramentas de curta duração, desde que o gateway consiga saber que a execução raiz terminou e que a autorização não possa ser associada novamente a um processo posterior com o mesmo nome.

O Sallyport usa autorização por sessão por padrão: a primeira chamada de um novo processo de agente mostra um cartão de aprovação que começa pela autoridade de assinatura de código do processo, e a aprovação dura até que essa execução termine. O bloqueio do cofre nega todas as ações enquanto ele estiver trancado, e uma configuração por chamada pode exigir aprovação para uma determinada credencial em todo uso. São controles deliberadamente pequenos. Um conjunto de regras de política apenas esconderia a pergunta sobre quem aprovou o quê.

A aprovação por chamada faz sentido para credenciais em que cada ação merece uma revisão cuidadosa, como uma conta de implantação em produção ou uma API administrativa destrutiva. Ela não se encaixa em toda solicitação somente de leitura. As pessoas aprendem a aprovar tudo quando cada ação inofensiva exige atenção. Coloque o atrito onde a consequência justificar e mantenha o limite de sessão curto e claro nos demais casos.

## Revogue a autorização antes de procurar o processo

Quando uma tarefa pai termina inesperadamente, revogue primeiro sua capacidade de fazer novas chamadas privilegiadas. Depois, encerre o grupo raiz, inspecione os sobreviventes e limpe o que escapou. Inverter essa ordem cria uma brecha: o filho que você ainda não encontrou pode continuar fazendo chamadas enquanto você examina as tabelas de processos.

Uma sequência de resposta adequada é:

1. Revogue a sessão ou bloqueie o cofre no gateway de ações.
2. Preserve o PID raiz, as informações do processo, os registros recentes de ações e o horário do cancelamento.
3. Envie `TERM` ao grupo de processos conhecido e inspecione os processos restantes.
4. Use `KILL` apenas nos processos que ainda pertencem à tarefa e se recusam a sair.
5. Verifique se processos locais desanexados ou trabalhos remotos exigem um cancelamento separado.

A primeira ação deve funcionar mesmo que o PID raiz já tenha desaparecido. Ela também deve funcionar se um agente tentar deliberadamente manter um auxiliar ativo. Um gateway que exige a presença do solicitante para a revogação está invertendo a lógica.

A revogação imediata deve negar solicitações futuras, não reescrever o histórico. Mantenha os registros que mostram a autorização e as chamadas anteriores. Se um gateway registrar apenas ações bem-sucedidas, ele esconderá evidências úteis durante uma investigação. Chamadas negadas depois da revogação mostram que algo continuou tentando agir.

Bloquear o cofre é o freio de emergência para todas as sessões ativas, o que é adequado quando você não consegue identificar rapidamente a execução comprometida. A revogação da sessão é a resposta mais restrita quando isso é possível. Mantenha as duas operações distintas para que o operador não precise escolher entre não fazer nada e interromper o trabalho de todos os engenheiros.

## Uma trilha de auditoria precisa se unir às evidências do processo

Um registro de atividade sem contexto de processo responde apenas a metade da pergunta. Ele pode dizer que uma solicitação HTTP ocorreu, mas não qual execução aprovada a iniciou. Um registro de sessão sem chamadas individuais tem o problema oposto. Você precisa das duas visões e de uma forma de verificar que ninguém as editou discretamente depois do incidente.

Registre pelo menos o evento de autorização, a identidade do processo solicitante, o início e o fim da sessão, cada ação privilegiada, a revogação e qualquer negação posterior à revogação. Inclua horários e identificadores estáveis de correlação. Não registre segredos brutos. Também não presuma que seja seguro armazenar uma linha de comando, pois ela frequentemente contém valores que nunca deveriam ter sido passados dessa forma.

O Sallyport projeta seus diários de Sessões e Atividades a partir de um único registro de auditoria criptografado e encadeado por hash. O comando `sp audit verify` verifica a cadeia offline sobre o texto cifrado sem precisar de uma chave do cofre. Essa verificação é útil quando você precisa entregar um registro exportado a alguém que deve confirmar sua integridade, mas não pode ler as credenciais armazenadas.

Uma cadeia de hash não torna completo um registro incompleto. Se o inicializador nunca registrou o PID raiz, o registro de auditoria não poderá reconstruí-lo depois. Se um trabalho remoto recebeu uma solicitação de API e continuou em outra máquina, as evidências locais do processo não mostrarão o processo remoto. Audite a solicitação que iniciou o trabalho remoto e exija que o sistema remoto exponha seus próprios registros de cancelamento e eventos.

## Uma falha conhecida exige duas correções separadas

Considere um agente de programação encarregado de executar testes de integração e publicar um relatório. Ele inicia um shell, que inicia o executor de testes, que inicia um auxiliar para enviar os resultados. O usuário vê uma falha no teste e cancela o agente. O agente pai termina. O shell desaparece. O auxiliar já se desanexou, mantém uma conexão externa e envia um relatório depois do cancelamento.

Se a credencial de upload estava em uma variável de ambiente, o auxiliar pode possuí-la mesmo depois que você revoga a aprovação local. Agora são necessárias a troca da credencial, a revisão dos registros e talvez uma resposta a incidente. A limpeza do processo chegou tarde demais porque a autoridade já havia passado para o filho.

Se o auxiliar solicitou o upload por meio de um gateway, revogar a sessão pai interrompe uma nova solicitação de upload. Se o upload já havia começado, a auditoria do gateway mostra esse fato. Você ainda encerra o auxiliar, mas não depende mais do encerramento como único meio de controle.

O caso mais difícil é o de um auxiliar que precisa sobreviver ao agente por definição, como um servidor local de pré-visualização ou um trabalho de lançamento em fila. Não o trate como filho e não faça com que herde autoridade para sempre. Dê a ele um responsável explícito, um registro de autorização separado, uma expiração definida e um controle de parada visível. Quando o trabalho sobrevive à tarefa que o iniciou, ele se tornou um objeto operacional próprio.

## Teste o cancelamento contra um descendente persistente

Um controle que você nunca testa falhará da forma menos conveniente. Crie um agente de teste inofensivo que inicie um filho que durma, abra uma conexão local inofensiva e tente uma ação no gateway depois que o pai terminar. Depois, cancele o pai em vários momentos: antes de o filho começar, enquanto ele estiver em execução, depois que ele se desanexar e enquanto houver uma ação em andamento.

O resultado esperado deve ser específico. O filho normal termina com o grupo de processos. Um filho desanexado pode continuar visível, o que prova por que você ainda precisa inspecionar. Depois da revogação, toda nova solicitação privilegiada recebe uma negação. O registro da sessão mostra a aprovação e a revogação, enquanto o registro de atividades mostra as chamadas feitas antes e depois do limite.

Não deixe que um teste aprovado signifique apenas que a interface mudou de estado. Verifique o estado do processo no sistema operacional, a decisão do gateway e o registro de auditoria. São três observações diferentes. Um botão de cancelamento que apenas esconde um cartão de tarefa é teatro.

A primeira mudança prática é simples: registre a identidade do processo raiz sempre que autorizar um agente e faça a autorização expirar de forma independente quando essa execução terminar. Depois, execute o teste do filho persistente antes de confiar uma função de cancelamento a credenciais capazes de alterar a produção.
