---
title: "Cadeia de suprimentos de software: um roteiro prático depois do backdoor no xz"
description: "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."
date: "2026-09-29"
tags: ["seguranca", "supply-chain", "slsa", "sigstore", "sbom", "github-actions", "npm", "devops"]
url: "https://abjr.dev/noticias/cadeia-suprimentos-software-xz-slsa-cra"
---

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:

```yaml
- 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:

```yaml
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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.

```bash
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](/noticias/containers-nao-sao-vms-escape-kernel): o isolamento do job de CI é a última linha quando uma dependência maliciosa passa por tudo isso.

## Fontes

- [NVD — CVE-2024-3094 (xz-utils)](https://nvd.nist.gov/vuln/detail/CVE-2024-3094)
- [oss-security — anúncio original do backdoor no xz](https://www.openwall.com/lists/oss-security/2024/03/29/4)
- [NVD — CVE-2025-30066 (tj-actions/changed-files)](https://nvd.nist.gov/vuln/detail/CVE-2025-30066)
- [GitHub Docs — Security hardening for GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions)
- [SLSA v1.0 — Security levels](https://slsa.dev/spec/v1.0/levels)
- [Sigstore — documentação](https://docs.sigstore.dev/)
- [npm — Generating provenance statements](https://docs.npmjs.com/generating-provenance-statements)
- [PyPI — Trusted Publishers](https://docs.pypi.org/trusted-publishers/)
- [PEP 740 — Index support for digital attestations](https://peps.python.org/pep-0740/)
- [pnpm — onlyBuiltDependencies](https://pnpm.io/settings#onlybuiltdependencies)
- [CISA — SBOM](https://www.cisa.gov/sbom)
- [Comissão Europeia — Cyber Resilience Act](https://digital-strategy.ec.europa.eu/en/policy/cyber-resilience-act)
