SCA, SAST e DAST: como coloquei segurança de verdade na minha pipeline
Durante muito tempo, CI/CD pra mim era só build, teste e deploy. Nenhuma dessas etapas responde se o código é seguro. Implementei SCA, SAST e DAST numa pipeline real e aprendi na prática o que cada um encontra — e o que nenhum deles substitui.
Durante muito tempo, quando eu pensava em CI/CD, pensava basicamente nisso: Push → Build → Testes → Deploy.
Se compilou e os testes passaram, beleza.
Só que existe um problema: nenhuma dessas etapas necessariamente responde se o código é seguro.
Uma aplicação pode compilar, passar em todos os testes e continuar vulnerável a SQL Injection, usar uma dependência com CVE conhecida ou até ter um secret commitado no repositório.
Foi estudando segurança de aplicações que comecei a implementar uma pipeline de DevSecOps em um projeto vulnerável que criei justamente para isso. E três conceitos apareceram o tempo todo: SCA, SAST e DAST.
Eles parecem fazer a mesma coisa, mas analisam partes completamente diferentes da aplicação.
Qual a diferença entre SCA, SAST e DAST
A forma mais simples que encontrei de pensar neles é:
- SCA analisa o que você instalou.
- SAST analisa o que você escreveu.
- DAST analisa como sua aplicação se comporta quando alguém tenta quebrá-la.
Eles não são concorrentes. Um encontra problemas que o outro muitas vezes nem consegue enxergar.
1 · SCA — Software Composition Analysis
Hoje praticamente nenhuma aplicação é escrita do zero. No meu backend Node.js, por exemplo, tenho dependências como Express, bibliotecas para JWT, acesso ao PostgreSQL e várias outras.
O problema é que uma vulnerabilidade não precisa estar no código que eu escrevi. Ela pode estar em uma biblioteca que instalei. É exatamente aí que entra o SCA.
No projeto, comecei com:
npm auditO npm audit compara a árvore de dependências do projeto com advisories de vulnerabilidades conhecidas. E foi assim que encontrei, por exemplo, uma versão vulnerável do jsonwebtoken.
Ou seja: eu poderia revisar meu código inteiro e não encontrar aquele problema. O problema estava na composição do software. Na pipeline coloquei:
- name: Audit dependencies
run: npm audit --audit-level=highAgora o CI também verifica as dependências. E o --audit-level=high começa a introduzir outro conceito importante: security gate. Posso decidir que vulnerabilidades menores serão reportadas, enquanto High ou Critical impedem aquela execução de passar.
2 · SAST — Static Application Security Testing
Depois das dependências, vem o código que nós escrevemos. Foi aí que adicionei SAST.
A principal característica é que o SAST consegue procurar padrões potencialmente vulneráveis sem precisar atacar uma aplicação rodando. No meu caso usei duas ferramentas: CodeQL e Semgrep.
O CodeQL transforma o código em uma representação consultável e procura fluxos que correspondem a problemas de segurança:
- uses: github/codeql-action/init@v4
with:
languages: javascript-typescript
queries: security-extended
- uses: github/codeql-action/analyze@v4Também adicionei Semgrep:
- name: Install Semgrep
run: pip install semgrep
- name: Run Semgrep
run: semgrep scan --config=autoDurante os testes, o CodeQL conseguiu apontar problemas que passariam facilmente despercebidos em uma revisão rápida, como log injection.
A ideia aqui é diferente do SCA: SCA vai de package.json/package-lock.json até uma dependência vulnerável. SAST vai do source code até um fluxo/padrão vulnerável.
Mas ainda existe uma limitação importante: estamos analisando o código, não o comportamento real da aplicação enquanto ela está rodando.
3 · DAST — Dynamic Application Security Testing
No DAST eu parei de simplesmente analisar arquivos. Eu subi a aplicação e deixei uma ferramenta tentar atacá-la — usei o OWASP ZAP.
Minha pipeline passou a fazer aproximadamente isso: sobe PostgreSQL → executa migrations → sobe backend → cria usuário de teste → faz login → captura JWT → executa OWASP ZAP.
O ZAP recebe minha especificação OpenAPI e começa a testar os endpoints. E aqui apareceu uma diferença enorme em relação ao SAST — ele pode fazer requests como:
GET /notes/search?q='
Authorization: Bearer <token>e observar o que acontece. Em um dos scans, esse request resultou em 500 Internal Server Error, e o ZAP levantou um alerta de possível SQL Injection, risco High, CWE-89, no parâmetro q.
Isso não significa automaticamente que toda descoberta do scanner é uma vulnerabilidade confirmada. Nesse caso, a confiança reportada era baixa — o próximo passo é analisar a implementação, reproduzir o comportamento e determinar se realmente existe SQL Injection.
Scanner encontra indícios. Desenvolvedor ainda precisa entender o finding.
Um problema que encontrei implementando DAST
No começo, o scan parecia funcionar. Só que quase todas as requisições estavam retornando 401 Unauthorized. O ZAP estava "testando" minha API, mas não conseguia chegar nos endpoints que eu queria testar.
Foi necessário criar um usuário específico para o DAST durante a execução da pipeline: POST /auth/register → POST /auth/login → JWT → Authorization: Bearer <JWT> → OWASP ZAP.
Depois apareceu outro problema: rate limiting. Eu tinha adicionado rate limits justamente como proteção de segurança, mas um scanner envia uma quantidade enorme de requests — resultado, 429 Too Many Requests e o scanner perdendo cobertura de novo.
Um scan verde com autenticação quebrada pode ser muito pior que um scan vermelho. Ele passa uma falsa sensação de segurança.
Secret scanning também entrou na pipeline
Além dos três principais, coloquei também o Gitleaks. A cada commit, ele verifica se há API key, token, password ou credential exposta:
- uses: gitleaks/gitleaks-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Um secret encontrado pode significar que aquela credencial já precisa ser considerada comprometida.
Os findings também precisam chegar em algum lugar
No OWASP ZAP, por exemplo, dá pra permitir que a própria Action registre os resultados em uma GitHub Issue com allow_issue_writing: true. Assim o finding não existe só durante aquela execução da pipeline.
Com o CodeQL a integração é ainda mais direta: os resultados vão direto pro code scanning do GitHub, na área Security and quality. Outras ferramentas integram via formato SARIF, que o GitHub interpreta e transforma em code scanning alerts.
Separamos então duas responsabilidades: encontrar a vulnerabilidade (scanner) e, a partir do finding, reportar (issue / security and quality) ou bloquear (pipeline falha) — porque reportar um problema e impedir que ele chegue em produção são coisas diferentes.
Encontrar não é suficiente — security gates
Para transformar isso em política de segurança de verdade, defini gates por severidade:
Informational → reportar
Low → reportar
Medium → política do projeto
High → bloquear
Critical → bloquearSe uma vulnerabilidade viola a política, a Action falha e o PR fica bloqueado. Mas isso só funciona se o repositório também exigir aquele status check nas regras de proteção da branch — pipeline falhar não bloqueia merge sozinho.
Nenhuma dessas ferramentas substitui as outras
Essa foi a maior conclusão que tirei implementando tudo:
- Instalo uma biblioteca vulnerável → SCA encontra.
- Escrevo uma query SQL concatenando input do usuário → SAST encontra.
- Um payload provoca comportamento inesperado só com banco, auth, middleware e app juntos → DAST encontra.
- Uma falha de autorização, onde o usuário A acessa
GET /notes/10e a nota pertence ao usuário B → talvez nenhum scanner encontre de forma confiável.
É aí que entram revisão humana, testes específicos e Threat Modeling.
Segurança não é "instalei o ZAP, agora minha aplicação é segura." É dependências → SCA, código → SAST, secrets → Secret Scanning, app rodando → DAST, arquitetura → Threat Modeling — tudo alimentando revisão humana.
O que mudou na minha forma de olhar CI/CD
Antes eu pensava: "a pipeline verifica se meu código funciona." Hoje prefiro pensar: "a pipeline verifica algumas das condições necessárias para eu confiar que esse código pode seguir adiante."
Build passar não significa código correto. Teste passar não significa código seguro. SAST passar não significa aplicação segura. DAST passar também não significa aplicação segura.
Mas quando combinamos várias dessas técnicas, aumentamos bastante nossa capacidade de encontrar problemas antes que eles cheguem em produção. E essa foi a parte mais interessante de estudar DevSecOps na prática: segurança deixa de ser uma etapa feita no final do projeto e passa a fazer parte do ciclo normal de desenvolvimento.