0020 — Binários publicados com proveniência assinada¶
Status: Aceita Data: 2026-10-02
Contexto¶
Até aqui, o nfse era distribuído só pelo código-fonte:
go install github.com/edusouza/nfse-emissor-go/cmd/nfse@vX.Y.Z. A integridade
vinha do checksum database do Go (sum.golang.org): quem instala recebe
exatamente o código da tag, verificado.
Isso exige o Go instalado, o que afasta justamente o público do projeto — prestadores do Simples Nacional que querem emitir a própria nota, não programar. O passo seguinte natural é publicar binários prontos.
Um binário pronto tem um problema que o go install não tem: ninguém sabe
de onde ele veio. E este binário recebe o certificado A1 e a senha — a
chave que assina em nome da empresa. Um binário adulterado na release, ou
construído na máquina de alguém a partir de um código que não é o da tag,
seria o ataque mais barato contra todos os usuários de uma vez. A
issue #48 registrou
isso como lacuna no SECURITY.md.
Decisão¶
Os binários são construídos só pela CI, a partir de uma tag, e publicados
com proveniência assinada. O workflow .github/workflows/release.yml roda
quando uma tag vX.Y.Z é enviada e:
- confere que a tag segue o formato de versão e que o commit está no
master; - roda os testes;
- constrói para Linux, macOS e Windows, em
amd64earm64, comCGO_ENABLED=0e-trimpath, gravando a versão no binário; - gera o
SHA256SUMSe um SBOM CycloneDX do módulo; - gera a proveniência SLSA e a atestação do SBOM de cada binário, com
actions/attest-build-provenanceeactions/attest-sbom; - publica tudo na release da tag.
A assinatura é do Sigstore, sem chave. O workflow se identifica por OIDC, o Sigstore emite um certificado de curta duração para aquela execução e registra a assinatura no log público de transparência. Não existe chave privada de assinatura para guardar, rotacionar ou vazar — e a assinatura diz qual workflow, de qual repositório, em qual commit construiu o binário.
Conferir é um comando:
gh attestation verify nfse_v1.0.0_linux_amd64 --repo edusouza/nfse-emissor-go
go install continua sendo o caminho principal. Os binários são para
quem não tem o Go; quem tem continua com a verificação do checksum database.
Alternativas consideradas¶
- Assinar com uma chave GPG ou cosign do mantenedor. Exige guardar uma chave privada de longa duração, que vira o novo alvo, e não prova onde o binário foi construído — só quem o assinou.
- GoReleaser. Faz tudo isto e mais, mas é uma dependência de terceiros no
caminho da publicação, com configuração própria. O que precisamos cabe em
um laço de
go builde duas ações oficiais do GitHub. - Não publicar binários. Mantém a superfície menor, mas deixa de fora quem mais precisa do projeto. A lacuna era a ausência de verificação, não a existência de binários.
Consequências¶
- O workflow tem permissão de escrita (
contents: write) para criar a release, além deid-token: writeeattestations: write. Elas ficam no job, não no workflow, e só esse workflow as tem. O job roda no environmentrelease, onde o mantenedor pode exigir aprovação antes de publicar. - Quem envia uma tag publica uma versão. Proteger as tags
v*(Settings → Rules) impede que alguém sem permissão de manutenção crie uma. - A conferência depende do usuário. A proveniência só protege quem a confere. O README e as notas de cada release mostram o comando.
- O SBOM é gerado pelo
cyclonedx-gomod, chamado porgo runem versão fixa e verificado pelo checksum database — não por uma ação de terceiros.