Segurança em CI/CD: o pipeline é uma superfície de ataque, não um detalhe de infra
O pipeline tem credencial de produção, roda código de terceiros e faz deploy. Veja como fechar segredo, execução de PR hostil e supply chain do build.
Neste artigo
Um projeto de código aberto recebia contribuições da comunidade, e o fluxo era o esperado: alguém abre um pull request, o CI roda os testes, um mantenedor revisa e faz o merge. O que ninguém notou é que o workflow de CI, gatilhado na abertura do PR, rodava com acesso aos segredos do repositório e executava scripts que vinham do próprio PR. Um contribuidor mal-intencionado abriu um pull request cujo script de build, em vez de compilar, exfiltrava as variáveis de ambiente — incluindo o token com permissão de publicar pacotes e a chave de deploy de produção — para um servidor externo. O ataque não tocou uma linha da aplicação. Ele explorou o pipeline, que ninguém tratava como parte da superfície de segurança, e que ao mesmo tempo tinha as chaves do reino e executava código de estranhos.
O CI/CD é, para um atacante, um dos alvos mais valiosos que existem: concentra credenciais de produção, roda automaticamente, executa código de terceiros a cada commit e tem autoridade para colocar artefatos direto no ambiente que serve os usuários. Comprometer o pipeline muitas vezes é mais fácil e mais lucrativo do que comprometer a aplicação. Este artigo trata de endurecer essa superfície esquecida.
Por que o pipeline é um alvo de primeira linha#
Três propriedades tornam o CI/CD irresistível. Primeiro, ele detém segredos de alto valor — tokens de deploy, chaves de nuvem, credenciais de registries — porque precisa deles para fazer seu trabalho. Segundo, ele executa código: o seu, o das dependências, o de plugins e actions de terceiros, e às vezes o de contribuidores externos. Terceiro, ele tem autoridade de deploy — o que sai do pipeline vai para produção com pouca ou nenhuma fricção adicional. Um atacante que injeta uma linha maliciosa em qualquer ponto dessa cadeia pode roubar os segredos, adulterar o artefato ou fazer deploy de código próprio, e frequentemente consegue isso sem jamais aparecer no código que passa em revisão.
A consequência é que segurança de pipeline não é uma preocupação de "infra" separada do desenvolvimento: é parte integral da segurança do produto, e merece o mesmo rigor de threat modeling que a aplicação recebe.
Segredo no CI: o erro mais caro#
O segredo no pipeline exige duas defesas complementares. A primeira é como ele é armazenado e acessado; a segunda é como ele é evitado. Sobre armazenamento: segredos ficam no cofre de segredos da plataforma de CI, injetados como variáveis mascaradas apenas nos jobs que precisam deles, nunca commitados no repositório nem impressos em log. Cuidado com o echo acidental — um comando de debug que ecoa o ambiente vaza tudo no log do build, que muitas vezes é público ou amplamente acessível. Plataformas mascaram segredos conhecidos no log, mas essa mascara não cobre transformações (o segredo em base64, concatenado, partido) — então a disciplina de nunca imprimir continua sendo sua.
A defesa superior, porém, é eliminar o segredo de longa duração. Em vez de guardar uma chave permanente de nuvem no CI — que, se vazar, vale até alguém perceber e revogar — o padrão moderno é a federação de identidade via OIDC: o provedor de CI prova sua identidade ao provedor de nuvem, que emite uma credencial temporária, de escopo restrito, que expira em minutos. Não há segredo de longa duração para vazar. É a aplicação de menor privilégio e de credencial efêmera ao pipeline, e reduz drasticamente o valor de um comprometimento.
Executar código de PR não confiável#
O incidente de abertura ilustra uma armadilha específica e famosa: gatilhos de CI que rodam código vindo de um pull request externo com acesso aos segredos do repositório. Em algumas plataformas, o gatilho padrão para PRs de forks é seguro (roda sem segredos), mas existe um gatilho alternativo — pensado para casos em que o workflow precisa de contexto privilegiado — que roda no contexto do repositório base, com segredos, e ainda assim pode acabar executando código controlado pelo autor do PR. Usar esse gatilho para rodar scripts do PR é entregar as credenciais para qualquer desconhecido que abra um pull request.
A regra: código de fonte não confiável (fork, contribuidor externo) roda sem segredos e sem privilégio, num ambiente isolado, e qualquer etapa que precise de segredo acontece só depois de revisão humana, num contexto separado que não expõe o pipeline privilegiado ao código não revisado. Trate todo PR externo como código hostil até que um humano tenha olhado — porque, do ponto de vista do pipeline, é exatamente isso que ele é.
A supply chain do build#
O pipeline não roda só o seu código; ele compõe passos a partir de actions, plugins, imagens e ferramentas de terceiros. Cada um é uma dependência, e cada dependência é um vetor. Referenciar uma action por tag mutável (@v3, @main) significa que o conteúdo por trás daquele nome pode ser alterado por quem a mantém — ou por quem comprometeu a conta de quem a mantém — sem que você perceba, e a versão nova roda no seu pipeline privilegiado na próxima execução. Já houve incidentes reais em que uma action popular foi comprometida e passou a exfiltrar segredos de milhares de pipelines de uma vez. A defesa é pinar por hash de commit imutável (o SHA completo), de modo que você roda exatamente o código que auditou, e atualizar conscientemente quando decide revisar a nova versão.
Esse tipo de ataque tem nome — poisoned pipeline execution — e engloba qualquer forma de fazer o pipeline executar código malicioso: seja pela dependência comprometida, pela action envenenada, pelo script de build de um pacote instalado, ou pela configuração do próprio pipeline que um atacante conseguiu alterar. O antídoto é a mesma disciplina de supply chain que se aplica às dependências da aplicação: procedência verificada, versões pinadas, e o mínimo de código de terceiros rodando com o máximo de privilégio.
Integridade do artefato: provar o que foi construído#
Depois de construído, o artefato viaja do pipeline até produção, e nesse caminho ele pode ser trocado. Assinar o artefato e gerar sua proveniência — um registro verificável de como, de onde e a partir de qual código-fonte ele foi construído — permite que o ambiente de destino confirme que está rodando exatamente o que o pipeline produziu, e não algo injetado no meio do caminho. Frameworks de integridade de supply chain formalizam níveis crescentes dessa garantia, do build reproduzível à proveniência assinada. O princípio prático é: produção só aceita artefato cuja origem ela consegue verificar.
Ambiente de build efêmero e isolado#
Um runner de CI que persiste estado entre builds é um risco: um build malicioso pode plantar algo que afeta o build seguinte — envenenar um cache, deixar um binário, alterar uma ferramenta. Runners efêmeros, que nascem limpos para cada job e são destruídos ao final, eliminam essa contaminação cruzada. Isolamento também importa: o job que roda código não confiável não deveria compartilhar rede ou filesystem com o job que tem os segredos. Quanto mais efêmero e isolado o ambiente de execução, menor a chance de um build comprometer o próximo ou de vazar lateralmente para onde não deveria.
Gates de segurança sem virar teatro#
O pipeline é o lugar natural para automatizar verificações de segurança, e vale usá-lo: análise estática do código em busca de padrões vulneráveis, análise de composição para achar dependências com CVE conhecido, varredura de segredos para impedir que uma chave commitada por engano chegue ao repositório, e verificação de configuração de infraestrutura como código. Bem calibrados, esses gates pegam classes inteiras de problema antes de produção.
O risco é o teatro de segurança: um gate que dispara tantos falsos positivos que o time o configura para nunca bloquear, ou que vira um carimbo que todos aprovam sem ler. Um gate só protege se ele de fato barra o que precisa barrar e se os seus achados são tratados. Calibre para sinal, não para volume — e resista à tentação, sob pressa, de baixar o limiar ou pular a verificação, porque um gate que se desliga na urgência não é um gate.
Nada vai direto para o servidor#
Um princípio operacional fecha o ciclo da segurança de deploy: produção recebe apenas artefato promovido pelo pipeline, identificado por um commit específico, e nunca é editada à mão. O servidor é imutável por edição manual — não se conecta via SSH para "ajustar rapidinho" um arquivo em produção, porque isso cria drift (a produção deixa de corresponder ao git), destrói a rastreabilidade (ninguém sabe o que de fato está rodando) e abre uma porta que contorna todos os gates que o pipeline aplica. Toda mudança, inclusive o hotfix urgente, passa pelo fluxo — do ambiente local ao repositório, do repositório à homologação, da homologação à produção — apenas acelerada quando preciso. A urgência justifica pressa no processo, nunca mão no servidor.
Complementarmente, a proteção do próprio caminho de código sustenta tudo isso: branch protection que exige revisão antes do merge, que impede push direto na branch protegida, e que garante que o código que o pipeline promove foi visto por outro par de olhos. Sem revisão obrigatória, um único desenvolvedor comprometido — ou um único token vazado com permissão de push — coloca código arbitrário na fila de deploy.
O que levar do artigo#
Segurança de CI/CD começa com uma mudança de percepção: o pipeline não é encanamento invisível, é um sistema privilegiado com acesso a produção e execução de código de terceiros, e merece threat modeling como qualquer outro. As defesas concretas se organizam em poucos princípios. Elimine segredos de longa duração em favor de credenciais efêmeras federadas. Trate código de fonte não confiável como hostil, rodando-o sem segredos e sem privilégio. Pine as dependências do build por hash e verifique procedência de tudo que o pipeline executa. Torne os runners efêmeros e isolados. Use gates de segurança que de fato bloqueiam, sem cair no teatro. E mantenha o princípio inegociável de que produção só recebe artefato do git e nunca a mão de ninguém. O pipeline que constrói o seu software com cuidado é o mesmo que um atacante usaria para destruí-lo com um único commit — a diferença está inteiramente no rigor com que você o protege.
