Notícias8 min de leitura

Cadeia de suprimentos de software: um roteiro prático depois do backdoor no xz

O xz, o tj-actions e os worms no npm mostraram que o ataque mira o build e as credenciais de publicação. Como fixar dependências, assinar artefatos, gerar SBOM e o que o Cyber Resilience Act passa a exigir.

  • seguranca
  • supply-chain
  • slsa
  • sigstore
  • sbom
  • github-actions
  • npm
  • devops

Em março de 2024, um engenheiro investigando uma lentidão de meio segundo no SSH encontrou um backdoor no xz-utils (CVE-2024-3094). O código malicioso estava nas versões 5.6.0 e 5.6.1, inserido por um mantenedor que passou dois anos ganhando a confiança do projeto. O backdoor mirava o sshd em distribuições que o ligam à liblzma por meio do systemd.

O detalhe que mudou a conversa do setor foi outro: o gatilho do payload não estava no repositório Git. Estava nos tarballs de release, num script de build que não aparecia na revisão de código. Quem auditou o GitHub auditou a coisa errada.

Depois vieram outros casos com a mesma forma: a action tj-actions/changed-files teve as tags reescritas em 2025 para vazar segredos de CI, e o ecossistema npm viu pacotes populares comprometidos por phishing de mantenedores e até um worm que se espalhava usando os tokens de publicação que roubava. Em todos, o código-fonte "oficial" não era o ponto de entrada. O ponto de entrada era o caminho entre o código e o artefato que você instala.

Este texto é o roteiro que eu aplicaria num time pequeno, em ordem de custo-benefício.

O modelo de ameaça em uma frase

Você executa, com as suas credenciais, código que outras pessoas publicaram, construído em máquinas que você não controla. Cada elo tem um jeito de falhar:

Elo Como falha Exemplo
Mantenedor Conta roubada ou mantenedor malicioso xz; phishing de mantenedores npm
Build / release Artefato diferente do código revisado Tarball do xz
Registro / tag Referência mutável apontada para outro código Tags do tj-actions
Seu CI Segredos expostos a código de terceiros Actions que leem secrets e logs
Instalação Script que roda ao instalar postinstall malicioso no npm

O objetivo não é zerar o risco. É garantir que uma dessas falhas não vire, sozinha, acesso ao seu ambiente de produção.

1. Fixe o que você executa no CI

Tag de action é mutável. uses: fulano/acao@v4 executa o que o dono da tag quiser, na hora que quiser. Fixe por SHA de commit:

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

O comentário com a versão é para humanos e para o Dependabot, que continua abrindo PRs de atualização mesmo com SHA fixado. Configure isso em .github/dependabot.yml com package-ecosystem: "github-actions".

Depois, reduza o que o workflow pode fazer. O token padrão do GitHub Actions pode ter escrita no repositório. Declare o mínimo no topo:

permissions:
  contents: read

E dê permissão extra só no job que precisa. Duas regras que evitam os incidentes mais comuns:

  • Nunca use pull_request_target fazendo checkout do código do PR. Esse evento roda com segredos do repositório; combinar os dois entrega segredos para qualquer fork.
  • Segredo de deploy não entra em job que roda testes de PR. Separe em workflows diferentes, com environments protegidos.

2. Trave dependências e desligue scripts de instalação

Lockfile no repositório e instalação que respeita o lockfile:

npm ci
pnpm install --frozen-lockfile
pip install --require-hashes -r requirements.txt

npm install atualiza o lockfile quando acha que deve. npm ci falha se o lockfile não bate com o package.json. No CI, você quer a segunda coisa.

Scripts de instalação (preinstall, postinstall) são o jeito mais direto de um pacote comprometido executar código na sua máquina. O pnpm, a partir da versão 10, não executa scripts de dependências por padrão; você lista explicitamente quais pacotes podem rodar build (onlyBuiltDependencies). No npm, dá para desligar com --ignore-scripts ou ignore-scripts=true no .npmrc e liberar caso a caso.

Uma política que funciona bem: atualização de dependência com atraso. Não instale uma versão publicada há poucas horas. A maioria dos pacotes maliciosos é detectada e removida em pouco tempo; esperar alguns dias antes de aceitar a atualização elimina boa parte do risco sem custo real. Renovate e Dependabot permitem configurar essa idade mínima.

3. Verifique proveniência

Proveniência responde: este artefato saiu de qual código, construído por qual workflow? É exatamente a pergunta que o xz não deixava responder.

npm. Pacotes publicados com npm publish --provenance a partir do GitHub Actions ou GitLab CI carregam uma atestação assinada ligando o .tgz ao commit e ao workflow. Para conferir as assinaturas e atestações do que está instalado:

npm audit signatures

PyPI. Trusted Publishing troca o token de API guardado no CI por identidade OIDC de curta duração, e o PEP 740 adiciona atestações ao pacote publicado.

Seus próprios artefatos. A action actions/attest-build-provenance gera atestação para binários e imagens construídos no GitHub Actions. Quem consome verifica com:

gh attestation verify ./meu-binario --owner minha-org

Imagens de container. Com Sigstore, a assinatura usa a identidade do workflow, sem chave privada para guardar:

cosign verify ghcr.io/minha-org/api@sha256:... \
  --certificate-identity-regexp '^https://github.com/minha-org/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

A verificação só tem valor se alguém a exige. O passo seguinte é colocar isso na admissão do cluster (Kyverno, Sigstore policy-controller) para que imagem sem assinatura válida não suba.

4. Gere SBOM no pipeline

SBOM (Software Bill of Materials) é a lista do que está dentro do artefato, em SPDX ou CycloneDX. Não impede ataque. Encurta a resposta: quando sair o próximo CVE, a pergunta "estamos afetados?" vira uma busca, não uma arqueologia.

syft dir:. -o cyclonedx-json > sbom.json
grype sbom:./sbom.json

Gere o SBOM do artefato final, a imagem ou o binário, não só do repositório. O que vai para produção inclui a imagem base, pacotes do sistema e coisas que o lockfile não vê.

5. Proteja as credenciais de publicação

Nos casos do npm, o alvo era o token do mantenedor. Se você publica pacotes:

  • 2FA com chave de segurança (FIDO2), não SMS nem TOTP que pode ser capturado em página falsa;
  • publicação só pelo CI com Trusted Publishing, sem token de longa duração guardado em lugar nenhum;
  • tokens granulares, com escopo por pacote e prazo de expiração.

Onde o SLSA entra

O SLSA (Supply-chain Levels for Software Artifacts) organiza isso em níveis para o build. Em resumo: no nível 1, existe proveniência; no nível 2, ela é gerada e assinada por um serviço de build hospedado; no nível 3, o build é isolado, e um job não consegue adulterar a proveniência de outro. Os passos acima, com build no GitHub Actions e atestação assinada, colocam a maioria dos projetos perto do nível 2 ou 3 com pouco esforço. O valor do SLSA é dar um vocabulário comum para cobrar isso de fornecedores.

A regulação chegou: Cyber Resilience Act

O Cyber Resilience Act da União Europeia entrou em vigor em dezembro de 2024. As obrigações de notificação de vulnerabilidades ativamente exploradas e de incidentes graves passam a valer em setembro de 2026; o restante, em dezembro de 2027. Fabricantes de produtos com elementos digitais vendidos na UE precisam, entre outras coisas, manter SBOM, tratar vulnerabilidades durante o período de suporte e notificar incidentes às autoridades.

Mantenedores de open source sem atividade comercial ficaram fora do núcleo das obrigações, com um regime mais leve para fundações que apoiam projetos. Quem empacota e vende software construído sobre esses projetos, não. Para empresas brasileiras que vendem software ou dispositivos para a Europa, o roteiro acima deixa de ser boa prática e vira requisito de mercado.

Checklist

  • Actions de terceiros fixadas por SHA, com Dependabot atualizando.
  • permissions: contents: read como padrão em todos os workflows.
  • Nenhum pull_request_target com checkout do código do PR.
  • Instalação pelo lockfile (npm ci, --frozen-lockfile, --require-hashes).
  • Scripts de instalação desligados por padrão, liberados por pacote.
  • Idade mínima para aceitar novas versões de dependências.
  • Artefatos publicados com proveniência; imagens assinadas e verificadas na admissão.
  • SBOM gerado do artefato final e guardado junto com a release.
  • Publicação de pacotes só via CI com Trusted Publishing; 2FA com chave física.

Em resumo

O xz mostrou que confiar no código-fonte não basta: é preciso confiar no caminho até o artefato. Fixar referências, travar instalação, exigir proveniência e manter SBOM são medidas baratas que cortam as classes de ataque mais comuns dos últimos anos. E, na Europa, deixaram de ser opcionais.

Se o seu pipeline executa código de PRs de terceiros, vale ler também sobre por que containers não são VMs: o isolamento do job de CI é a última linha quando uma dependência maliciosa passa por tudo isso.

Fontes

Adelson de Bittencourt Junior

Desenvolvedor full-stack em Criciúma, SC, com passagem por suporte de telefonia VoIP e redes. Escreve sobre redes, segurança, infraestrutura e bancos de dados. Sobre o site · Enviar correção