Conhecimento / Arquivo Técnico / ARC-003
Quatro falhas até um Semgrep verde: consertando o job de SAST no meu CI
O check do Semgrep falhou em todas as execuções. O que cada falha significava, como corrigi e o que eu leria primeiro da próxima vez.
Adicionei o Semgrep ao CI deste site para rodar análise estática (SAST) a cada push na main. Ele falhou em todas as execuções. Esta página conta o que foi cada falha, na ordem em que apareceram, e o que finalmente deixou o check verde.
Fiz isso com a ajuda de um assistente de código com IA. O repositório é privado e o assistente não consegue ler os logs do CI, então eu enviava cada erro em captura de tela, e toda correção abaixo foi antes reproduzida na minha máquina.
Onde começou
O workflow roda semgrep scan com conjuntos de regras do OWASP Top Ten, do CWE Top 25, de auditoria de segurança, de Next.js, React, TypeScript e Dockerfiles. Ele usa --error, então o job falha quando o Semgrep encontra qualquer coisa, e a ideia era entregar o resultado à aba Security do GitHub como um arquivo SARIF.
Falha 1: o próprio scan achou 8 itens
Eu não conseguia ler o log, então instalei o Semgrep num ambiente virtual descartável e rodei o mesmo comando. Ele achou 8 problemas, todos de severidade WARNING:
- 7 usos de tag mutável em GitHub Actions, nos dois workflows (
actions/checkout@v4,docker/login-action@v3e semelhantes). Uma tag pode ser movida pelo dono da ação, então o código que roda no meu pipeline poderia mudar sem nenhuma mudança no meu repositório. A regra do Semgrep égithub-actions-mutable-action-tag. - 1 chamada dinâmica de
urllibno script que gera os SVGs da marca (dynamic-urllib-use-detected).
A correção das ações: fixar cada uma pelo SHA completo do commit para o qual a tag apontava, e manter a versão em um comentário. Li os SHAs nos repositórios oficiais com git ls-remote, então o código que roda é o mesmo de antes, só que agora não muda em silêncio.
A correção do script levou três tentativas. Primeiro troquei a URL por um dicionário de URLs fixas. O Semgrep ainda sinalizou, porque um valor buscado por uma variável conta como dinâmico. Depois escrevi as URLs como literais, e foi sinalizado de novo: a regra reclama de qualquer argumento que não seja literal, e o caminho de destino era um deles. A correção honesta foi tirar o download do script. Agora ele espera os dois arquivos de fonte numa pasta, e o README mostra os comandos curl para baixá-los. Um script que desenha um logo não precisa de código de rede.
Falha 2: Invalid SARIF
Sem os achados, o job ainda falhava, agora com Invalid SARIF. JSON syntax error ... Unexpected end of JSON input. O scan tinha passado. O passo que envia os resultados estava recebendo um arquivo quebrado.
Reproduzi localmente. O workflow combinava --sarif --output semgrep.sarif --text, e essa combinação deixa o arquivo vazio. A correção foi um único flag, --sarif-output=semgrep.sarif, que escreve um SARIF válido e mantém o relatório legível no log. Aproveitei para subir a ação de upload para a v4, porque o aviso do run dizia que a v3 será descontinuada em dezembro de 2026.
Falha 3: Resource not accessible by integration
Em seguida o passo de upload falhou com essa mensagem, apontando para a API de "obter um workflow run". Em repositório privado a ação de upload lê o run do workflow, e isso exige a permissão actions: read, que meu workflow não declarava. A documentação do GitHub a lista para repositórios privados. Adicionei.
Falha 4: Code scanning is not enabled for this repository
Esta era a parede de verdade. Enviar SARIF para a aba Security é code scanning, e a página de solução de problemas do GitHub para esse erro diz que repositórios privados precisam do GitHub Code Security ativado. Eu não o tinha ativado, então nenhuma mudança no workflow faria esse upload funcionar.
As opções eram ativar o recurso, tornar o repositório público ou parar de enviar para a aba Security. Não verifiquei quanto custa ativá-lo, e não quero tornar o repositório público. Escolhi a terceira. O workflow agora salva o arquivo SARIF como artefato do run com actions/upload-artifact, fixado por SHA, mantido por 30 dias, e o passo falha se o arquivo não existir. Como nada mais é enviado ao code scanning, também removi as permissões actions: read e security-events: write que eu tinha acabado de adicionar. O job fica só com contents: read. O artigo do pipeline neste site dizia que os achados iam para a aba Security, então o corrigi nos dois idiomas.
Mais um pin
O job roda no contêiner semgrep/semgrep:latest, que é o mesmo tipo de referência mutável que eu tinha acabado de tirar das ações. Fixei por digest. Antes de fazer o commit, baixei essa imagem exata com o Podman e rodei dentro dela o comando do workflow: Semgrep 1.182.1, nenhum achado, código de saída 0.
Um detalhe que notei e não investiguei: dentro dessa imagem o Semgrep rodou 323 regras, e a cópia que eu tinha instalado com pip rodou 1.517. O CI usa a imagem, então o check do site cobre o conjunto menor.
Resultado
Os dois workflows estão verdes: o job do Semgrep passa e a imagem continua sendo construída e publicada.
O que eu faria diferente
- Ler primeiro os requisitos de repositório privado. As falhas 3 e 4 estão ambas documentadas. Corrigi uma de cada vez, e teria economizado duas rodadas lendo a página do GitHub sobre upload de SARIF antes do primeiro upload.
- Reproduzir antes de chutar. Rodar o comando exato localmente, e depois dentro da imagem exata, transformou cada marca vermelha vaga numa causa específica.
- Não enganar uma regra para ela ficar quieta. O achado do
urlliblembrou que a correção mais limpa era ter menos código, e não uma forma mais esperta de escrevê-lo.
O que ele ainda não faz
- O check não bloqueia nada. Ele roda depois do push, então um commit com achado ainda chega à
maine só deixa o run vermelho. Torná-lo uma verificação obrigatória continua planejado. - As versões fixadas não se atualizam sozinhas. As ações e a imagem do Semgrep ficam congeladas até eu trocá-las à mão, e não configurei o Dependabot.
- O arquivo SARIF é só um artefato. Preciso abrir o run e baixá-lo para ler os achados num visualizador.
- Um resultado limpo com 323 regras não é prova de que o código é seguro. É uma camada, e o artigo do pipeline lista o que ainda falta: varredura da imagem e das dependências, uma lista de materiais de software e testes dinâmicos.