Ir para o conteúdo

Segurança do ciclo de desenvolvimento

Os controles que protegem um release do PEART rodam continuamente durante o desenvolvimento, não só na hora do release. O invariante que guia isso é que make check é igual ao CI: os mesmos portões que um mantenedor roda localmente são os que precisam passar em cada push para a main, então um commit que reprovaria no CI reprova antes na máquina do desenvolvedor.

Os portões em cada commit

O make check roda, e o CI impõe:

  • Análise estática de segurança com gosec, ligada como linter do golangci-lint ao lado das passagens padrão de vet e lint.
  • Varredura de segredos com gitleaks, para que uma credencial commitada por acidente reprove o build.
  • Varredura de vulnerabilidade em dependências com govulncheck sobre toda a árvore de dependências.
  • Um portão de sintaxe de JavaScript que parseia cada script com um parser de ES-module em Go puro, pegando um script quebrado antes de sair (sem Node no pipeline).
  • Checagens de comprimento de linha e formatação, e uma checagem de consistência de i18n entre os arquivos de locale.

Os testes abrangem testes unitários, testes table-driven para a lógica mais delicada, testes de integração e testes de navegador. Um teste de navegador merece ser nomeado: ele carrega a casca real da página num navegador real e reprova se a Content-Security-Policy que o servidor envia é de fato violada no console, não apenas ausente do fonte da página, para que a CSP sobre a qual toda a segurança do front-end se apoia não possa regredir em silêncio. O código do próprio projeto carrega zero ocorrências de innerHTML, unsafeHTML ou unsafeStatic, uma propriedade que é medida, não presumida.

Build e distribuição

Os binários são Go cross-compilado puro, sem CGO (ADR 0004), compilados com -trimpath para que nenhum caminho da máquina de build vaze no artefato. Mudanças de schema do banco são migrações forward-only, revisáveis uma a uma.

Bill of materials do software

Cada release publica um SBOM em CycloneDX. Ele é produzido pelo cyclonedx-gomod para o grafo de módulos Go, e depois um segundo passo dobra nele os pacotes de front-end vendorizados sob web/vendor/ (o Lit e um gerador de QR-code) como componentes CycloneDX também, então o SBOM publicado lista um pacote npm vendorizado do mesmo jeito que lista um módulo Go. O bill of materials fica, portanto, completo nos dois ecossistemas de linguagem, não só no lado Go.

Releases assinados e atestados

Os releases são assinados sem chave (keyless) com o cosign, usando o token OpenID Connect de curta vida do próprio fluxo de release, em vez de qualquer chave privada de longa vida que o projeto teria que proteger ou rotacionar. As assinaturas são publicadas no log de transparência público do Sigstore.

Assinados no release: o manifesto de checksums, o SBOM e a imagem de container (assinada pelo digest do conteúdo, não por uma tag que se move). Qualquer um pode verificar o manifesto de checksums contra a identidade do fluxo de release:

cosign verify-blob \
  --certificate-identity-regexp 'https://github.com/mentesan/peart/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --signature checksums.txt.sig --certificate checksums.txt.pem \
  checksums.txt

e a imagem pelo digest:

cosign verify \
  --certificate-identity-regexp 'https://github.com/mentesan/peart/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/mentesan/peart@<digest>

A proveniência do front-end vendorizado é hoje afirmada pelo web/vendor/MANIFEST.md; verificar cada arquivo vendorizado por hash contra o pacote de origem no CI é um endurecimento planejado.