Voltar
9/2/20268 min de leitura

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.

Primeiro: 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.

Em uma pipeline, podemos imaginar:

Aplicação
SCA
Dependências
SAST
Código
DAST
App rodando

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:

Meu código
express
jsonwebtoken
drizzle
dezenas/centenas de dependências transitivas

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:

bash
npm audit

O 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:

yaml
- name: Audit dependencies
  run: npm audit --audit-level=high

Agora o CI também verifica as dependências.

E o --audit-level=high começa a introduzir outro conceito importante: security gate.

Eu posso decidir que vulnerabilidades menores serão reportadas, enquanto vulnerabilidades High ou Critical impedem aquela execução da pipeline 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
  • Semgrep

O CodeQL é particularmente interessante porque transforma o código em uma representação consultável e procura fluxos que correspondem a problemas de segurança.

Na pipeline:

yaml
- uses: github/codeql-action/init@v4
  with:
    languages: javascript-typescript
    queries: security-extended
 
- uses: github/codeql-action/analyze@v4

Também adicionei Semgrep:

yaml
- name: Install Semgrep
  run: pip install semgrep
 
- name: Run Semgrep
  run: semgrep scan --config=auto

Durante 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
package.json
package-lock.json
dependência vulnerável
SAST
source code
fluxo/padrão vulnerável

Mas ainda existe uma limitação importante.

Estamos analisando o código.

Não estamos necessariamente observando o comportamento real da aplicação enquanto ela está rodando.

É aí que entra a parte que achei mais interessante.

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:

GitHub Actions
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:

http
GET /notes/search?q='
Authorization: Bearer <token>

e observar o que acontece.

Em um dos scans, esse request resultou em:

500 Internal Server Error

O ZAP então levantou um alerta de possível:

SQL Injection
Risk:High
CWE-89
Parameter:q

Isso não significa automaticamente que toda descoberta do scanner é uma vulnerabilidade confirmada.

Nesse caso, por exemplo, a confiança reportada era baixa. O próximo passo é analisar a implementação, reproduzir o comportamento e determinar se realmente existe SQL Injection.

Essa parte é fundamental:

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 justamente 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:

ZAP
request ×4
429 Too Many Requests

O scanner novamente perdia cobertura.

Esse foi um aprendizado importante: não basta colocar uma ferramenta de segurança no CI e assumir que ela está testando sua aplicação corretamente.

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.

O objetivo é diferente:

Commit → Gitleaks
API key?
Token?
Password?
Credential?

Um secret encontrado pode significar que aquela credencial já precisa ser considerada comprometida.

No GitHub Actions ficou algo semelhante a:

yaml
- uses: gitleaks/gitleaks-action@v3
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Isso complementa os outros scanners.

Como ficou a pipeline

Depois de juntar tudo:

Pull RequestGITHUB ACTIONSBuild✓SCAnpm auditSASTCodeQL+SemgrepSecret ScanGitleaksDASTOWASP ZAP

Mas ainda faltava uma coisa.

O que acontece quando alguma ferramenta encontra uma vulnerabilidade?

Os findings também precisam chegar em algum lugar

Outra coisa que implementei foi fazer com que os resultados dos scanners não ficassem perdidos no log do GitHub Actions.

No OWASP ZAP, por exemplo, podemos permitir que a própria Action registre os resultados em uma GitHub Issue:

yaml
allow_issue_writing: true

Assim, o finding não existe apenas durante aquela execução da pipeline. Ele vira algo que pode ser acompanhado e tratado dentro do próprio repositório.

Com o CodeQL, a integração é ainda mais direta.

Os resultados da análise são enviados para o code scanning do GitHub e aparecem na área Security and quality, onde é possível visualizar os alerts, severidade, arquivo afetado e acompanhar a correção.

Outras ferramentas de análise estática também podem integrar seus resultados ao GitHub através do formato SARIF:

Scanner
SARIF
GitHub Code Scanning
Security and quality

O GitHub interpreta esse arquivo e transforma os resultados em code scanning alerts dentro do repositório.

Então começamos a separar duas responsabilidades importantes:

Encontrar vulnerabilidade → Scanner
Reportar
Issue / Security and quality
Bloquear
Pipeline falha

Porque reportar um problema e impedir que ele chegue em produção são coisas diferentes.

É aí que entram os security gates.

Encontrar uma vulnerabilidade não é suficiente

Um scanner pode simplesmente gerar um relatório e deixar a pipeline continuar.

Para transformar isso em uma política de segurança de verdade, precisamos definir security gates.

Por exemplo:

Informationalreportar
Lowreportar
Mediumpolítica do projeto
Highbloquear
Criticalbloquear

Se uma vulnerabilidade violar a política:

Scanner
Finding
Security Policy
exit code != 0
GitHub Action ❌
PR bloqueado

E existe uma distinção importante aqui.

Pipeline falhar não significa automaticamente que ninguém consegue fazer merge.

O repositório também precisa exigir aquele status check através das regras de proteção da branch.

Então o fluxo completo passa a ser:

Pull RequestSecurity PipelinePASSFAILMerge liberadoMerge bloqueado

É aqui que segurança deixa de ser apenas um relatório que ninguém lê e começa a fazer parte do processo de desenvolvimento.

Nenhuma dessas ferramentas substitui as outras

Essa foi provavelmente a maior conclusão que tirei implementando tudo.

Imagine que eu instale uma biblioteca vulnerável:

SCA pode encontrar.

Agora imagine que eu escreva uma query SQL concatenando input do usuário:

SAST pode encontrar.

Agora imagine que determinado payload provoque um comportamento inesperado somente com banco, autenticação, middleware e aplicação funcionando juntos:

DAST pode encontrar.

E imagine que eu tenha uma falha de autorização onde:

Usuário A
GET /notes/10
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."

É muito mais próximo de:

DependênciasSCA
CódigoSAST
SecretsSecret Scanning
App rodandoDAST
ArquiteturaThreat Modeling
revisão humana

Cada camada cobre uma parte diferente do problema.

O que mudou na minha forma de olhar CI/CD

Antes eu pensava:

"A pipeline verifica se meu código funciona."

Hoje eu 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, para mim, 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.