O que é SAST e Por Que Implementar em CI/CD
SAST (Static Application Security Testing) é uma metodologia de análise de código-fonte sem executá-lo, buscando vulnerabilidades, padrões inseguros e violações de políticas de segurança antes do código chegar à produção. A implementação em pipelines CI garante que cada commit seja analisado automaticamente, bloqueando merges problemáticos e reduzindo custos de correção posterior.
A diferença fundamental entre SAST e outras abordagens é que ela trabalha com o código-fonte direto, não com binários ou aplicações rodando. Isso permite detecção precoce de problemas como SQL injection, XSS, credenciais expostas e uso de bibliotecas desatualizadas. Integrar SAST no CI/CD transforma segurança em parte do fluxo de desenvolvimento, não um overhead pós-entrega.
Semgrep: Pattern Matching Agnóstico e Customizável
Conceito e Arquitetura
Semgrep é um motor de busca de padrões que funciona sem dependências complexas e oferece suporte a múltiplas linguagens (Python, JavaScript, Java, Go, C, C++, etc.). Diferente de outras ferramentas, Semgrep usa uma linguagem de padrões simples baseada em YAML, permitindo criar regras customizadas em minutos sem conhecimento profundo em parsing ou AST.
O grande diferencial é a capacidade de escrever regras de negócio específicas da sua organização. Enquanto SonarQube e CodeQL focam em vulnerabilidades genéricas, Semgrep permite detectar padrões anti-pattern próprios, como uso de APIs internas proibidas ou patterns de logging inseguro que sua empresa quer evitar.
Instalação e Configuração Básica
A instalação é trivial comparada a outras ferramentas. No Linux/Mac, basta executar:
brew install semgrep
# ou via pip
pip install semgrep
Para verificar a instalação:
semgrep --version
Usando Semgrep em um Pipeline CI/CD
Vamos implementar Semgrep em um pipeline GitHub Actions. Crie o arquivo .github/workflows/semgrep.yml:
name: Semgrep SAST
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
p/python
generateSarif: true
- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: semgrep.sarif
Este pipeline executa regras pré-definidas de segurança. Para entender o que está acontecendo: p/security-audit e p/owasp-top-ten são packs de regras mantidas pela Semgrep Registry; generateSarif converte o resultado para formato SARIF, padrão aberto que GitHub Code Scanning entende nativamente.
Criando Regras Customizadas
Agora vem o poder real: criar suas próprias regras. Suponha que sua empresa proíbe uso de eval() em Python. Crie rules/custom-rules.yaml:
rules:
- id: no-eval-allowed
pattern: eval(...)
message: "eval() é proibido por política de segurança. Use json.loads() ou ast.literal_eval()"
languages: [python]
severity: ERROR
- id: hardcoded-database-password
patterns:
- pattern-either:
- pattern: "password = '...'"
- pattern: "password = \"...\""
message: "Senha hardcoded detectada. Use variáveis de ambiente"
languages: [python]
severity: CRITICAL
Execute a regra customizada:
semgrep --config=rules/custom-rules.yaml .
Exemplo Prático: Detectando SQL Injection
Considere este código Python vulnerável (deliberadamente):
def get_user(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
return database.execute(query)
Crie a regra rules/sql-injection.yaml:
rules:
- id: sql-injection-f-string
pattern-either:
- pattern: $DB.execute(f"...{...}...")
- pattern: $DB.execute("..." + str(...))
- pattern: $DB.execute("..." % (...))
message: |
SQL Injection detectada. Use prepared statements com placeholders
Correto: cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
languages: [python]
severity: CRITICAL
Semgrep detectaria a vulnerabilidade imediatamente no pipeline.
SonarQube: Análise Profunda com Métricas de Qualidade
Arquitetura e Diferenças Fundamentais
SonarQube é uma plataforma enterprise de análise estática que combina SAST com métricas de qualidade (duplicação de código, cobertura de testes, complexidade ciclomática). Enquanto Semgrep é rápido e customizável, SonarQube oferece histórico, trends e integração com gestão de projetos.
SonarQube executa análise profunda em AST (Abstract Syntax Tree), permitindo detectar padrões complexos que Semgrep não alcança facilmente. É ideal para organizações grandes que precisam de compliance, relatórios executivos e controle centralizado de qualidade.
Instalação com Docker
A forma mais prática é via Docker:
docker run -d \
--name sonarqube \
-p 9000:9000 \
-e SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonarqube \
-e SONAR_JDBC_USERNAME=sonar \
-e SONAR_JDBC_PASSWORD=sonar_password \
sonarqube:latest
Acesse http://localhost:9000 (usuário padrão: admin/admin).
Integração em Pipeline GitLab CI
Crie .gitlab-ci.yml:
stages:
- scan
- build
sonarqube_scan:
stage: scan
image: sonarsource/sonar-scanner-cli:latest
script:
- sonar-scanner \
-Dsonar.projectKey=my-python-app \
-Dsonar.sources=src \
-Dsonar.host.url=http://sonarqube-server:9000 \
-Dsonar.login=$SONAR_TOKEN \
-Dsonar.python.coverage.reportPath=coverage.xml
artifacts:
reports:
sast: sonarqube-report.json
allow_failure: false
O -Dsonar.python.coverage.reportPath integra cobertura de testes; sem isso, SonarQube não consegue calcular a métrica de cobertura.
Exemplo: Detectando Code Smells e Vulnerabilidades
Considere esta classe Java com problemas:
public class UserService {
private String dbPassword = "admin123"; // Hardcoded password
public User getUser(int id) {
String query = "SELECT * FROM users WHERE id = " + id; // SQL Injection
return database.execute(query);
}
public void logUser(User user) {
System.out.println("User: " + user); // Println in production code
}
public String processData(String input) {
if (input != null) {
if (input.length() > 0) {
if (input.equals("admin")) { // Deep nesting
return "admin";
}
}
}
return "";
}
}
SonarQube detectará:
- CRITICAL: Senha hardcoded
- CRITICAL: SQL Injection
- MAJOR: System.out.println em código de produção
- MAJOR: Complexidade ciclomática excessiva
- MINOR: Return statement redundante
Configuração de Quality Gates
Quality Gates definem quando um projeto "passa" ou "falha". Configure em SonarQube:
Condições:
- Segurança: 0 vulnerabilidades críticas
- Confiabilidade: Máximo 5 bugs
- Manutenibilidade: Nota >= C
- Cobertura: >= 80%
- Duplicação: <= 3%
No pipeline, a análise falha se alguma condição não for atendida:
script:
- sonar-scanner [...]
- curl -s "http://sonarqube:9000/api/qualitygates/project_status?projectKey=my-app" \
| jq '.projectStatus.status' | grep -q "OK" || exit 1
CodeQL: Análise Semântica com Banco de Dados de Vulnerabilidades
Conceito: Consultas SQL em Código-Fonte
CodeQL, desenvolvido pela GitHub, transforma código-fonte em um banco de dados consultável via uma linguagem similar a SQL. Isso permite detectar vulnerabilidades através de consultas semânticas, não apenas pattern matching. Se Semgrep é "busca por regex evoluída" e SonarQube é "análise com métricas", CodeQL é "análise lógica programática".
Um exemplo: para detectar SQL Injection, você não apenas procura por f"SELECT...", mas rastreia o fluxo de dados desde a entrada do usuário até a execução SQL, detectando quando dados não-sanitizados chegam lá.
Instalação e Setup Inicial
Instale a CLI de CodeQL:
# macOS
brew install codeql
# Linux - download manual
wget https://github.com/github/codeql-cli-bundle/releases/download/v2.14.0/codeql-linux64.zip
unzip codeql-linux64.zip
export PATH=$PWD/codeql:$PATH
Verifique:
codeql --version
GitHub Actions com CodeQL
O jeito mais prático é usar GitHub Actions (CodeQL vem integrado ao GitHub):
name: CodeQL Analysis
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * 1' # Segunda-feira às 2am
jobs:
analyze:
runs-on: ubuntu-latest
strategy:
matrix:
language: [python, javascript]
steps:
- uses: actions/checkout@v3
- uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: security-and-quality
- uses: github/codeql-action/autobuild@v2
- uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"
O autobuild detecta automaticamente como compilar o projeto (para linguagens compiladas como C/C++/Java). Para linguagens interpretadas, esse passo é desnecessário.
Criando Consultas Customizadas CodeQL
CodeQL permite criar queries customizadas em sua linguagem própria (QL). Crie queries/custom-command-injection.ql:
import javascript
from CallExpression call, Identifier func
where
func.getName() = "exec" and
call.getCallee() = func and
call.getArgument(0).getType() = StringType
select
call,
"Possível command injection: 'exec' com string. Use array com argumentos separados"
Execute contra sua codebase:
codeql database create /tmp/codeql-db --language=javascript --source-root=.
codeql query run queries/custom-command-injection.ql --database=/tmp/codeql-db
Exemplo Prático: Detectando XSS em Node.js/Express
Considere este código vulnerável:
const express = require('express');
const app = express();
app.get('/profile/:name', (req, res) => {
const name = req.params.name;
res.send(`<h1>Welcome ${name}</h1>`); // XSS vulnerability
});
app.post('/comment', (req, res) => {
const comment = req.body.text;
database.save(`<p>${comment}</p>`); // XSS stored
res.json({ success: true });
});
CodeQL detectaria isso com a query padrão js/xss. Para criar uma regra customizada que detecta especificamente template strings inseguras:
import javascript
from TemplateLiteral template, DataFlowNode source
where
source.asExpr() = template.getElement(_) and
source.getVariable().getName().matches("req\\..*")
select
template,
"Template string com user input não sanitizado: " + source.getVariable().getName()
CodeQL rastreia que req.params.name ou req.body.text (entrada do usuário) flui diretamente para HTML, sem sanitização.
Diferença Chave: Data Flow Analysis
CodeQL excele em análise de fluxo de dados. Ela entende isso:
# CodeQL rastreia o fluxo:
user_input = request.args.get('id') # Source: entrada do usuário
processed = f"SELECT * FROM users WHERE id = {user_input}" # Sink: SQL execution
database.execute(processed)
# Mesmo que o código esteja em arquivos diferentes,
# CodeQL descobre que user_input chegou em database.execute()
Semgrep encontraria o padrão de f-string, mas CodeQL prova que existe um caminho de dados inseguro.
Integrando as Três Ferramentas em um Pipeline Real
Estratégia: Layers de Análise
Cada ferramenta tem seu propósito:
- Semgrep (rápido, primária): Executa em ~30s, bloqueia problemas óbvios
- SonarQube (completo, gateway): Análise profunda + métricas, bloqueia se Quality Gate falhar
- CodeQL (semântico, backup): Detecta vulnerabilidades complexas de fluxo
O pipeline completo em GitHub Actions:
name: Complete Security Pipeline
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Layer 1: Semgrep (rápido)
- name: Semgrep Fast Scan
uses: returntocorp/semgrep-action@v1
with:
config: p/owasp-top-ten
generateSarif: true
continue-on-error: true
# Layer 2: CodeQL (profundo)
- uses: github/codeql-action/init@v2
with:
languages: [python, javascript]
- uses: github/codeql-action/autobuild@v2
- uses: github/codeql-action/analyze@v2
# Layer 3: SonarQube (métricas)
- name: SonarQube Scan
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
docker run \
--rm \
-e SONAR_HOST_URL=https://sonarqube.company.com \
-e SONAR_LOGIN=$SONAR_TOKEN \
-v $(pwd):/src \
sonarsource/sonar-scanner-cli \
-Dsonar.sources=/src \
-Dsonar.projectKey=my-project
# Upload all results
- uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: semgrep.sarif
category: semgrep
Configuração de Merge Checks
Configure GitHub para exigir que todos os scans passem:
Settings > Branches > Branch protection rule:
- Require CodeQL checks to pass ✓
- Require Semgrep checks to pass ✓
- Require status checks to pass before merging ✓
Agora, nenhum pull request pode ser merged sem passar em TODAS as ferramentas.
Exemplo: Projeto Python Real
Suponha um projeto Flask com múltiplos problemas:
# app.py
from flask import Flask, request
import sqlite3
app = Flask(__name__)
DB_PASSWORD = "root123" # Problema 1: senha hardcoded
@app.route('/search')
def search():
query = request.args.get('q')
# Problema 2: SQL injection
results = sqlite3.execute(f"SELECT * FROM items WHERE name = '{query}'")
return results
@app.route('/eval-code', methods=['POST'])
def eval_endpoint():
code = request.json.get('code')
# Problema 3: eval() remoto
result = eval(code)
return str(result)
Semgrep detecta em ~30s:
Arquivo: app.py
- Hardcoded password
- eval() usage (regra customizada)
CodeQL detecta em ~2min:
Arquivo: app.py
- SQL injection (rastreia: request.args.get → f-string → sqlite3.execute)
- Remote code execution via eval()
SonarQube detecta em ~3min:
- Hardcoded credentials (CRITICAL)
- Code injection (CRITICAL)
- Missing input validation (MAJOR)
- Complexity issues
- Sem testes unitários (coverage 0%)
Quality Gate falha porque segurança crítica não passou. PR é bloqueado.
Conclusão
Dominar SAST em pipelines CI exige entender que cada ferramenta resolve um problema diferente. Semgrep é velocidade e customização; SonarQube é visibilidade e compliance; CodeQL é precisão semântica. A combinação das três cria uma defesa em camadas que pega desde problemas óbvios até vulnerabilidades sutis de fluxo de dados.
O principal aprendizado prático é que segurança integrada no CI é mais eficaz e barata que segurança testada depois. Um bug de segurança detectado no pull request custa horas para o developer; o mesmo bug encontrado em produção custa horas para o SRE, danos à reputação e potencial brecha de dados.
Por fim, lembre-se que ferramentas não substituem design seguro. SAST é um detector de problemas conhecidos; um desenvolvedor com mentalidade security-first ainda é o melhor preventivo.