51 · O caçador de bugs como gate de qualidade no CI¶
Um gate de qualidade no CI funciona como uma equipe que usa o SonarQube espera: um projeto por repositório, uma linha de base no branch padrão, um perfil que diz o que reprova, todo pull request varrido e anotado, o merge bloqueado enquanto o gate reprova, e as decisões de uma pessoa guardadas de uma varredura para a seguinte. Este capítulo monta isso do início, para GitHub, GitLab e Jenkins. O capítulo 28 traz as condições do gate por completo, e o capítulo 46 a verificação de agentes por completo.
Idiomas
O seletor lê turnos escritos em inglês e em português do Brasil (pt-BR). O idioma é escolhido, nunca adivinhado a partir do turno: na página Settings do console (Language the selector reads), em language numa requisição de plano, ou no settings.language do seu arquivo de vocabulário; sem nenhum deles, inglês. Um turno em português pode ser digitado com os acentos ou sem eles. O português europeu não é uma gramática que o seletor tenha, e um turno em qualquer outra língua não é lido.
Dois gates¶
Um agente: stalwart verify |
Uma política ou um roteador: as varreduras de um projeto | |
|---|---|---|
| Verifica | o grafo dos agentes, o rulebook e o código das suas ferramentas (QC) |
uma política de guardrails, uma planilha de regras, um roteador construído a partir do seu catálogo |
| Comparado com | nada: todo achado é lido como novo | a linha de base do projeto, a última varredura do branch padrão |
| Reprova com | qualquer achado QC, e as condições do gate sobre os achados do grafo |
as condições do gate sobre o que é novo, pelo perfil do projeto |
| Lembra | nada | o histórico de cada achado e as decisões de uma pessoa |
| Roda a partir de | qualquer CI, com o comando stalwart |
a Action do GitHub, ou a API HTTP a partir de qualquer CI |
Um agente declarado no agent.yaml é verificado com stalwart verify. Uma equipe que guarda uma política de
guardrails ou um roteador num repositório, e quer uma linha de base e uma trilha de revisão, o guarda num
projeto. Um repositório pode ter os dois.
1. Um projeto e a sua linha de base¶
POST /v1/projects com um name e o default_branch (main onde nenhum é dado) cria um projeto. A primeira
varredura no branch padrão é a sua linha de base: todo achado nela é existente, então nenhum reprova o gate. A
partir daí:
- uma varredura no branch padrão avança a linha de base, e os achados que ela não tem mais ficam
fixed; - uma varredura em qualquer outro branch relata contra a linha de base, como
new,existing,fixedoureintroduced, e não move nada; - um achado que foi corrigido e volta fica
reintroduced, e o gate o lê como novo.
Então o branch padrão é varrido a cada push nele, e todo pull request é varrido contra ele.
2. O perfil de qualidade¶
Sem perfil, o gate tem as suas condições padrão (capítulo 28): reprova com um achado Critical novo, uma linha quebrada das suas próprias tabelas, uma política nova que não pode ser publicada, um achado não reproduzido e uma divergência do motor. Um perfil acrescenta condições, ou as remove:
{
"gate": {
"add": ["no_new_severity_at_least:High", "no_new_rule:QG101"]
}
}
no_new_severity_at_least:High reprova com um achado novo de severidade High ou maior, e no_new_rule:QG101
com um achado novo sob essa regra. PUT /v1/projects/{id}/profile com {"profile": ...} o define, e
{"profile": null} volta ao padrão; a próxima varredura o lê. Um perfil precisa de uma chave com o
entitlement hunt_details (capítulo 40).
3. Todo pull request¶
No GitHub, a Action do Guardrail Bug Hunter faz cada passo a cada execução:
- envia os arquivos que as suas entradas nomeiam -- a política, a configuração de rails, o modelo extraído de um host;
- submete uma varredura do projeto no branch e no commit do pull request, e a acompanha até o fim;
- envia o SARIF da varredura ao code scanning, para cada achado aparecer na linha de que trata;
- publica um comentário no pull request -- achados novos, corrigidos e existentes, o resultado do gate e um link para o projeto -- e atualiza esse comentário numa nova execução em vez de acrescentar outro;
- reprova o check quando o gate reprova.
Torne esse check obrigatório nas regras de proteção do branch, e um pull request cujo gate reprova não pode ser mergeado. Rode o mesmo workflow nos pushes ao branch padrão, para a linha de base ficar em dia.
Em qualquer outro CI, a mesma varredura são três requisições à API HTTP (capítulo 18):
POST /v1/uploadspara cada arquivo;POST /v1/projects/{id}/scanscom obranch, ocommite umarequesthuntcomoPOST /v1/solvesa recebe, com o operandogate: true;GET /v1/solves/{job_id}até o job terminar: ogatedo documento de achados trazpassed, asconditionsque leu e as que falharam (failed).
Saia com falha onde passed for false, e torne o job um check obrigatório do merge request. O SARIF está entre
os artefatos do job, para uma visão de code scanning ou para o SonarQube (abaixo).
4. Agentes em todo pull request¶
stalwart verify agent.yaml sai com 1 onde o gate reprova, 2 onde o arquivo não pode ser lido ou falta a
chave ou a licença, e 3 onde a caça ou as verificações de código não puderam rodar
(capítulo 46). Todo CI bloqueia um merge com um passo que falhou, então o comando é o
gate. O runner é uma máquina nova a cada vez, então a licença é ativada primeiro e liberada por último,
aconteça o que acontecer no meio.
GitHub Actions:
# .github/workflows/agent.yml: the shop agent checked on every pull request.
name: agent
on: [pull_request]
jobs:
verify:
runs-on: ubuntu-latest
env:
QLS_API_KEY: ${{ secrets.QLS_API_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install stalwart-agents
- run: stalwart licence activate # a runner is a new machine every time
- run: stalwart verify agent.yaml --replay --keep findings/
- if: always() # the place is released whatever happened above
run: stalwart licence release
- if: always()
uses: actions/upload-artifact@v4
with:
name: findings
path: findings/
GitLab CI, como .gitlab-ci.yml, com a chave numa variável de CI/CD mascarada:
# .gitlab-ci.yml: the shop agent checked on every merge request.
verify:
image: python:3.13
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
QLS_API_KEY: $QLS_API_KEY # a masked CI/CD variable holding a key of your account
script:
- pip install stalwart-agents
- stalwart licence activate # a runner is a new machine every time
- stalwart verify agent.yaml --replay --keep findings/
after_script:
- stalwart licence release # the place is released whatever happened above
artifacts:
when: always
paths: [findings/]
Em Settings > Merge requests, Pipelines must succeed bloqueia o merge enquanto o job falha.
Jenkins, como um Jenkinsfile, com a chave numa credencial de texto secreto chamada qls-api-key:
// Jenkinsfile: the shop agent checked on every change request.
pipeline {
agent { docker { image 'python:3.13' } }
environment {
QLS_API_KEY = credentials('qls-api-key') // a secret text credential holding a key of your account
}
stages {
stage('verify') {
when { changeRequest() }
steps {
sh 'python -m venv .venv && .venv/bin/pip install stalwart-agents'
sh '.venv/bin/stalwart licence activate' // an agent is a new machine every time
sh '.venv/bin/stalwart verify agent.yaml --replay --keep findings/'
}
}
}
post {
always {
sh '.venv/bin/stalwart licence release || true' // the place is released whatever happened above
archiveArtifacts artifacts: 'findings/**', allowEmptyArchive: true
}
}
}
Um pipeline multibranch relata o resultado do estágio no change request, e a proteção de branch do repositório o torna obrigatório.
5. Triagem¶
O ciclo de vida de um achado é a decisão de uma pessoa sobre ele, guardada no projeto e lida por toda varredura seguinte:
| Ciclo de vida | Quer dizer | O que o gate faz |
|---|---|---|
open |
ninguém decidiu ainda | o lê pelas suas condições |
confirmed |
é um problema real, a ser corrigido | o lê pelas suas condições |
false_positive |
não é um problema | o deixa fora das condições que uma decisão resolve: um achado não reproduzido, uma divergência do motor, uma linha quebrada das suas tabelas. Um achado reproduzido não é um falso positivo: continua aberto |
accepted_risk |
é um problema que a equipe aceita até uma data, com uma justificativa | como para um falso positivo, até a data; depois o achado fica open de novo |
Uma decisão resolve um achado onde ele está; não esconde um que volta. Um achado corrigido e depois reintroduzido é novo de novo, e reprova as condições sobre achados novos -- um Critical, um piso que o perfil define --, qualquer que tenha sido a decisão antes.
PATCH /v1/projects/{id}/findings/{fingerprint} com o lifecycle, uma justification e, para um risco
aceito, expires como YYYY-MM-DD registra a decisão; um risco aceito sem data dura 90 dias. As páginas
Findings do console mostram as varreduras e os achados de cada projeto e registram as mesmas decisões, e
GET /v1/projects/{id}/findings/{fingerprint} lê o histórico inteiro de um achado: quando apareceu, cada
decisão e quem a tomou. Registrar uma decisão e ler o histórico de um achado precisam de hunt_details, como o
perfil. Um achado QC não tem ciclo de vida: só mudar o código o resolve
(capítulo 50).
SonarQube¶
Uma equipe que já lê os seus achados no SonarQube importa o SARIF da varredura como issues externas
(sonar.sarifReportPaths), com as mesmas regras, severidades, locais e fingerprints. Guarde as decisões no
projeto: o gate lê a linha de base e os ciclos de vida do projeto, não os do SonarQube.
Os limites¶
- Um gate só é tão bom quanto o que recebe. Uma varredura de uma política sem o catálogo classifica toda ação como High; envie o catálogo ou o modelo extraído com ela (capítulo 28).
- O
stalwart verifynão tem linha de base. Todo achado de um agente é lido como novo a cada execução; um Critical reprova o gate até ser corrigido. - Uma caça que não pôde rodar nunca é uma aprovação. Sem rota até o serviço é o status de saída 3, não 0.