Em dezembro de 2020, a SolarWinds revelou que o software Orion — usado por mais de 18.000 organizações incluindo múltiplas agências do governo americano — havia sido comprometido. O vetor não era uma vulnerabilidade em código da SolarWinds: era o build pipeline. Atacantes (posteriormente atribuídos ao SVR russo) comprometeram o processo de build e injetaram código malicioso no software antes de ser assinado e distribuído. O binário distribuído era legítimo, assinado pela SolarWinds, indistinguível do software esperado — e continha um backdoor ativo. O incidente redefiniu o que "código seguro" significa: não apenas o código que você escreve, mas todo o processo que produz e distribui o artefato final.
Em dezembro de 2021, a Apache Software Foundation divulgou a CVE-2021-44228 na biblioteca Log4j 2 — Log4Shell. Uma vulnerabilidade de JNDI injection que permitia execução remota de código com uma linha em qualquer campo logado. A biblioteca estava presente em praticamente toda aplicação Java corporativa — diretamente ou como dependência transitiva de outras bibliotecas. Organizações com SBOM (Software Bill of Materials) sabiam em horas se eram afetadas. Sem SBOM, a busca por "onde usamos Log4j?" levou dias ou semanas — tempo em que sistemas permaneceram expostos.
Esses dois incidentes, com menos de um ano de diferença, tornaram supply chain security uma prioridade que não pode mais ser delegada ao time de segurança enquanto os desenvolvedores se focam em features. A cadeia de suprimentos de software — dependências, pipelines de build, artefatos distribuídos — é superfície de ataque tão relevante quanto o código da aplicação.
O modelo de ameaça da cadeia de suprimentos
A cadeia de suprimentos de software tem múltiplos pontos de compromisso potencial. O modelo de ameaça típico inclui:
- Dependências comprometidas: um pacote que você usa é atualizado com código malicioso (typosquatting, account takeover do mantenedor, ou injeção no repositório do pacote). Exemplo: event-stream (npm, 2018) — o mantenedor original transferiu o pacote e o novo dono injetou código que roubava bitcoins de carteiras específicas.
- Dependências com vulnerabilidades conhecidas: Log4Shell é o exemplo canônico. A dependência é legítima mas tem CVE pública não corrigida na versão que você usa.
- Build pipeline comprometido: SolarWinds. O código-fonte é seguro, mas o processo de build é comprometido — o artefato final é diferente do código revisado.
- Repositório de artefatos comprometido: imagens de container no Docker Hub ou pacotes no npm/PyPI são substituídos por versões maliciosas após publicação.
- Dependência de dependência: você usa biblioteca A que usa biblioteca B que tem a vulnerabilidade. Dependências transitivas são frequentemente não auditadas.
SBOM — Software Bill of Materials
SBOM é o inventário completo de componentes de software — dependências diretas e transitivas, versões, licenças, e origens. O conceito vem da manufatura: uma BOM de produto físico lista cada componente, do parafuso ao chip, permitindo rastrear problemas a fornecedores específicos e responder rapidamente a recalls. Para software, a analogia é exata: quando Log4Shell foi divulgada, organizações com SBOM consultaram o inventário e sabiam em minutos se tinham Log4j e em qual versão.
Os dois formatos padrão de SBOM:
- SPDX (Software Package Data Exchange): formato da Linux Foundation, ISO/IEC 5962:2021. Mais antigo, amplamente suportado por ferramentas legais e de compliance.
- CycloneDX: formato do OWASP, focado em segurança e análise de vulnerabilidades. Suporta hardware, firmware, e serviços além de software. Mais adotado em tooling de segurança moderno.
# Syft — geração de SBOM (CNCF, open source)
# Para uma imagem de container
syft myapp:latest -o cyclonedx-json > sbom.json
syft myapp:latest -o spdx-json >> sbom-spdx.json
# Para um diretório de código
syft dir:. -o cyclonedx-json > sbom.json
# Grype — verificação de vulnerabilidades contra o SBOM
grype sbom:sbom.json # relatório de vulnerabilidades
grype sbom:sbom.json --fail-on high # falha o CI se houver CVE high ou critical
# Integração em GitHub Actions
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myapp:latest
format: cyclonedx-json
output-file: sbom.json
- name: Scan for vulnerabilities
uses: anchore/scan-action@v3
with:
sbom: sbom.json
fail-build: true
severity-cutoff: high
O SBOM gerado deve ser tratado como artefato de build: versionado junto com o código, armazenado no registro de imagens ou como asset de release, e atualizado em cada deploy. Em ambientes regulados (defesa, saúde, infraestrutura crítica), o SBOM é frequentemente requisito contratual — o US Executive Order on Cybersecurity de 2021 tornou SBOM obrigatório para software vendido ao governo federal americano.
SLSA — Supply chain Levels for Software Artifacts
SLSA (pronunciado "salsa") é um framework de maturidade para segurança da cadeia de suprimentos, desenvolvido pelo Google em 2021 e agora mantido pela OpenSSF (Open Source Security Foundation). SLSA define quatro níveis de maturidade, cada um com requisitos específicos sobre o processo de build:
- SLSA 1: o processo de build é documentado e automatizado. O build é scripted (não manual). Mínimo para qualquer sistema novo.
- SLSA 2: o build usa um build service (GitHub Actions, Google Cloud Build, etc.) e produz proveniência assinada — um documento que descreve o que foi buildado, de qual fonte, com quais inputs.
- SLSA 3: a proveniência é verificável por terceiros e o build service é auditado. O código-fonte da dependência é verificado antes do build. Requer build hermeticamente isolado (sem acesso à rede durante o build, para prevenir injeção de dependências não revisadas).
- SLSA 4: two-person review de mudanças de código, processo de build totalmente reproduzível (mesmo input → mesmo output bitwise), e verificação de proveniência end-to-end. O nível de maturidade que previne o ataque SolarWinds.
A maioria das organizações começa no SLSA 1 (build automatizado) e visa SLSA 2-3 (proveniência assinada, build service auditado). SLSA 4 é alcançável mas requer investimento significativo em infraestrutura de build.
Dependabot e Renovate — atualização automática de dependências
Mantener dependências atualizadas é o controle mais simples e mais ignorado de supply chain security. Vulnerabilidades como Log4Shell têm patches disponíveis rapidamente após a divulgação — a janela de exposição é quanto tempo leva para o time atualizar a dependência.
Dependabot (GitHub, gratuito) abre automaticamente PRs para atualizar dependências quando novas versões são publicadas, e adicionalmente abre PRs de segurança quando CVEs são publicadas para dependências que o repositório usa — independente do schedule normal de atualização.
# .github/dependabot.yml — configuração Dependabot
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
groups: # agrupa updates de patches em 1 PR
minor-and-patch:
update-types:
- "minor"
- "patch"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"] # major requer revisão manual
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
Renovate (open source, mais configurável que Dependabot) adiciona funcionalidades como grouping avançado, auto-merge para patches (após CI verde), e suporte a mais ecosystems. Para organizações com múltiplos repositórios, Renovate com configuração compartilhada (via repositório de preset) é mais gerenciável que Dependabot configurado por repositório.
Dependabot e Renovate geram PRs — que precisam ser revisados, testados, e mergeados. Em repositórios com centenas de dependências, o volume de PRs pode ser esmagador, levando o time a ignorar ou fechar os PRs sem revisar. A solução é configuração intencional: auto-merge para patches com CI verde, PRs agrupados por categoria, e rate limiting de PRs abertos simultaneamente. Um sistema que abre PRs que ninguém processa é pior que nenhum sistema — cria falsa sensação de cobertura.
Sigstore e Cosign — assinatura de artefatos
Sigstore é um projeto da Linux Foundation que torna a assinatura de artefatos de software acessível — sem a complexidade de gerenciar chaves GPG por longos períodos. O componente central para imagens de container é Cosign.
O modelo de segurança do Sigstore usa keyless signing com OIDC: em vez de uma chave privada de longa duração (que precisa ser armazenada com segurança, rotacionada, e nunca comprometida), o assinante obtém um certificado de curta duração (10 minutos) de um serviço Fulcio após autenticação OIDC com GitHub/Google/Microsoft. O artefato é assinado com esse certificado efêmero, e o log de transparência Rekor registra o evento. Qualquer um pode verificar que o artefato foi assinado por aquela identidade OIDC naquele momento — sem precisar confiar em uma chave pública distribuída.
# Assinar imagem com Cosign (keyless, via GitHub Actions OIDC)
# .github/workflows/sign.yml
jobs:
sign:
runs-on: ubuntu-latest
permissions:
id-token: write # necessário para OIDC token
packages: write # para push no registry
steps:
- name: Sign container image
uses: sigstore/cosign-installer@v3
- run: |
cosign sign --yes \
ghcr.io/myorg/myapp:latest@sha256:DIGEST
# Verificar a assinatura (em qualquer máquina)
cosign verify \
--certificate-identity "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myapp:latest
# Em Kubernetes — policy que exige imagens assinadas (Sigstore Policy Controller)
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-signed-images
spec:
images:
- glob: "ghcr.io/myorg/**"
authorities:
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subject: https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main
A assinatura de imagens com Cosign integrada à política do Kubernetes (via Sigstore Policy Controller ou Kyverno) garante que apenas imagens assinadas pelo pipeline de CI autorizado podem ser executadas no cluster. Isso mitiga o cenário onde uma imagem maliciosa é injetada no registry — sem assinatura válida, o cluster recusa o deploy.
Dependency pinning e verificação de integridade
Além de atualizar dependências, é necessário garantir que a versão que você declara é a versão que você recebe. Package managers modernos têm mecanismos de lockfile (package-lock.json, Pipfile.lock, go.sum, Cargo.lock) que registram hashes criptográficos de cada dependência — qualquer alteração na dependência após publicação altera o hash e o install falha.
Para imagens de container em Dockerfiles, referenciar por tag (como FROM python:3.13) é mutável — a tag pode ser redirecionada para uma imagem diferente. Referenciar por digest (FROM python:3.13@sha256:abc123...) é imutável — o hash garante que é exatamente a imagem verificada. Ferramentas como Renovate podem atualizar automaticamente os digests quando novas versões são publicadas.
# Dockerfile — tag mutável (menos seguro)
FROM python:3.13
# Dockerfile — digest imutável (mais seguro)
FROM python:3.13@sha256:a814e9e0e0c8843b48be7c1d5e3e3b3e5a05b5b4c3e1e3b4c5a8e9e0e8c5a3b2
# go.sum — verificação de integridade de módulos Go
# Cada módulo tem seu hash registrado — go build falha se o hash divergir
github.com/gorilla/mux v1.8.1 h1:TnbdfS2HFR4NMW47NKrNkCqT7WFxGHBeqA9qQxR3biA=
github.com/gorilla/mux v1.8.1/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
Typosquatting e namespace confusion
Typosquatting é a publicação de pacotes com nomes similares a pacotes populares para enganar desenvolvedores que digitam errado: reqests em vez de requests, coloama em vez de colorama. Em 2021, um pesquisador publicou pacotes com nomes de componentes internos de empresas como Apple, Microsoft, e Shopify — e os builds automatizados dessas empresas instalaram os pacotes maliciosos, demonstrando o vetor de dependency confusion.
Dependency confusion explora a precedência de registry: quando uma empresa tem pacotes internos com nomes não registrados no registry público (npm, PyPI), um atacante publica um pacote com o mesmo nome no registry público com versão maior — e o package manager baixa o pacote público em vez do interno. A mitigação é usar scoped packages (@mycompany/package no npm, namespace explícito no PyPI), configuração de registry privado que tem precedência sobre o público para os namespaces da empresa, e verificação de proveniência.
Decisões de engenharia
Escolha CycloneDX quando o objetivo principal é segurança — análise de vulnerabilidades, integração com Grype/Dependency-Track, e rastreabilidade em incidentes. CycloneDX tem melhor suporte em tooling de segurança moderno e permite modelar componentes de hardware, firmware, e serviços além de bibliotecas. Escolha SPDX quando o requisito é compliance legal ou auditoria de licenças — SPDX é ISO/IEC 5962:2021 e é o formato que ferramentas legais e compliance de licença (como FOSSA) preferem. Para ambientes regulados que exigem SBOM por contrato (governo americano, defesa), SPDX é frequentemente especificado explicitamente.
Dependabot é a escolha padrão para repositórios GitHub — zero configuração, gratuito, e integrado nativamente. Adequado para projetos menores ou times que não querem investir em configuração. Renovate é preferível quando você tem múltiplos repositórios (configuração compartilhada via preset), precisa de auto-merge condicional (patches com CI verde), agrupar todas as dependências de uma mesma ferramenta em um único PR, ou usar ecosystems não suportados pelo Dependabot. Renovate tem curva de configuração maior mas oferece controle significativamente mais granular. Para organizações com 10+ repositórios, Renovate com preset central escala muito melhor.
SLSA 1 (build automatizado e documentado) é o mínimo não-negociável — qualquer processo de build manual é indefensável. SLSA 2 (build service auditado com proveniência assinada) é a meta realista para a maioria das organizações — GitHub Actions e Google Cloud Build têm suporte nativo, e o esforço é razoável. SLSA 3 (build hermético, proveniência verificável por terceiros) é adequado para projetos críticos ou de infraestrutura. SLSA 4 (reproduzível bitwise, two-person review obrigatório) é praticamente apenas para projetos de altíssimo risco ou exigência regulatória — o custo operacional é significativo. Comece com SLSA 2 e evolua conforme o perfil de risco justifica.
Keyless signing (via OIDC + Fulcio) é a abordagem recomendada para a maioria dos casos: sem chave privada para gerenciar, rotacionar, ou comprometer — a identidade do workflow de CI é a âncora de confiança. A assinatura é vinculada ao repositório e branch específicos. Key-based signing com chave de longa duração faz sentido quando você precisa assinar offline, em ambientes air-gapped sem acesso à internet, ou quando a política organizacional requer chaves HSM com custody explícita. O custo do key-based é a gestão da chave privada — que precisa de secrets management robusto, rotação, e plano de revogação em caso de comprometimento.
Perguntas de entrevista
O que diferencia o ataque SolarWinds de uma vulnerabilidade como Log4Shell como vetor de risco?
Log4Shell é uma vulnerabilidade no código de uma dependência — o problema está no software em si, e a mitigação é atualizar para a versão corrigida. O risco é que você use uma versão vulnerável sem saber (Log4j como dependência transitiva), mas a natureza do ataque é passiva: a vulnerabilidade existe e pode ser explorada, mas o código distribuído é o código que foi escrito.
SolarWinds é fundamentalmente diferente: o código-fonte estava correto, as revisões de código foram feitas, os testes passaram — mas o artefato distribuído continha código malicioso injetado durante o processo de build. Isso significa que mesmo com segurança de código excelente, um build pipeline comprometido pode produzir artefatos maliciosos que passam por todas as verificações convencionais. A defesa requer SLSA 4 (proveniência verificável end-to-end e builds reproduzíveis), que é o único nível que torna esse vetor detectável. SolarWinds mostrou que o perímetro de segurança precisa incluir o pipeline de build, não apenas o código.
Por que referenciar imagens Docker por digest em vez de tag aumenta a segurança?
Tags de imagem são mutáveis — python:3.13 pode apontar para qualquer imagem, e o mantenedor (ou um atacante que comprometeu o registry) pode redirecionar a tag para uma imagem diferente sem aviso. Dois builds com FROM python:3.13 executados em datas diferentes podem estar usando imagens completamente distintas.
Digests de imagem (@sha256:abc123...) são imutáveis por design — o digest é o hash SHA-256 do manifesto da imagem. Se o conteúdo da imagem mudar, o digest muda. Referenciar por digest garante que você sempre obtém exatamente a imagem que você verificou, independentemente do que aconteça com a tag. A desvantagem é que digests não se atualizam automaticamente com novas versões de segurança — por isso o workflow correto é combinar pinning por digest com Renovate configurado para abrir PRs que atualizam os digests periodicamente, combinando imutabilidade com manutenção ativa.
O que é dependency confusion e como mitigá-lo?
Dependency confusion explora como package managers resolvem conflitos entre pacotes privados e públicos. Se uma empresa tem um pacote interno chamado mycompany-utils hospedado em um registry privado, e um atacante publica um pacote com o mesmo nome no npm/PyPI público com uma versão maior (ex: 9999.0.0), muitas configurações de package manager vão preferir o pacote público (versão maior) sobre o interno — baixando código malicioso silenciosamente.
Mitigações: (1) Use namespaced packages — @mycompany/utils no npm, namespace explícito no PyPI — prefixos que não existem no registry público; (2) Configure o registry privado como único source para os namespaces da empresa, com o registry público como fallback apenas para pacotes que não estão no namespace privado; (3) Verifique proveniência de todos os pacotes instalados no CI — Syft + Grype detectam pacotes de origens inesperadas; (4) Use Artifact Registry (GCP) ou Nexus com proxy virtualizado que aplica policies de namespace antes de consultar registries públicos.
Como um SBOM acelera a resposta a incidentes de supply chain?
Quando uma vulnerabilidade como Log4Shell é publicada, a pergunta crítica é "quais dos nossos sistemas são afetados?". Sem SBOM, responder exige varrer o código-fonte de cada repositório, analisar arquivos de dependência de cada linguagem/ecosystem, rastrear dependências transitivas manualmente — processo que leva dias a semanas em organizações com centenas de serviços.
Com SBOM, a resposta é uma query: grype sbom:sbom.json --only-fixed | grep log4j ou uma consulta ao Dependency-Track (plataforma de gestão de SBOM) que indexa todos os SBOMs de todos os serviços. Em minutos, você tem a lista completa de serviços afetados, a versão exata em cada um, e se há um patch disponível. O SBOM transforma "pânico de dias" em "triagem de horas". Durante Log4Shell, a diferença entre ter SBOM e não ter foi medida em tempo de exposição — organizações sem SBOM ficaram vulneráveis dias a mais enquanto faziam inventário manual.
Como keyless signing com Sigstore funciona e por que é mais prático que chaves GPG tradicionais?
No modelo tradicional com chaves GPG, você gera um par de chaves, armazena a chave privada com segurança (secrets manager, HSM), distribui a chave pública para verificadores, e precisa rotacionar a chave periodicamente. Se a chave privada vazar, todas as assinaturas históricas ficam suspeitas e você precisa revogar e redistribuir. É operacionalmente pesado e frequentemente ignorado.
No modelo keyless do Sigstore: o pipeline de CI se autentica via OIDC (GitHub Actions tem token OIDC nativo), obtém um certificado de curta duração (10 minutos) do Fulcio CA que está vinculado à identidade OIDC (https://github.com/myorg/myapp/.github/workflows/release.yml), usa esse certificado efêmero para assinar o artefato, e o evento é registrado no Rekor (transparency log público e imutável). Para verificar, você não precisa de chave pública distribuída — verifica a identidade OIDC no log de transparência. Não há chave privada para gerenciar, rotacionar, ou comprometer. A âncora de confiança é a identidade do workflow, que está amarrada ao repositório e branch. É mais simples de adotar e operacionalmente mais robusto.
Como praticar
-
Gerar e analisar SBOM de um projeto existente. Instale Syft e execute contra um projeto ou imagem Docker que você mantém. Analise o SBOM gerado: quantas dependências diretas? Quantas transitivas? Execute Grype para verificar CVEs — provavelmente encontrará pelo menos algumas com severity medium ou high. Priorize a correção das high/critical e documente as medium como backlog de segurança.
Critério: O SBOM deve ser gerado em formato CycloneDX JSON e conter pelo menos as dependências diretas do projeto. O Grype deve retornar um relatório sem erros de execução. Identificar e documentar ao menos uma CVE high ou critical com o caminho de dependência (direta ou transitiva) que a introduziu. -
Configurar Dependabot em um repositório GitHub. Crie o arquivo
.github/dependabot.ymlcom os ecosystems relevantes para o projeto (npm, pip, go modules, Docker, GitHub Actions). Configure grouping de patches para reduzir volume de PRs. Observe os primeiros PRs gerados — verifique os changelogs das atualizações e decida quais podem ser auto-merged com CI verde.
Critério: Após merge do arquivo de configuração, o Dependabot deve abrir pelo menos um PR de atualização de dependência em até 48h. O PR deve incluir o changelog da versão alvo. Configurar uma regra de grouping e verificar que patches do mesmo ecosystem aparecem agrupados em um único PR. -
Assinar uma imagem de container com Cosign. Em um repositório GitHub com GitHub Actions, adicione um workflow de release que builda a imagem, faz push no ghcr.io, e assina com
cosign sign --yes. Verifique a assinatura localmente comcosign verify. Observe as entradas no Rekor transparency log — a assinatura é pública e auditável por qualquer pessoa.
Critério:cosign verify --certificate-identity <workflow-url> --certificate-oidc-issuer https://token.actions.githubusercontent.com ghcr.io/<org>/<repo>:latestdeve retornar sucesso com os claims de identidade corretos. Localizar a entrada no Rekor em rekor.sigstore.dev com o UUID da assinatura. -
Integrar Grype no pipeline de CI com política de falha. Adicione um step de Grype ao GitHub Actions que gera SBOM com Syft e verifica vulnerabilidades com
--fail-on high. Introduza intencionalmente uma dependência com CVE conhecida alta (por exemplo, uma versão antiga de uma biblioteca com CVE pública). Verifique que o CI falha com saída clara identificando a vulnerabilidade.
Critério: O CI deve falhar com exit code não-zero ao encontrar CVE de severity high ou critical. O output deve identificar o nome da dependência, a versão vulnerável, o CVE ID, e a versão corrigida disponível. Após atualizar para a versão corrigida, o CI deve passar. -
Avaliar a postura de supply chain de um projeto open source com OpenSSF Scorecard. Execute
scorecard --repo github.com/<org>/<repo>em um projeto que você usa como dependência. Analise os resultados: quais checks passaram? Quais falharam? Os checks cobrem: Branch-Protection, CI-Tests, Code-Review, Dependency-Update-Tool, Pinned-Dependencies, SAST, Signed-Releases, Token-Permissions, e outros. Use os resultados para decidir se a dependência tem o nível de maturidade de supply chain adequado para o risco que ela representa no seu sistema.
Critério: Produzir um relatório com o score total e os 3 checks com pior avaliação, com análise de se cada falha representa risco real para o uso da dependência. Para pelo menos uma falha, propor como o projeto poderia corrigi-la.
Referências para aprofundar
- article SolarWinds Attack — Technical Analysis — FireEye/Mandiant (2020).
- article Log4Shell (CVE-2021-44228) — Timeline and Analysis — LunaSec.
- docs SLSA — Supply Chain Levels for Software Artifacts.
- docs Syft — SBOM Generator.
- docs Sigstore — Cosign, Fulcio, Rekor.
- article Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies — Alex Birsan (Medium, 2021).
- docs CycloneDX — Specification — OWASP.
- article OpenSSF Scorecard — Measuring Open Source Security — OpenSSF.
- video Securing the Software Supply Chain — Dan Lorenc (KubeCon NA 2022).
- article NIST Cybersecurity Executive Order — SBOM Requirements.
- book Hacking the Hacker — Roger Grimes (Wiley, 2017).
- article In-toto — A Framework for Securing Supply Chains — NYU Secure Systems Lab.