Tutorial: SAST com Semgrep
Objetivo
Ao final você terá rodado o SAST do AurumCode (quality_gates.sast, Semgrep)
em cinco situações: com uma regra local e sem rede, com um pacote do registry
(o que acontece sem rede), com nosemgrep e .semgrepignore sem e sob
política central, com a origem sast aparecendo no gate e na auditoria, e com
o Semgrep falhando. Depois, o mesmo mecanismo com um linter real: a engine
govet (categoria lint), declarada em quality_gates.scanners, achando um
defeito só na linha que o PR adicionou e sem receber os segredos do processo,
e falhando quando o go não existe. O ponto central: um scanner que não
produziu relatório confiável nunca vale como "zero achados"; vira
inconclusivo.
Cada comando e cada saída vêm de uma execução real, registrada em
demo/tutoriais/sast/out/ e conferida por run.sh --check. Os blocos de
configuração são os arquivos de demo/tutoriais/sast/, byte a byte. O
guia corporativo (demo/gate-corporativo) mostra o SAST dentro do fluxo
completo; aqui ele aparece isolado.
Pré-requisitos
git,dockerebash. Nada mais roda no seu host.- A imagem do produto, construída do
Dockerfileda raiz; ela já traz o Semgrep na versão fixada em.board/bootstrap/locks/scanners.yml(o tutorial usa esse Semgrep, não baixa outra imagem):
docker build -t aurumcode:local /caminho/para/AurumCode
- Rede não é necessária: a demonstração roda com
--network nonee com o provedor de modelo falso (AURUMCODE_LLM_FIXTURE, arquivofixture-llm.json, sem achados). O SAST não depende do modelo: o achado do Semgrep nasce de código, depois da chamada ao modelo.
bash demo/tutoriais/sast/run.sh all # sete casos; grava out/
bash demo/tutoriais/sast/run.sh --check # compara out/ com expected/, sem docker
Cada caso cria um repositório descartável em demo/tutoriais/sast/.estado/
(ignorado pelo git), com main e uma branch feature.
Caso 1: regra local, offline
quality_gates.sast liga o Semgrep sobre a árvore inteira do repositório.
rule_packs aceita, além de nomes do registry (p/...), caminhos de arquivo
de regra: o valor vai literalmente para semgrep --config, relativo à raiz do
repositório. Sem rede é a forma recomendada para runners isolados.
quality_gates:
sast:
engine: semgrep
enabled: true
fail_on_severity: ERROR
rule_packs: ["regras/sast.yml"]
review:
language: pt-BR
rules:
- id: demo-sem-eval
languages: [javascript]
severity: ERROR
message: "eval() executa texto como codigo; use uma tabela de operacoes"
pattern: eval(...)
A mudança da branch feature acrescenta uma função que chama eval:
aurumcode review --base main
$ aurumcode review --base main
calc.js:5: [error] eval() executa texto como codigo; use uma tabela de operacoes (rule semgrep:regras.demo-sem-eval)
exit_code=3
O que observar: o rule_id é semgrep: mais o check_id do Semgrep, que para
arquivo local leva o caminho do arquivo (regras.demo-sem-eval); arquivo e
linha vêm do relatório do Semgrep. Não há gate: declarado: o SAST decide
sozinho, e o achado em ERROR (o fail_on_severity padrão) dá saída 3. A
linha de gate também traz origem repo, porque a seção veio do repositório.
Caso 2: pacote do registry, o que acontece sem rede
Com rule_packs: [p/security-audit] o Semgrep precisa baixar o pacote. O
caminho de sucesso (pacote baixado, achados reais do registry) exige rede e não
foi executado aqui. O que o tutorial executa é o que importa para a
confiança: sem rede, o resultado não é "limpo".
quality_gates:
sast:
engine: semgrep
enabled: true
fail_on_severity: ERROR
rule_packs: [p/security-audit]
gate:
fail_on: [error]
inconclusive: block
review:
language: pt-BR
aurumcode review --base main
$ aurumcode review --base main
aurumcode review: SAST (Semgrep) inconclusivo: a varredura não produziu resultado confiável (o Semgrep falhou ao rodar [sast_execution_error]); nenhum achado determinístico do Semgrep foi publicado nesta execução.
aurumcode review: policy gate: SAST (semgrep, origem sast, secao repo) inconclusivo (o Semgrep falhou ao rodar [sast_execution_error])
exit_code=1
O que observar: o comando demora cerca de dois minutos (o limite de uma
varredura é 120 s) e termina com sast_execution_error. Com
gate.inconclusive: block isso reprova (saída 1). O parecer não traz
achado do SAST: leia a linha SAST ... inconclusivo antes de confiar nele.
(RESULTADO do script: o exit 1 é o esperado e prova que a falha de download
não foi lida como varredura limpa; o log não distingue timeout de recusa de
rede, então o tutorial não afirma qual das duas aconteceu.)
Caso 3: nosemgrep e .semgrepignore, sem e sob política
O autor da mudança silencia um eval com // nosemgrep: demo-sem-eval e
esconde outro arquivo, legado.js, num .semgrepignore:
legado.js
Sem política central, esses mecanismos funcionam como o Semgrep os define. Sob
--politica, o AurumCode roda o Semgrep com --disable-nosem e
--x-ignore-semgrepignore-files (a segunda é uma flag interna do Semgrep, por
isso a versão fixada importa): quem escreve o código não consegue silenciar o
que a política manda varrer. A política deste tutorial:
gate:
fail_on: [error]
inconclusive: block
sources: [sast]
quality_gates:
sast:
engine: semgrep
enabled: true
fail_on_severity: ERROR
rule_packs: ["/policy/regras/sast.yml"]
review:
language: pt-BR
Observe que na política o pacote de regras é um caminho absoluto
(/policy/regras/sast.yml): o diretório da política não faz parte da árvore
varrida, e o caminho é resolvido a partir da raiz do repositório. O mesmo
repositório, sem e com política:
aurumcode review --base main
aurumcode review --base main --politica /policy
--- sem politica (config do proprio repositorio): nosemgrep e .semgrepignore valem
exit_code=0
--- sob politica central: o mesmo codigo, o autor nao consegue silenciar
calc.js:5: [error] eval() executa texto como codigo; use uma tabela de operacoes (rule semgrep:policy.regras.demo-sem-eval)
legado.js:2: [error] eval() executa texto como codigo; use uma tabela de operacoes (rule semgrep:policy.regras.demo-sem-eval)
exit_code=3
O que observar: sem política, exit 0 e nenhum achado; sob política, os dois
eval aparecem (calc.js:5 apesar do nosemgrep, legado.js:2 apesar do
.semgrepignore) e o gate reprova. O check_id agora é policy.regras....
porque o arquivo de regras mudou de lugar. (Conclusão do script: a diferença
de exit entre as duas execuções, 0 e 3, sobre o mesmo código.)
Caso 4: a origem sast no gate e na auditoria
Quando a política declara gate, todo achado que passou pelo gate de evidência
conta pela severidade, qualquer que seja a origem (skills, analysis,
sast). A política acima usa sources: [sast]. O mesmo comando, agora com
--auditoria:
aurumcode review --base main --politica /policy --auditoria auditoria.json
$ aurumcode review --base main --politica /policy --auditoria auditoria.json
(severidade error, limiar error, origem sast, secao policy)
exit_code=3
auditoria blocking_findings: rule_id=semgrep:policy.regras.demo-sem-eval path=calc.js line=5 origin=sast
A linha auditoria blocking_findings foi impressa pelo script a partir de
blocking_findings[] do JSON da auditoria; o campo origin vale sast. Com
uma política que lista sources: [skills, analysis] (sem sast):
gate:
fail_on: [error]
inconclusive: warn
sources: [skills, analysis]
quality_gates:
sast:
engine: semgrep
enabled: true
fail_on_severity: ERROR
rule_packs: ["/policy/regras/sast.yml"]
review:
language: pt-BR
--- gate.sources sem sast
exit_code=0
O que observar: o achado do Semgrep continua publicado no parecer, mas já não
reprova o gate (exit 0). Esta política só vale para o gate com gate
declarado; a seção quality_gates.sast do repositório, ao contrário, nunca é
afrouxável sob política.
Caso 5: linter real, engine govet
go vet é uma engine registrada (govet, categoria lint, origem govet).
Ela só roda quando quality_gates.scanners a declara; sem a declaração, o
review é o de sempre e nada é instalado:
quality_gates:
scanners:
- engine: govet # categoria lint, origem govet
fail_on: warning # go vet reporta warning; o padrao ERROR so publicaria
gate:
fail_on: [error]
review:
language: pt-BR
A main já tem um fmt.Printf com verbo errado em legado/legado.go; a
branch feature adiciona outro em calc/calc.go, linha 9. A engine roda
go vet -json ./... e guarda só o achado que cai numa linha adicionada pelo
intervalo revisado (git diff --relative <base>...<head>): o de legado.go é histórico
do repositório, não do PR. A regra é o analisador do próprio go vet
(go-vet/printf), que qualquer um reexecuta.
A imagem do produto não traz o Go, então este caso põe na frente do PATH um
go falso que devolve exatamente o JSON que o go vet -json real (go1.27.1)
devolve para este repositório, e grava se os segredos do processo do
aurumcode chegaram a ele. O caso passa LLM_API_KEY e GITHUB_TOKEN ao
aurumcode; o processo da engine recebe só um ambiente explícito (PATH,
HOME, TMPDIR, locale, certificados, os caches do Go e
GOTOOLCHAIN=local/GOPROXY=off/CGO_ENABLED=0):
#!/bin/sh
# go falso, so para o tutorial: a imagem do produto nao traz o Go. Responde o
# que o `go vet -json ./...` real (go1.27.1) responde para este repositorio de
# exemplo, com os mesmos dois achados medidos: um numa linha que o PR
# adicionou, outro num arquivo que o PR nao tocou. Antes, grava em
# ambiente-do-go.txt se os segredos do processo do aurumcode chegaram aqui.
{
for v in LLM_API_KEY GITHUB_TOKEN; do
if printenv "$v" >/dev/null; then echo "$v no ambiente do go: PRESENTE"; else echo "$v no ambiente do go: ausente"; fi
done
} > "$PWD/ambiente-do-go.txt"
case "$1" in
env) echo go1.27.1-falso ;;
vet)
cat <<JSON
{"example.com/calc/calc": {"printf": [{"posn": "$PWD/calc/calc.go:9:35", "message": "fmt.Printf format %d has arg s of wrong type string"}]}}
{"example.com/calc/legado": {"printf": [{"posn": "$PWD/legado/legado.go:6:37", "message": "fmt.Printf format %d has arg s of wrong type string"}]}}
JSON
;;
*) echo "go falso: comando nao simulado: $*" >&2; exit 2 ;;
esac
aurumcode review --base main
$ aurumcode review --base main
aurumcode review: policy gate: go-vet/printf - fmt.Printf format %d has arg s of wrong type string (rule go-vet/printf) (severidade warning, limiar warning, origem govet, secao repo)
calc/calc.go:9: [warning] fmt.Printf format %d has arg s of wrong type string (rule go-vet/printf)
exit_code=3
LLM_API_KEY no ambiente do go: ausente
GITHUB_TOKEN no ambiente do go: ausente
O que observar: um achado só, em calc/calc.go:9, com origem govet; o
fail_on: warning da entrada faz o warning do go vet reprovar (com o padrão
ERROR ele só seria publicado). Nenhum segredo chegou ao go. (Conclusão do
script: exit 3 e as duas linhas ausente.)
Quando falha
Semgrep ausente do PATH, com erro de execução, ou com saída que não é um
relatório confiável (JSON inválido, sem results, ou com errors) é um
achado inconclusivo próprio, nunca "varredura limpa". O produto resolve
semgrep pelo PATH (não existe --semgrep-bin), então o tutorial coloca na
frente do PATH um Semgrep falso que sai com 2 e não imprime relatório:
#!/bin/sh
# Semgrep falso, so para o tutorial: sai com erro, sem relatorio. O aurumcode
# resolve "semgrep" pelo PATH (nao existe --semgrep-bin).
echo "semgrep falso: falhando de proposito" >&2
exit 2
O mesmo comando, com gate.inconclusive: block e depois warn (o script
acrescenta o bloco gate: ao .aurumcode/config.yml do caso):
aurumcode review --base main
--- gate.inconclusive: block
aurumcode review: policy gate: SAST (semgrep, origem sast, secao repo) inconclusivo (o Semgrep falhou ao rodar [sast_execution_error])
exit_code=1
--- gate.inconclusive: warn
exit_code=0
O que observar: com block, exit 1; com warn, o alerta inconclusivo é
publicado e o comando sai 0, mas a revisão não é aprovação (o parecer não
traz achado do SAST, o que não quer dizer varredura limpa). Escolha block num
gate de conformidade. (Conclusão do script: os exits 1 e 0, a mesma falha com
dois modos.)
Sem go no PATH
O mesmo repositório do Caso 5, agora com um PATH sem o Go (a imagem do
produto traz o Go pinado em /usr/local/go/bin; o caso o deixa de fora do
PATH). O aurumcode não instala nada: a engine fica inconclusiva
(lint_unavailable) e, com gate.inconclusive ausente (= block), o gate
reprova.
aurumcode review --base main
$ aurumcode review --base main
aurumcode review: LINT (Govet) inconclusivo: a varredura não produziu resultado confiável (lint habilitado, mas o go vet não está disponível [lint_unavailable]); nenhum achado determinístico do Govet foi publicado nesta execução.
aurumcode review: policy gate: LINT (govet, origem govet, secao repo) inconclusivo (lint habilitado, mas o go vet não está disponível [lint_unavailable])
exit_code=1
O que observar: inconclusivo nunca é "zero achados"; o mesmo vale para um
pacote que não compila ou uma dependência fora do cache de módulos
(lint_execution_error). (Conclusão do script: exit 1.)
Problemas comuns
- Pacote
p/...num runner sem rede: demora até o limite de 120 s e fica inconclusivo (Caso 2). Use arquivos de regra locais, versionados na política. - Caminho do pacote sob política: é relativo à raiz do repositório revisado; use caminho absoluto para o diretório da política.
check_idmuda com o caminho do arquivo de regras: se você referencia a regra em exceções ou comparações, use orule_idexato da saída.nosemgrep"não funciona": sob política é desligado de propósito.- Esperar
--semgrep-bin: não existe; use oPATHou a imagem do produto. - Resultado sem achados com SAST inconclusivo: leia as linhas
SAST ... inconclusivo; o parecer não resume o estado do SAST.
Como fica
Capturas geradas por scripts/docs/capturas.sh a partir das saídas gravadas em demo/tutoriais/sast/out/: o terminal de cada caso e, quando o caso publica, o comentario do PR e os status checks. O manifesto docs/assets/capturas/capturas.json registra o digest de cada insumo.
govet-achado


govet-sem-go


nosemgrep-e-semgrepignore


origem-sast


registry-sem-rede


regra-local


semgrep-falha

