8 min de leitura

A reutilização de PIDs corrompe a atribuição de sessões de agentes

A reutilização de PIDs pode atribuir ações de agentes ao processo errado. Crie sessões de agentes mais seguras no macOS com dados de início do processo, verificações do assinante, instantâneos do pai e revalidação rigorosa.

A reutilização de PIDs corrompe a atribuição de sessões de agentes

Um PID isolado serve para uma tela de status. Ele não serve como identidade por trás de uma aprovação, de um registro de auditoria ou de uma sessão de agente que contém credenciais. O sistema operacional recicla IDs de processos. Quando um sistema de agentes trata um número reutilizado como se fosse o mesmo ator, pode associar um processo posterior à aprovação de um processo anterior.

Esse problema não exige um invasor com acesso ao kernel nem uma condição de corrida exótica. Ele aparece quando um agente de curta duração termina, uma máquina ocupada cria processos suficientes para reutilizar seu PID e algum componente reconecta uma chamada, atualiza uma tela ou resolve uma linha antiga de auditoria pelo PID. O processo posterior pode ser um software comum. A atribuição ainda estará errada, e um sistema de aprovação que age com base em uma atribuição incorreta já perdeu o motivo de existir.

A correção não é complicada, mas exige mudar o vocabulário. Pare de chamar um PID de identidade de processo. Armazene um registro capturado do processo que contenha o PID, o horário de início, os dados de assinatura de código e a proveniência do processo pai, coletados no momento em que você tomou a decisão de autorização. Depois, faça cada comparação posterior provar que o processo ativo ainda corresponde a esse registro.

Um PID identifica uma posição, não a vida útil de um processo

Um ID de processo identifica uma entrada de processo atualmente alocada no kernel. Ele não nomeia um programa para sempre nem oferece uma garantia de exclusividade global ao longo do tempo. Quando o processo termina, seu PID pode ser reutilizado.

Essa distinção parece minuciosa até que um sistema permita que uma aprovação anterior continue válida. Imagine que um cliente de agente seja iniciado como PID 4812, peça para usar uma credencial de implantação e receba a aprovação do usuário. O cliente termina. Mais tarde, outro processo recebe o PID 4812. Se o seu gateway perguntar «o PID 4812 tem uma sessão aprovada?», poderá recuperar a aprovação antiga e associá-la ao novo processo.

A parte perigosa é que os processos antigo e novo não precisam se sobrepor. Muitas implementações cometem esse erro em uma junção de banco de dados, em uma consulta ao cache ou em um trabalho tardio de enriquecimento. Elas salvam uma aprovação como pid = 4812, salvam as chamadas da mesma forma e confiam que uma consulta posterior terá o mesmo significado de antes. Não terá.

Um PID ainda pode ser útil em um registro voltado para pessoas, porque ajuda um operador a inspecionar um sistema ativo. Mantenha-o. Apenas não faça dele a identidade principal usada para autorização ou atribuição.

Há uma segunda armadilha: fork e exec complicam as explicações simplistas. Um fork cria um novo filho com um PID diferente. Um exec substitui a imagem do programa em um processo existente, mantendo o PID. Se você aprovar um executor de shell e ele fizer exec de outro binário, o PID continuará familiar, mas o código que fará a próxima solicitação poderá ser diferente. Um projeto de identidade precisa considerar tanto a reutilização do PID depois do encerramento quanto a substituição do programa dentro de um processo que continua ativo.

Capture um registro do processo quando a sessão começar

Um registro de sessão confiável é um instantâneo obtido na fronteira de segurança, não uma coleção de fatos reconstruída depois. Capture-o quando o processo do agente pedir pela primeira vez a criação de uma sessão, antes de exibir uma aprovação que descreva o solicitante.

Para um gateway de agentes no macOS, eu armazenaria estes campos como um único instantâneo imutável do processo:

  • PID e ID efetivo do usuário.
  • Horário de início do processo, com segundos e microssegundos quando a plataforma os fornecer.
  • Caminho do executável observado no momento da captura.
  • Identificador de assinatura, contexto da equipe ou do assinante e hash do diretório de código quando disponíveis.
  • Instantâneo do processo pai, incluindo seu próprio PID e horário de início.

O horário de início transforma um PID em uma referência específica de uma vida útil. Uma tupla prática seria:

process_instance = (
  pid = 4812,
  start_time = 2026-07-22T14:03:18.482911Z,
  euid = 501
)

Essa tupla responde à pergunta específica «Esta é a mesma vida útil de processo do kernel que vi antes?». Ela não responde se o processo é confiável. O registro de assinatura responde a outra pergunta: «Que código o macOS validou quando inspecionei este processo?». O instantâneo do pai responde a mais uma: «O que criou este processo, conforme observado quando a sessão começou?»

Não reduza essas perguntas a um único rótulo textual, como Claude Code (PID 4812). Esse rótulo pode funcionar em um cartão de aprovação, mas elimina os fatos necessários quando um revisor pergunta por que determinada sessão foi autorizada.

No macOS, um inspetor de processos pode usar proc_pidinfo com PROC_PIDTBSDINFO para obter proc_bsdinfo, que inclui pbi_start_tvsec e pbi_start_tvusec. A Apple também expõe start_time do processo em seu modelo de processos do Endpoint Security. O importante não é qual API você escolhe. O importante é capturar o horário de início no momento da decisão e compará-lo depois.

Um auxiliar C específico para a parte da vida útil pode ser assim:

#include <libproc.h>
#include <sys/proc_info.h>
#include <cstdio.h>

int read_process_lifetime(pid_t pid) {
    struct proc_bsdinfo info = {0};
    int size = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0,
                            &info, sizeof(info));
    if (size != sizeof(info)) {
        return -1;
    }

    printf("pid=%d start=%lld.%06d parent=%d uid=%d\n",
           info.pbi_pid,
           info.pbi_start_tvsec,
           info.pbi_start_tvusec,
           info.pbi_ppid,
           info.pbi_uid);
    return 0;
}

O formato da saída importa mais do que a linguagem usada:

pid=4812 start=1784738598.482911 parent=4760 uid=501

Se uma leitura posterior retornar o mesmo PID com um horário de início diferente, você está diante de outro processo. Negue a correspondência da sessão, mesmo que todas as camadas de conveniência queiram tratar o número como familiar.

A assinatura de código descreve o código, não uma instância em execução

Os dados de assinatura de código acrescentam uma proveniência útil, mas não se transformam magicamente em uma identidade de processo. Um identificador de assinatura pode ser compartilhado por todas as versões lançadas de um aplicativo. Um identificador de equipe identifica uma organização assinante, não um executável específico. Um hash do diretório de código é mais específico para o conteúdo assinado, mas muitas instâncias simultâneas do mesmo executável ainda compartilharão esse hash.

A documentação de assinatura de código da Apple faz uma distinção importante que os produtos de segurança costumam ignorar. Um identificador de assinatura pode ser reivindicado por vários assinantes. Por isso, a Apple recomenda verificar a categoria de validação e, para código que não é da Apple, também o identificador da equipe. A nota técnica TN3127 da Apple explica ainda que um requisito designado combina um identificador com requisitos de assinatura para estabelecer a identidade do código entre atualizações.

Esse é o modelo mental correto: os dados de assinatura informam qual executável o macOS validou e se ele atende ao seu requisito de confiança. Eles não informam se este é o mesmo processo que o usuário aprovou cinco minutos atrás.

Para autorizar sessões, use as duas camadas:

same_process_lifetime:
  pid, start_time, euid all match the captured record

same_expected_code:
  captured signing requirement still validates for the live process

same_session:
  the session token refers to this captured process record, not only its PID

Não compare apenas um nome de exibição, um identificador de pacote ou um caminho. Os caminhos mudam. Ferramentas de linha de comando sem assinatura podem ter propriedades de identidade fracas ou válidas apenas localmente. Um usuário também pode executar uma cópia de uma ferramenta conhecida a partir de outro local. A interface de aprovação pode mostrar um rótulo amigável, mas o código de autorização deve preservar os dados de assinatura avaliados que deram origem a esse rótulo.

Uma recomendação ruim comum diz para autorizar apenas pelo identificador de assinatura, porque ele permanece estável entre atualizações do aplicativo. Ela é popular porque a continuidade entre atualizações parece conveniente. Está errada para uma aprovação por execução. Se uma pessoa aprovou um processo de agente em execução, uma inicialização posterior do mesmo programa assinado é uma nova execução e deve receber uma nova decisão de sessão. A identidade estável do código é um motivo para tornar o cartão de aprovação fácil de entender. Ela não dá permissão para estender silenciosamente uma aprovação de uma execução a todas as instâncias futuras.

O PID do pai só é evidência quando sua vida útil é preservada

Os dados do processo pai ajudam os revisores a entender como um agente foi iniciado. Eles podem distinguir um cliente iniciado no terminal de um processo iniciado por um editor, um agendador ou outro agente. Mas ppid = 4760 tem o mesmo problema de reutilização que o PID do filho.

O padrão errado é fácil de identificar:

session.agent_pid = 4812
session.parent_pid = 4760

Três horas depois, um visualizador de auditoria resolve o PID 4760 para um processo que tem esse número e o apresenta como o pai da sessão. O pai original pode ter terminado há muito tempo. Outro processo agora ocupa o 4760. A página de auditoria transformou uma afirmação histórica em uma consulta ao sistema ativo e reescreveu a história silenciosamente.

Capture o instantâneo do pai junto com o instantâneo do filho:

parent_instance = (
  pid = 4760,
  start_time = 2026-07-22T14:01:02.117604Z,
  euid = 501,
  executable_path = "/usr/bin/login",
  signing_requirement = "captured evaluation",
  relationship = "observed_parent_at_session_open"
)

Esse último campo de relação parece banal, mas evita muita linguagem inadequada. O registro do pai significa «este era o pai direto quando o sistema observou o filho». Ele não significa «este pai autorizou o filho», «este pai será o dono do filho para sempre» ou «este continua sendo o pai atual». Essas afirmações exigem evidências separadas.

Se você tiver eventos de processo do Endpoint Security, es_process_t da Apple fornece o PID do pai original e expõe parent_audit_token e responsible_audit_token, além do horário de início e dos dados de assinatura. Os tokens de auditoria são preferíveis aos PIDs simples quando a API os fornece, porque preservam mais contexto. Ainda assim, trate o objeto de processo de um evento como uma observação capturada. Não o substitua por uma consulta posterior ao PID e diga que são equivalentes.

Para sistemas sem Endpoint Security, use as melhores APIs de processo disponíveis, capture rapidamente e torne a incerteza visível. O pai pode terminar entre a leitura do filho e a leitura do pai. Não invente certeza nessa condição de corrida. Marque o pai como indisponível ou parcial, retenha a identidade do filho que você conseguiu capturar e evite afirmar uma relação com o pai que não pôde observar.

Revalide antes que uma sessão aprovada execute uma ação

Mantenha as credenciais longe dos agentes
O Sallyport mantém as credenciais dos agentes em seu cofre criptografado e executa as ações HTTP ou SSH por conta própria.

A aprovação de uma sessão exige dois momentos de tratamento da identidade: a captura quando a sessão é aberta e a revalidação quando a sessão usa a autoridade. A captura protege o registro de auditoria. A revalidação protege a próxima ação.

Imagine esta sequência:

  1. Um processo de agente abre uma sessão, e você captura o PID 4812, o horário de início, os dados do assinante e os detalhes do pai.
  2. Uma pessoa aprova exatamente essa execução do agente.
  3. O processo termina enquanto o token da sessão permanece na memória ou em uma conexão IPC local.
  4. Um processo posterior recebe o PID 4812 e apresenta um token antigo ou alcança uma associação antiga por causa de um erro.
  5. Seu gateway verifica o processo ativo antes de executar uma chamada que usa credenciais.

Na verificação final, o PID pode coincidir. O horário de início não coincidirá. Essa diferença deve encerrar a sessão. Não corrija o registro com o novo horário, não crie uma sessão substituta silenciosamente e não atribua a ação ao processo anterior.

A verificação também deve falhar de forma segura quando a inspeção falhar. Se o seu código não consegue ler as informações do processo porque ele terminou, as permissões mudaram ou o sistema operacional retornou dados incompletos, não pode estabelecer que o solicitante é o processo aprovado. A resposta correta é exigir uma nova sessão.

Mantenha a comparação restrita e literal. Não use correspondência flexível como «mesmo nome do comando», «mesmo diretório de trabalho» ou «mesma janela do terminal». Esses campos ajudam uma pessoa a entender o contexto, mas não resistem à ambiguidade acidental ou criada por um invasor. Um processo pode mudar seu diretório de trabalho, e dois processos sem relação podem escolher o mesmo nome de comando.

O lugar certo para armazenar em cache é a decisão de autorização vinculada à instância de processo capturada. O lugar errado é um mapa de PID para estado de aprovação. Um mapa indexado pelo PID passará nos testes em um laptop tranquilo e falhará sob grande rotatividade de processos, o que torna o problema especialmente difícil de diagnosticar depois que chega aos usuários.

Modele as sessões como eventos imutáveis, não como linhas mutáveis de processos

Um diário de sessões deve preservar o que o gateway observou em cada momento. Uma linha mutável que sempre diga «PID 4812 está ativo» não consegue responder se seus campos vieram do agente original, de um processo substituto ou de uma atualização tardia em segundo plano.

Use registros de eventos somente para acréscimo, com referências explícitas. A estrutura pode ser simples:

{
  "event_type": "session_authorized",
  "session_id": "sess_7d9f",
  "process_instance": {
    "pid": 4812,
    "start_time": "2026-07-22T14:03:18.482911Z",
    "euid": 501,
    "signing_id": "com.example.agent",
    "team_id": "A1B2C3D4E5",
    "cdhash": "captured-code-directory-hash"
  },
  "parent_instance": {
    "pid": 4760,
    "start_time": "2026-07-22T14:01:02.117604Z"
  },
  "approval": {
    "scope": "this process run",
    "decision": "approved"
  }
}

O evento da próxima chamada faz referência a sess_7d9f e registra seu próprio horário, o canal solicitado, o alvo da ação e o resultado. Ele não copia apenas pid: 4812 esperando que um analista consiga reconstruir o restante. Se a revalidação falhar, escreva um evento de negação separado com a diferença observada.

{
  "event_type": "action_denied",
  "session_id": "sess_7d9f",
  "reason": "process_start_time_mismatch",
  "captured_pid": 4812,
  "captured_start_time": "2026-07-22T14:03:18.482911Z",
  "observed_start_time": "2026-07-22T15:47:09.031882Z"
}

Isso dá ao revisor algo concreto: o número do processo foi reutilizado, o gateway reconheceu a diferença e negou a solicitação. Sem os dois horários, o evento apenas diz que algo falhou. Isso não basta quando é preciso distinguir um defeito de software de uma tentativa hostil de herdar autoridade.

A separação do Sallyport entre um diário de sessões e um diário de atividades é útil aqui, porque a autorização de uma sessão e as ações individuais respondem a perguntas de auditoria diferentes. Ambos precisam manter o instantâneo do processo que existia quando a decisão da sessão foi tomada, em vez de depender de um PID isolado durante uma projeção ou revisão posterior.

O cartão de aprovação deve descrever a proveniência sem fingir certeza

Mantenha as aprovações vinculadas às execuções
Novas execuções de agentes exigem autorização por sessão por padrão, em vez de herdar a aprovação de uma execução anterior.

As pessoas não conseguem tomar uma boa decisão diante de uma caixa de diálogo que diga apenas «O agente quer acesso». Elas precisam de contexto de identidade suficiente para reconhecer o solicitante, mas o cartão não deve ser carregado com campos que parecem precisos e dizem pouco.

Comece pela autoridade de assinatura de código que você avaliou. Inclua o nome ou o caminho do executável quando isso ajudar. Mostre a relação com o processo em linguagem simples, como «iniciado por um aplicativo de terminal assinado» ou «iniciado por um auxiliar de editor», somente se você tiver capturado os dados que sustentam essa afirmação. Inclua o PID como detalhe para solução de problemas, não como a identidade declarada.

Evite apresentar uma cadeia de processos pais como se fosse uma cadeia de certificados. A relação com o pai é uma observação pontual do sistema operacional. Um pai assinado pode iniciar um filho sem assinatura. Um pai esperado pode ser um wrapper que faz exec de outra coisa. O cartão de aprovação deve deixar claro quem está solicitando a sessão agora e oferecer os dados do pai como contexto.

Essa distinção também muda a forma como a revogação funciona. Revogue um identificador de sessão, não um PID. Quando uma pessoa revogar a sessão, marque o registro imutável da sessão como revogado e rejeite futuras tentativas de ação que façam referência a ele. Se o processo original ainda estiver em execução, perderá o acesso. Se tiver terminado e seu PID tiver sido reutilizado, a revogação continuará correta porque nunca dependeu do controle daquele PID numérico.

Uma aprovação deve significar uma execução observada. Se os usuários quiserem uma decisão de confiança mais ampla, crie um recurso explícito separado, com um escopo claro, como um requisito de código assinado junto com uma duração definida. Não introduza esse escopo mais amplo em uma aprovação por sessão apenas porque a implementação tinha um cache de PID conveniente.

Teste a falha em vez de confiar no comportamento normal dos processos

Coloque um gateway de ações na frente
Os agentes se conectam pelo shim sp mcp incluído, enquanto o Sallyport continua sendo o gateway de ações.

Os erros de reutilização de PID ficam escondidos porque os testes manuais comuns não geram rotatividade suficiente. Uma suíte de testes deve obrigar seu código a provar que rejeita um processo substituto que herdou um número e que rejeita um exec que altera o código por trás de um processo que continua ativo.

Você não precisa esperar que o macOS reutilize naturalmente um PID específico. Coloque a camada de inspeção de processos atrás de uma interface e forneça a ela instantâneos controlados. Um bom teste tem um registro aprovado e uma observação ativa posterior com o mesmo PID, mas um horário de início diferente:

captured: pid=4812 start=1784738598.482911 signer=team-A
observed: pid=4812 start=1784744029.031882 signer=team-B
expected: deny with process_start_time_mismatch

Adicione um segundo caso em que o PID e o horário de início coincidam, mas o requisito de assinatura falhe. Adicione outro em que o filho continue ativo, mas o exec tenha alterado os dados do executável. Esses testes provam que o código de autorização combina os campos corretos, em vez de apenas testar se a inspeção do processo retorna dados.

Teste a visualização de auditoria separadamente. Crie uma sessão antiga com um PID de pai, simule o encerramento desse pai e depois forneça um processo sem relação que tenha o mesmo PID. A sessão histórica renderizada deve manter os campos capturados do pai original. Ela não deve substituí-los por um nome obtido da tabela atual de processos.

Por fim, teste o cancelamento e as falhas de inspeção. Um processo pode desaparecer no breve intervalo entre o recebimento de uma solicitação IPC e a consulta dos seus detalhes. Seu código deve registrar um motivo claro para a negação e exigir que o solicitante estabeleça uma nova sessão. Sistemas que transformam essas falhas em correspondências de melhor esforço criam exatamente a ambiguidade que o modelo de identidade foi criado para eliminar.

A invariante útil é simples e rigorosa

Toda ação privilegiada de um agente deve fazer referência a uma sessão vinculada à vida útil de um único processo capturado. Uma solicitação ativa correspondente deve provar que vem dessa mesma vida útil, e seu executável ainda deve atender à identidade de código aprovada. Um PID reutilizado falha no primeiro teste. Um binário diferente por trás de um PID inalterado falha no segundo.

Essa invariante mantém os dados do processo em seus papéis corretos. Os dados de início distinguem vidas úteis. Os dados do assinante identificam o código validado. Os detalhes do pai fornecem proveniência. O ID da sessão carrega a decisão humana. Nenhum desses campos pode fazer o trabalho dos outros.

O Sallyport pode tornar essa decisão visível ao iniciar a autorização da sessão com a autoridade de assinatura de código do processo e manter registros separados para execuções e chamadas. A implementação ainda deve impedir que um número conveniente, por mais familiar que pareça em um log, substitua o processo que conquistou a aprovação.

Na próxima vez que você vir uma tabela indexada por pid, pergunte o que acontece depois que o processo termina. Se a resposta for «consultamos de novo», essa tabela não é um armazenamento de identidade de sessão. Corrija isso antes que um evento inofensivo do ciclo de vida do processo se transforme em um erro de autorização.

FAQ

Por que um ID de processo não é uma identidade de processo exclusiva?

Um PID é uma posição na tabela de processos do sistema operacional, não uma identidade permanente. Quando um processo termina, o macOS pode atribuir o mesmo número a outro processo. Se o seu registro disser apenas «PID 8421», ele poderá apontar para o processo errado depois de uma quantidade suficiente de reutilizações.

O que devo armazenar com um PID para identificar um processo de agente com segurança?

Use o PID junto com o horário de início do processo, no mínimo. Em um sistema de controle de agentes, registre também o ID do usuário, o caminho do executável capturado no momento da conexão, as informações de assinatura e um instantâneo do processo pai. Cada campo detecta uma forma diferente de um PID isolado induzir você ao erro.

Um PID junto com o horário de início é suficiente para autorizar um agente?

Não. O horário de início distingue duas execuções que receberam o mesmo PID, mas não informa se o executável é confiável. Combine-o com informações de assinatura validadas e mantenha o processo pai como evidência de proveniência.

Posso confiar apenas em um identificador de assinatura do macOS?

Um identificador de assinatura de código nomeia o código dentro de um escopo de assinatura, mas não prova, por si só, que o processo é aquele que você pretendia aprovar. Uma verificação sólida de identidade inclui o contexto do assinante ou da equipe e valida o requisito de código. Para registros forenses, mantenha também o hash do diretório de código quando a plataforma o disponibilizar.

Como registrar o processo pai sem erros causados pela reutilização de PIDs?

Capture o processo pai quando o filho se conectar ou quando você observar o evento de exec. Não consulte o PID do pai mais tarde presumindo que o resultado representa a verdade histórica. Os PIDs dos pais podem ser reutilizados da mesma forma que os PIDs dos filhos.

Como impedir que a reutilização de um PID aprove o agente errado?

Trate a identidade do processo como um dado de autorização, não como um rótulo de exibição. Verifique novamente o processo ativo imediatamente antes de conceder uma sessão, compare o horário de início e os dados de assinatura com o registro capturado e negue a solicitação quando o processo tiver terminado ou mudado.

O Endpoint Security resolve a atribuição de processos no macOS?

Ele pode ser útil, mas não substitui um registro de identidade que leve o ciclo de vida em conta. O Endpoint Security expõe o horário de início do processo, dados de auditoria, campos de assinatura de código e dados de auditoria do processo pai em suas estruturas de processo. Ele também exige o entitlement adequado e um trabalho operacional que muitos aplicativos de desktop não precisam.

Qual é a diferença entre um cdhash e uma identidade de processo?

Não. Um hash de diretório de código identifica o conteúdo do código assinado, enquanto a identidade de um processo identifica uma instância em execução desse código. Dez processos simultâneos podem ter o mesmo hash, e um PID pode se referir a vários processos diferentes ao longo do tempo.

Como um registro de auditoria deve modelar sessões de agentes e encerramentos de processos?

Mantenha eventos imutáveis, em vez de uma única linha mutável de sessão que seja sobrescrita. Armazene a identidade capturada no momento da aprovação da sessão, adicione registros de chamadas que façam referência a essa identidade e registre a revogação ou o encerramento como eventos posteriores. Assim, os revisores terão uma sequência para inspecionar, não uma suposição baseada no estado atual.

O exec pode mudar um processo de agente aprovado sem alterar seu PID?

O fork pode preservar um PID enquanto o exec substitui a imagem do programa nesse processo. Isso significa que o PID e até a relação com o processo pai podem continuar iguais, enquanto o executável e o assinante mudam. Verifique a identidade quando o agente realmente se conectar e novamente antes de usar uma autorização de longa duração.

Sallyport

O Sallyport executa chamadas de API e comandos SSH pelo seu agente de IA. As chaves ficam em um cofre local no seu Mac; você aprova cada execução e toda ação vai para um registro selado.

© 2026 Sallyport · Código aberto sob Apache-2.0 · Oleg Sotnikov