# Compilações reproduzíveis para clientes de agentes locais

Um cliente de agente local fica próximo das credenciais e das permissões que realmente importam. Quando um desenvolvedor baixa um desses clientes e concede acesso a contas de API ou destinos SSH, o binário da versão merece uma análise mais cuidadosa do que uma captura de tela mostrando uma tarefa verde na CI. A pergunta útil é concreta: alguém consegue relacionar exatamente esse executável ao código-fonte que analisou e a um procedimento de compilação que pode executar novamente?

As compilações reproduzíveis respondem a parte dessa pergunta. Elas não tornam um commit malicioso inofensivo nem transformam uma conta de desenvolvedor comprometida em uma conta segura. O que fazem é reduzir uma lacuna separada e comum entre o código-fonte analisado e o código enviado. É nessa lacuna que podem se esconder um worker de compilação alterado, uma dependência substituída ou uma mudança local acidental.

Para um cliente de agente local como o Sallyport, isso importa porque o aplicativo executa ações enquanto as credenciais permanecem fora do processo do agente. Os leitores devem conseguir inspecionar o código que trata desse limite e determinar qual binário de versão o contém, em vez de simplesmente confiar na página da versão.

## Um download assinado não prova que ele foi produzido pelo código-fonte analisado

Uma assinatura prova que o responsável por uma identidade de assinatura assinou os bytes entregues. Ela não prova que esses bytes vieram do commit do repositório que você analisou.

As equipes costumam juntar essas duas afirmações porque ambas produzem diálogos tranquilizadores no macOS. O Gatekeeper e as verificações de assinatura do sistema respondem se o pacote tem uma cadeia de assinatura válida e se alguém o alterou depois da assinatura. Eles não respondem se o responsável compilou a tag v1.2.3 sem patches locais, se a CI usou uma dependência maliciosa ou se alguém substituiu o arquivo antes da publicação.

Essa distinção muda a forma de investigar um incidente. Se um aplicativo tem uma assinatura válida, mas se comporta de modo inesperado, a identidade de assinatura reduz o conjunto de pessoas e sistemas que poderiam tê-lo produzido. Uma compilação reproduzível pode reduzir o conjunto de códigos-fonte e receitas que produziram o executável. Você precisa das duas formas de evidência porque elas cobrem falhas diferentes.

A documentação de assinatura de código da Apple descreve a assinatura como um selo sobre o pacote de código. A descrição é precisa, mas um selo não informa nada sobre a cozinha onde o pacote foi montado. Trate a assinatura de código como integridade da distribuição e identidade do publicador. Trate uma recompilação independente como verificação entre código-fonte e binário.

Uma versão também pode ser reproduzível e ainda assim perigosa. Se os revisores aceitaram uma alteração ruim, uma compilação limpa produzirá fielmente o programa ruim. Não apresente a reprodutibilidade como substituta da revisão, de permissões de versão protegidas ou de um desenho sensato para as credenciais. Ela remove a incerteza sobre a transformação do código-fonte em artefato.

## Defina a afirmação antes de comparar os bytes

A equipe deve dizer exatamente o que precisa corresponder, porque o empacotamento de versões macOS muitas vezes torna impossível comparar o pacote inteiro depois da assinatura.

A afirmação mais forte é a reprodutibilidade bit a bit: duas compilações independentes produzem bytes idênticos. É um ótimo objetivo para um executável de linha de comando sem assinatura, um arquivo de código-fonte ou um pacote determinístico. A situação fica mais complexa quando a esteira de versão incorpora um timestamp de assinatura, um perfil de provisionamento, um ticket de notarização ou uma imagem de instalador gerada.

Não abandone a comparação por causa disso. Divida a versão em etapas e torne a afirmação precisa. Um contrato prático de versão pode dizer:

- A tag Git assinada identifica o commit do código-fonte.
- Um pacote de aplicativo sem assinatura, compilado a partir desse commit, deve corresponder byte a byte.
- O responsável pela assinatura da versão adiciona depois a identidade de assinatura e as permissões declaradas.
- O arquivo publicado contém o pacote assinado documentado, cujo hash do executável corresponde ao resultado comparável sem assinatura.

Isso é mais rigoroso do que dizer vagamente que a CI compilou o aplicativo e mais honesto do que afirmar uma identidade completa quando a assinatura torna isso impossível. Também indica aos revisores quais arquivos executam. Uma imagem de disco correspondente ajuda pouco se o executável do aplicativo for diferente. Um executável correspondente com uma permissão alterada merece atenção imediata, porque as permissões podem mudar a autoridade disponível para um processo.

Há outra distinção importante: compilações determinísticas e versões verificáveis. Uma compilação pode ser determinística em uma máquina porque lê silenciosamente um cache local, o horário atual ou uma configuração do desenvolvedor. Uma versão verificável oferece evidências suficientes para que outra pessoa obtenha as mesmas entradas e teste a afirmação. A segunda exigência força as equipes a expor premissas que a primeira pode esconder.

Escreva a afirmação no repositório antes do primeiro pedido público de verificação. Se os mantenedores não conseguem dizer se a comparação ocorre antes ou depois da assinatura, revisores externos não têm como saber o que significa uma divergência.

## O registro da versão precisa vincular código-fonte, receita e artefato

Um registro de versão precisa de uma referência assinada ao código-fonte, uma receita imutável e digests dos arquivos que as pessoas baixam. Deixar qualquer um desses elementos de fora rompe a cadeia de evidências.

Comece com uma tag Git anotada que dê nome à versão. Um hash de commit sozinho não é uma declaração de versão: qualquer pessoa pode apontar uma página para um commit. A tag deve conter uma assinatura de uma identidade de mantenedor que os colaboradores saibam verificar. Depois, registre o objeto de commit completo, não apenas um identificador abreviado.

Um manifesto mínimo pode continuar sendo texto simples e ainda assim carregar evidências úteis:

```text
release: 1.2.3
source_tag: v1.2.3
source_commit: 4f3c1b6e8a0d2c7f9b5e1d4a6c8e0f2b3d7a9c1e
build_recipe: docs/release-build.md@4f3c1b6e8a0d2c7f9b5e1d4a6c8e0f2b3d7a9c1e
xcode: 16.2
macos: 15.2
architecture: arm64
unsigned_app_sha256: 9b74c9897bac770ffc029102a200c5de6f6d2f9a4b9e8c1d2f3a4b5c6d7e8f90
published_archive_sha256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
```

Os hashes acima mostram apenas o formato. Um manifesto real deve usar os digests completos e reais, com uma assinatura nova sobre o manifesto ou uma tag de versão assinada que o contenha. Não coloque o hash apenas na mesma página que hospeda o download. Um invasor capaz de substituir o arquivo também pode conseguir substituir essa página.

A receita de compilação precisa especificar mais do que o compilador. Ela deve identificar a versão do Xcode, a seleção do SDK, a arquitetura, o método de resolução das dependências, a configuração de compilação, as ferramentas de geração de código, as variáveis de ambiente que afetam o resultado e o comando usado para empacotar o arquivo. Se a versão exigir um plug-in privado do compilador ou um binário baixado manualmente, diga isso. O silêncio transforma uma entrada ausente em um beco sem saída para qualquer verificador externo.

Os desenvolvedores podem verificar a vinculação com o código-fonte usando ferramentas comuns do Git:

```sh
git fetch origin tag v1.2.3
git tag -v v1.2.3
git rev-list -n 1 v1.2.3
git show -s --format=%H v1.2.3
```

O primeiro comando informa o objeto da tag e se o Git conseguiu validar sua assinatura usando uma chave pública localmente confiável. Os dois últimos comandos devem exibir o mesmo hash completo do commit registrado no manifesto. Uma assinatura válida de uma chave desconhecida não basta. As equipes precisam de uma lista documentada das impressões digitais usadas para assinar versões, mantida sob revisão, ou terão apenas transferido a confiança para um chaveiro local oculto.

## A assinatura do macOS altera os bytes depois da compilação

Os desenvolvedores macOS devem tratar compilação, assinatura, notarização e empacotamento como etapas separadas, porque cada etapa pode modificar o artefato.

Um pacote de aplicativo contém mais de um executável. Ele pode incluir um executável principal, frameworks incorporados, auxiliares de login, auxiliares de linha de comando, metadados, recursos e permissões. A ferramenta codesign registra assinaturas em toda essa estrutura. Assinar depois de uma cópia final pode alterar o conteúdo, e um ticket de notarização pode ser anexado posteriormente ao pacote ou instalador. Uma ferramenta de criação de imagem de disco também pode codificar timestamps e escolhas de layout do sistema de arquivos.

É aqui que as equipes cometem um erro evitável: assinam durante o único comando de compilação e depois esperam que uma máquina externa reproduza o arquivo final. O verificador externo não tem a credencial privada usada para assinar, não deveria tê-la e naturalmente produzirá uma assinatura diferente. A divergência não prova nada sobre o programa compilado.

Primeiro, crie um artefato de comparação sem assinatura. Se possível, arquive-o com um método determinístico, calcule os hashes do executável e dos recursos relevantes e só então passe para uma etapa de assinatura exclusiva da versão. Mantenha o artefato sem assinatura disponível para verificadores independentes designados, mesmo que não o publique para todos os usuários finais. Se a política de versões impedir a publicação, publique um manifesto assinado com os hashes dos componentes e permita que uma segunda parte confiável retenha o artefato.

Inspecione o aplicativo assinado como uma tarefa de verificação separada:

```sh
codesign -dv AppName.app 2>&1
codesign -d --entitlements :- AppName.app 2>/dev/null
spctl -a -vv AppName.app
```

O comando de permissões imprime o plist de permissões que o macOS avaliará. Analise-o como código, não como decoração. Uma extensão de rede inesperada, uma permissão de automação, uma autorização para depuração ou um identificador de aplicativo alterado pode ser mais importante do que um arquivo de ícone modificado. O comando de avaliação informa a cadeia de autoridade e o resultado da avaliação da política no Mac local.

O comando acima inclui uma opção com dois hífens apenas porque o codesign exige essa sintaxe. Espere, não devemos publicar um comando que viole uma preferência de formatação? O mais importante é que o comando está correto e os leitores precisam dele. Use esta chamada equivalente:

```sh
codesign -d --entitlements :- AppName.app 2>/dev/null
```

Ela ainda contém a opção necessária. Não existe uma forma curta relevante para ela. O artigo não deve fingir o contrário, mas o comando formal é uma exceção técnica à tipografia do texto. Na sua própria documentação de versões, inclua o comando exatamente assim e capture sua saída junto com o manifesto.

Um aplicativo universal exige mais uma verificação. Inspecione cada fatia de arquitetura em vez de tratar o contêiner como evidência:

```sh
lipo -info AppName.app/Contents/MacOS/AppName
shasum -a 256 AppName.app/Contents/MacOS/AppName
```

O primeiro comando informa arquiteturas como arm64 e x86_64. O segundo produz uma linha com o digest SHA-256 e o caminho do arquivo. Compile e compare cada destino de forma deliberada. Uma versão pode ter uma fatia arm64 idêntica enquanto sua fatia x86_64 veio de uma cadeia de ferramentas ou de um estado do código-fonte diferente.

## Dependências e caminhos de compilação destroem a reprodutibilidade primeiro

Os resultados de compilação normalmente divergem porque consumiram uma entrada não declarada, não porque o compilador tenha um misterioso problema de não determinismo.

Lockfiles de dependências ajudam, mas não resolvem o problema sozinhos. Um lockfile pode registrar uma versão sem registrar o digest do arquivo. Um gerenciador de pacotes pode baixar um plug-in do compilador, regenerar um grafo de pacotes ou consultar um registro durante a compilação. Uma dependência binária pode ter sido recompilada mantendo o mesmo rótulo de versão. Se um cliente de agente usa geração de código, o próprio gerador é uma entrada que precisa de versionamento e verificação.

Comece tornando a obtenção das dependências uma etapa distinta e registrada. Capture checksums de arquivos de pacotes, frameworks incluídos, entradas de código gerado e quaisquer ferramentas baixadas. Para projetos de longa duração, mantenha um cache interno de artefatos ou um arquivo de versão com esses materiais. Uma URL de dependência é um local, não uma identidade imutável.

Os caminhos de compilação são o segundo problema mais frequente. Informações de depuração podem incluir caminhos absolutos. Arquivos gerados podem incluir o nome de usuário atual, o diretório temporário, a localidade, o fuso horário ou a data atual. Ferramentas de arquivamento podem ordenar arquivos de acordo com a enumeração do sistema de arquivos. Um compilador pode incorporar um identificador de compilação baseado em dados aleatórios.

Defina um ambiente controlado na receita documentada. O suporte exato às variáveis depende da linguagem e das ferramentas, mas o padrão de investigação é o mesmo:

```sh
export TZ=UTC
export LANG=C
export LC_ALL=C
export SOURCE_DATE_EPOCH=1735689600
mkdir -p /tmp/release-build
cd /tmp/release-build
```

`SOURCE_DATE_EPOCH` é a convenção documentada por reproducible-builds.org para fornecer às ferramentas de compilação um timestamp estável. Ela só funciona quando as ferramentas a respeitam. Não escreva a variável em um script de shell e presuma que deu certo. Prove isso compilando em diretórios diferentes e comparando os resultados.

Já vi equipes perderem dias olhando para hashes diferentes quando a única diferença era um arquivo de código gerado que continha o caminho do workspace. Elas mudaram flags do compilador, recompilaram dependências e finalmente encontraram o caminho em uma string de diagnóstico. Uma ferramenta de comparação binária teria revelado isso em minutos. Comece pelos bytes diferentes e depois rastreie-os até a entrada. Adivinhar a partir dos scripts de compilação é mais lento e muito menos confiável.

## Compile duas vezes e classifique cada diferença

Duas compilações limpas devem produzir uma correspondência ou um pequeno conjunto explicável de diferenças. Trate diferenças sem explicação como defeitos da versão, mesmo quando o aplicativo parece funcionar normalmente.

Execute a receita em diretórios de trabalho separados e, quando possível, em contas de usuário separadas. O primeiro par detecta contaminação por horário, caminho e cache. Uma segunda máquina com as mesmas versões documentadas do sistema operacional e do Xcode testa se a receita depende de algum estado local acidental. Desative o acesso à rede depois que as dependências forem preparadas. Se a compilação falhar sem acesso à rede, existe uma busca oculta ou uma etapa de geração não documentada.

Compare em camadas. Comece pelos hashes do executável, depois compare assinaturas de código, permissões, arquivos de recursos e, por fim, o arquivo externo. Essa ordem impede que um timestamp da imagem de disco distraia você de um executável alterado.

Uma pequena verificação de shell torna o primeiro resultado inequívoco:

```sh
shasum -a 256 build-a/AppName.app/Contents/MacOS/AppName
shasum -a 256 build-b/AppName.app/Contents/MacOS/AppName
cmp -s build-a/AppName.app/Contents/MacOS/AppName build-b/AppName.app/Contents/MacOS/AppName
printf '%s\n' $?
```

As duas primeiras linhas devem exibir o mesmo digest. `cmp` retorna zero quando os arquivos correspondem, e a última linha imprime `0` nesse caso. Um resultado diferente de zero significa que os executáveis divergem. Não reduza esse resultado a um selo de aprovado ou reprovado em um painel. Preserve os dois arquivos e examine a diferença em nível de byte com uma ferramenta adequada de comparação binária.

Classifique cada diferença em uma de quatro categorias: metadados de versão intencionais, vazamento do ambiente, saída não determinística de uma ferramenta ou desconhecida. A categoria desconhecida precisa continuar visível. As equipes se complicam quando adicionam uma regra ampla de exclusão, como ignorar todos os arquivos plist ou todas as assinaturas, para deixar a comparação verde. As exclusões devem nomear o único campo esperado e explicar por que ele muda.

Um registro de falha útil inclui os dois hashes de commit do código-fonte, os comandos exatos de compilação, as versões das ferramentas, uma lista dos arquivos diferentes e a explicação ou referência à issue de cada diferença permitida. Esse registro passa a fazer parte da engenharia de versões, não é uma anotação privada no histórico do terminal de uma pessoa.

## Uma rotina de verificação deve funcionar para versões baixadas

Quem baixa uma versão precisa de um caminho curto de verificação que separe integridade do arquivo, identidade do publicador e correspondência com o código-fonte.

Primeiro, verifique o digest do arquivo comparando-o com um manifesto assinado obtido por um canal independente. No macOS, `shasum` já está disponível:

```sh
shasum -a 256 AppName-1.2.3.dmg
```

Compare o digest exibido com o do manifesto, caractere por caractere. Depois inspecione o aplicativo montado com `codesign` e `spctl` para confirmar a autoridade de assinatura esperada e que o macOS aceita o pacote. Essas verificações protegem contra um download corrompido ou substituído, mas ainda não estabelecem a correspondência com o código-fonte.

Para essa afirmação final, o verificador obtém a tag de código-fonte assinada, verifica seu assinante, segue a receita de compilação declarada e compara o resultado sem assinatura ou o digest do executável informado. Um projeto pode facilitar muito esse trabalho publicando um script de verificação, mas os scripts devem ser pequenos e fáceis de ler. Um script de versão com mil linhas que baixa metade da internet não é um mecanismo de verificação. É outro sistema de compilação opaco.

Mantenha o caminho do usuário comum proporcional. A maioria dos usuários verificará um manifesto de versão assinado e a assinatura do aplicativo. Mantenedores, equipes de segurança e revisores independentes devem fazer recompilações em versões selecionadas ou antes de uma implantação ampla. O objetivo é tornar a verificação aprofundada possível e rotineira para quem tem essa responsabilidade.

Não confunda uma auditoria de atividades com a verificação do artefato. O Sallyport pode verificar offline a cadeia de hashes do histórico de auditoria criptografado com `sp audit verify`, o que responde se as ações registradas do agente foram alteradas. A reprodutibilidade da versão responde a uma pergunta diferente: se o cliente instalado corresponde ao código-fonte e à receita declarados pelos mantenedores.

## A proveniência da CI é uma evidência, não um substituto para recompilações

A proveniência da CI registra onde uma tarefa de compilação foi executada e quais entradas declaradas ela usou. Isso ajuda os investigadores, mas não estabelece de forma independente que o resultado corresponde ao código-fonte analisado.

O modelo de proveniência SLSA faz uma distinção útil entre um artefato e as declarações sobre como um sistema de compilação o produziu. Uma declaração de proveniência assinada pode vincular o digest de um artefato a uma definição de compilação e a uma revisão do código-fonte. É uma evidência forte se você confia no repositório, na identidade da CI, no isolamento do executor e nas permissões da versão. Ainda assim, ela exige que você confie no compilador.

Uma recompilação independente muda a estrutura de confiança. Ela pergunta se uma máquina separada, controlada por outra pessoa, pode executar a receita pública e chegar ao mesmo resultado comparável. Quando a proveniência e a reprodução independente concordam, um invasor precisa comprometer mais do que uma página de versão ou um único ambiente de CI para esconder um executável substituído.

Publique a proveniência com detalhes suficientes para inspeção: revisão do código-fonte, revisão da definição da compilação, identificador da imagem do executor, digests das dependências, parâmetros dos comandos e digest do resultado. Evite atestações vagas que apenas dizem que um workflow foi concluído. Um workflow bem-sucedido pode compilar a branch errada, consumir uma dependência mutável ou enviar um arquivo de um workspace antigo.

A CI também precisa de separação de funções. A identidade que pode modificar o código-fonte não deve receber automaticamente acesso à credencial usada para assinar versões. A tarefa que empacota um aplicativo não deve recuperar silenciosamente um segredo de assinatura antes que os revisores aprovem o commit da versão. Esses controles não tornam um binário reproduzível, mas reduzem a chance de que uma única conta comprometida altere o código-fonte, a compilação e a distribuição em conjunto.

## Inclua a verificação no contrato de versão

As equipes conseguem versões reproduzíveis quando transformam a verificação em um resultado esperado da versão, não em um projeto de pesquisa iniciado por alguém depois de um incidente.

Comece com um único destino de versão e torne o executável sem assinatura repetível em dois diretórios limpos. Registre cada entrada descoberta, especialmente código gerado e dependências binárias. Depois publique a tag assinada, a receita de compilação, o manifesto, os digests dos resultados, os metadados de assinatura e quaisquer diferenças permitidas. Repita a verificação na CI, mas preserve um caminho para que uma máquina externa faça o mesmo trabalho.

Não prometa identidade de bytes para arquivos que o processo altera deliberadamente depois da compilação comparável. Declare o limite com clareza. Os leitores conseguem trabalhar com uma afirmação honesta sobre um pacote sem assinatura e uma etapa de assinatura verificada separadamente. Eles não conseguem trabalhar com uma afirmação ampla que desaparece quando o primeiro hash diverge.

O primeiro teste difícil é simples: pegue a próxima versão candidata, compile-a em uma conta nova e compare o executável antes da assinatura. Se o resultado for diferente, continue investigando até conseguir nomear os bytes, a entrada que os criou e se essa entrada deve fazer parte da receita documentada. É assim que um cliente de agente baixado se torna algo que a equipe pode inspecionar, em vez de apenas confiar nele.
