Gate corporativo: SAST, SBOM, inventário e assinatura
Este guia mostra, com arquivos completos e uma demonstração executável, como uma organização obriga todo pull request a passar por cinco controles:
| Controle | O que faz | Onde se configura |
|---|---|---|
| SAST | Semgrep sobre a árvore inteira do repositório | quality_gates.sast |
| SBOM | Trivy gera um CycloneDX do repositório | quality_gates.ssor_dtrack.sbom_generator |
| Inventário | o SBOM vai para um OWASP Dependency-Track v5 e o gate lê as métricas do projeto | quality_gates.ssor_dtrack |
| Assinatura | Cosign assina o SBOM (e imagens, se houver) | quality_gates.supply_chain |
| Dependências | dependência nova ou alterada no PR conferida contra o OSV, pacote malicioso ou typosquat e licença proibida | dependencies |
A referência de cada chave está em configuration.md; aqui
está o conjunto que funciona junto. Todo host, projeto e organização abaixo é
um placeholder (ORG, example.org, 127.0.0.1).
Os blocos YAML deste guia são os arquivos de demo/gate-corporativo/, byte
a byte (tests/acceptance/AUR-554.sh AC-001 compara). Copie-os como estão e
troque só os placeholders.
1. Política central
A organização mantém um repositório de política (aqui ORG/aurumcode-policy)
cujo diretório contém .aurumcode/config.yml e as regras. O repositório do
dev não consegue afrouxar o que a política governa. Sob política central,
rules, ignore, gate e exceptions do repositório são sempre ignorados,
com um aviso nomeado, mesmo quando a política não os declara. Em
quality_gates, vale a seção que a política declara: ssor_dtrack e
supply_chain declarados na política substituem os do repositório, e os que
ela não declara continuam do repositório. Os scanners (sast e scanners) se
resolvem engine por engine: quality_gates.sast e as entradas com
required: true na política impedem o repositório de trocar ou desligar
aquela engine; uma entrada da política sem required: true cede à do
repositório, com aviso. A tabela completa de quem decide cada seção está no
tutorial de política central.
gate:
fail_on: [high]
inconclusive: block
quality_gates:
sast:
engine: semgrep
enabled: true
fail_on_severity: ERROR
rule_packs: ["/github/policy/regras/sast-demo.yml"]
ssor_dtrack:
enabled: true
server_api_host: "http://127.0.0.1:8081"
api_key_secret: DTRACK_API_KEY
project_id_secret: DTRACK_PROJECT_ID
thresholds: {max_critical: 0, max_high: 0, policy_violations: 0}
timeout_seconds: 180
poll_interval_seconds: 5
sbom_generator:
tool: trivy
format: cyclonedx
spec_version: "1.6"
output_file: sbom_app_cyclonedx.json
supply_chain:
engine: cosign
sign_sbom: true
sign_artifacts: false
gate.fail_on: [high]reprova achados de severidade alta ou acima;gate.inconclusive: blockfaz uma revisão inconclusiva (provedor fora, scanner quebrado, servidor de inventário inalcançável) reprovar em vez de passar em silêncio. É também o padrão: sem a chave, comgatedeclarado ou um scanner habilitado, o inconclusivo reprova. Usewarn, escrito, só durante a adoção.- Chave desconhecida (erro de digitação como
fial_on) na política ou no.aurumcode/config.ymldo repositório é erro de carga, antes do modelo, com a chave nomeada;quality_gates.sast.engineque não é uma engine registrada da categoriasast(hoje sósemgrep) também, assim como umenginedesconhecido emquality_gates.scanners. quality_gates.sasté o alias da entradasemgrepdequality_gates.scanners(ver Configuração). Na lista genérica,required: truena política impede o repositório de remover ou afrouxar a engine; o override do repositório vira aviso de política ignorada.engine: gitleaks(categoriasecrets) varre os commits do PR (base..head), não só a árvore final. Na política,required: truegarante que o repositório não a desliga,gitleaks:allownão suprime achado e um.gitleaksignorena raiz vira o achado bloqueantegitleaks:ignore-file-present. Nenhuma saída leva o valor do segredo. Ver Configuração.quality_gates.sast:rule_packsaceitap/...do registro do Semgrep (precisa de rede) ou arquivos de regra locais, determinísticos e offline, como abaixo. O caminho/github/policy/...é onde o workflow reutilizável monta o repositório da política dentro do container.quality_gates.ssor_dtrack:server_api_hostprecisa serhttps://em produção (por exemplohttps://dtrack.example.org);http://127.0.0.1:...existe só para o servidor local da demonstração.api_key_secreteproject_id_secretsão nomes de variáveis de ambiente, nunca valores.thresholdscomparacritical,highepolicyViolationsTotaldas métricas do projeto;policy_violations: 0reprova qualquer violação de política do Dependency-Track.quality_gates.supply_chain:sign_sbom: trueassina o SBOM. Em CI a assinatura é keyless (identidade OIDC do job, sem chave para guardar).
Regras de SAST locais
rules:
- id: demo-sem-eval
languages: [javascript]
severity: ERROR
message: "eval() executa texto como codigo; use um parser ou uma tabela de operacoes"
pattern: eval(...)
O achado aparece como semgrep:<check_id>; o check_id do Semgrep é derivado
do caminho do arquivo de regra (github.policy.regras.demo-sem-eval, porque
a política é montada em /github/policy). Um achado de severidade ERROR
reprova (fail_on_severity: ERROR); abaixo do limiar ele é publicado sem
reprovar. Em produção, troque ou some o arquivo por p/security-audit etc.
Padrões de engenharia centrais
O workflow aceita dois repositórios centrais: policy_repository, da
segurança, e standards_repository, da engenharia (a camada de padrões,
AUR-613). A camada de padrões é o .aurumcode/ de outro repositório, obtido
por checkout pelo workflow, fora da árvore revisada, com persist-credentials
desligado, e contribui só duas coisas: skills
(.aurumcode/skills/<nome>/SKILL.md), que valem para todo serviço como
regras do time de engenharia, e analistas (.aurumcode/profiles.yml), que
cada serviço seleciona em review.profiles. Ela nunca declara gate, ignore,
exceções, regras, scanners ou publicação: um config.yml com qualquer seção
é recusado, e o review falha antes de chamar o modelo. Ordem: política de
segurança > padrões > serviço; uma skill ou analista de mesmo nome numa camada
de baixo é ignorado com aviso que nomeia as duas camadas. O achado de uma
skill de padrões diz [padrões de engenharia] no parecer e conta no gate como
fonte skills; a auditoria registra standards (repositório, ref e digest
do conteúdo) só quando a camada existe.
jobs:
review:
uses: SuaOrg/AurumCode/.github/workflows/review.yml@<sha-fixa-do-aurumcode>
with:
policy_repository: SuaOrg/security-policy
policy_ref: a1b2c3d4e5f6...
standards_repository: SuaOrg/devops-policy
standards_ref: 0f9e8d7c6b5a...
secrets:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
AURUMCODE_POLICY_TOKEN: ${{ secrets.AURUMCODE_POLICY_TOKEN }}
AURUMCODE_STANDARDS_TOKEN: ${{ secrets.AURUMCODE_STANDARDS_TOKEN }}
AURUMCODE_STANDARDS_TOKEN só é preciso para um repositório de padrões
privado ou interno (token fine-grained com Contents: read nesse repositório);
sem ele o checkout usa o github.token. Fora do workflow, aurumcode review
--padroes <dir> (ou AURUMCODE_STANDARDS) aponta a mesma camada. A seção
"Camada de padrões de engenharia" de docs/configuration.md tem a tabela do
que cada camada pode.
Continua valendo, como alternativa, o caminho por pasta no repositório da
política: skills do time de engenharia numa pasta própria (por exemplo
skills/padroes/), listadas em review.context.skills do config.yml da
política, com a aprovação dessa pasta dada ao time de engenharia por
CODEOWNERS (/skills/padroes/ @ORG/engenharia) — o CODEOWNERS só obriga
essa aprovação se a proteção da branch exigir revisão de code owners. Essas
seções viram regras da política e só mudam quando o policy_ref aponta a
nova SHA. A diferença da camada nativa: repositório, dono, token e ref
próprios, sem misturar a aprovação de segurança com a de engenharia.
2. Configuração do repositório
O repositório do dev só precisa do básico; o gate vem da política.
review:
language: en
publication: comments
3. Workflow obrigatório da organização
# Workflow obrigatorio da organizacao (repositorio do dev ou ruleset da organizacao).
# ORG, <SHA...> e os secrets sao placeholders: troque pelos seus.
name: Revisao obrigatoria AurumCode
on:
pull_request:
jobs:
review:
# Um workflow reutilizavel nao concede permissao que o chamador nao tem:
# id-token: write e necessario para a assinatura keyless (Cosign).
permissions:
contents: read
pull-requests: write
statuses: write
checks: read
id-token: write
uses: ORG/AurumCode/.github/workflows/review.yml@<SHA-FIXA-DE-40-HEX-DO-AURUMCODE>
with:
policy_repository: ORG/aurumcode-policy
policy_ref: <SHA-FIXA-DE-40-HEX-DA-POLITICA>
secrets:
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
DTRACK_API_KEY: ${{ secrets.DTRACK_API_KEY }}
DTRACK_PROJECT_ID: ${{ secrets.DTRACK_PROJECT_ID }}
# Politica em repositorio privado ou interno: token so de leitura nele.
AURUMCODE_POLICY_TOKEN: ${{ secrets.AURUMCODE_POLICY_TOKEN }}
- Para a camada de padrões de engenharia, acrescente em
with:standards_repository: ORG/devops-policyestandards_ref: <SHA-FIXA-DE-40-HEX-DOS-PADRÕES>e, se o repositório for privado,AURUMCODE_STANDARDS_TOKENemsecrets:(bloco da seção "Padrões de engenharia centrais"). A demonstração da seção 7 não monta essa camada. - No workflow obrigatório da organização, fixe
review.ymlepolicy_refpor SHA de 40 hex, nunca por branch ou tag: uma tag pode ser apontada para outro commit, a SHA não. É isso que garante que todo PR da organização é julgado pela mesma ferramenta e pela mesma política até alguém, de propósito, apontar outra SHA. Num repositório avulso, a tag de uma release basta (veja Primeiro review). - A política não fixa uma versão mínima do AurumCode: a versão que roda é a
da SHA em
uses:deste workflow. - Atualizar o AurumCode é uma PR neste workflow, que vale para todos os
repositórios de uma vez. Deixe a versão num comentário ao lado da SHA
(
@<SHA> # v2.0.0) e ligue o Dependabot (package-ecosystem: github-actions) no repositório deste workflow: ele abre a PR quando sair versão nova, com as notas dela. O Dependabot atualiza só a referência deuses:, nunca opolicy_ref: opolicy_refé um input do workflow e muda por uma PR de quem mantém este workflow, depois que a mudança da política foi aprovada (seção 8). - O workflow publica o status
aurumcode/policy-gate; ele só bloqueia o merge se a proteção da branch (ou o ruleset da organização) o exigir. - Para tornar o workflow obrigatório em toda a organização, crie um ruleset da
organização com a regra Require workflows to pass before merging,
apontando para este arquivo, e a regra Require status checks to pass
before merging, com
aurumcode/policy-gate. O AurumCode não cria nem confere esse ruleset: ele é configurado no GitHub, e depende do plano da organização (o GitHub documenta rulesets da organização para os planos Team e Enterprise, e a regra que exige o workflow na documentação do Enterprise Cloud). Sem essa regra, cada repositório chama este workflow por conta própria e exigeaurumcode/policy-gatena proteção da branch. - Publique o SARIF no code scanning em um segundo job do seu próprio workflow
(
security-events: write): veja a seção "Trilha de auditoria e SARIF" de configuration.md.
Como o workflow reutilizável encadeia os passos (.github/workflows/review.yml,
AUR-555). A ordem é: SBOM (aurumcode sbom, Trivy fixado por digest), depois
a revisão (gate: SAST, envio do SBOM ao Dependency-Track e leitura das
métricas), depois a assinatura (Cosign), que só roda quando a revisão termina
com sucesso: um artefato reprovado não é assinado. O bundle .sigstore.json
sobe como artefato do job. DTRACK_API_KEY e DTRACK_PROJECT_ID são secrets
opcionais do workflow_call (required: false), expostos só ao passo da
revisão; o workflow chamador os repassa em secrets: (como no bloco acima), ou
usa secrets: inherit, como o code-review.yml deste repositório. Sem
ssor_dtrack nada disso é exigido; com ssor_dtrack e secret ausente o gate
fica inconclusivo (dtrack_secret_missing) e, com inconclusive: block,
reprova, nunca aprova. Limites: os nomes repassados são os padrão
(DTRACK_API_KEY/DTRACK_PROJECT_ID); se api_key_secret/project_id_secret
forem renomeados, o workflow não repassa os novos nomes. Se o passo de SBOM
falha, o job falha antes da revisão. O que o AUR-555 provou é a estrutura do
YAML por testes; este guia e a demonstração não provam a execução em um
runner real do GitHub. A demonstração (seção 7) reproduz a mesma ordem
(sbom, review, sign) em containers locais.
Versões fixadas das ferramentas
Versões no workflow reutilizável e na imagem do produto, nesta versão do AurumCode:
| Ferramenta | Versão | Onde está fixada |
|---|---|---|
| gitleaks | v8.30.1 | imagem por digest em .board/bootstrap/locks/scanners.yml; o Dockerfile e o workflow recusam outra versão |
| Semgrep | 1.172.0 | Dockerfile (semgrep==1.172.0), a mesma versão de scanners.yml |
| osv-scanner | v2.0.0 | Dockerfile, compilado do módulo nessa versão |
| Trivy | 0.73.0 | imagem por digest em scanners.yml; o workflow roda essa imagem, nunca um Trivy instalado |
| Cosign | v3.1.3 | cosign-release em .github/workflows/review.yml, com o instalador fixado por SHA |
Nenhuma ferramenta roda como latest. Atualizar uma delas é uma PR neste
repositório, revisada como qualquer outra; o seu workflow passa a usar a
versão nova quando você troca a SHA de uses:.
4. Secrets
| Nome | Para quê | Onde fica |
|---|---|---|
LLM_API_KEY, LLM_BASE_URL |
provedor do modelo | secrets da organização/repositório |
DTRACK_API_KEY |
chave de API do time no Dependency-Track | secret; o nome vem de api_key_secret |
DTRACK_PROJECT_ID |
UUID do projeto do serviço | secret; o nome vem de project_id_secret |
AURUMCODE_POLICY_TOKEN |
ler a política central num repositório privado ou interno (o github.token de cada repositório não lê outro repositório privado) |
secret da organização; token fine-grained ou de GitHub App com Contents: read só no repositório da política |
O AURUMCODE_POLICY_TOKEN vai direto ao actions/checkout do passo
Checkout central policy. Com a política num repositório privado ou interno,
se o token faltar, tiver vencido ou não tiver Contents: read nele, o job
para nesse passo, com o erro do próprio actions/checkout, antes de qualquer
revisão: nenhum
parecer e nenhum status aurumcode/policy-gate é publicado. Com esse status
exigido pela proteção da branch, a PR fica esperando por ele e não pode ser
mesclada; o motivo está no log desse passo.
Uma PR aberta pelo Dependabot roda o workflow só com os Dependabot secrets:
os secrets de Actions não chegam a ela. Com o workflow obrigatório, isso vale
para toda PR do Dependabot em todo repositório da organização. Cadastre os
mesmos nomes da tabela também como Dependabot secrets da organização; sem
eles, a PR para no primeiro passo (LLM_API_KEY ou LLM_BASE_URL vazio),
não lê a política
privada (sem AURUMCODE_POLICY_TOKEN) ou fica inconclusiva no inventário
(sem DTRACK_*, com ssor_dtrack ligado), e nenhum desses casos aprova.
Nada disso entra em Markdown ou YAML versionado. A chave do Dependency-Track é
registrada no filtro de redação assim que é lida: não aparece no parecer, na
auditoria, no SARIF nem nos logs. Dê ao time dono da chave só
BOM_UPLOAD, VIEW_PORTFOLIO e VIEW_POLICY_VIOLATION, e crie um projeto
por serviço: enviar SBOMs de serviços diferentes para o mesmo projeto faz um
sobrescrever o outro.
5. O que reprova e o que é inconclusivo
Reprova (código de saída 3 no --base; status aurumcode/policy-gate
failure no PR):
- achado do Semgrep na severidade de
fail_on_severityou acima, nomeando a regra e a linha (src/calc.js:5 ... semgrep:github.policy.regras.demo-sem-eval); - métrica do Dependency-Track acima do limite
(
ssor_dtrack: policy_violations 1 > policy_violations 0,critical 3 > max_critical 0); - achado de skill da política acima de
gate.fail_on; - com a seção
dependenciesdeclarada: advisory introduzido pelo PR no limiar dedependencies.fail_on, pacote malicioso (MAL-), suspeita de typosquat no limiar e licença delicenses_denied(veja Dependências do PR); - falha de assinatura:
aurumcode signnunca tem modo "warn".
Inconclusivo (nunca aparece como "aprovado"; com inconclusive: block, ou
sem a chave, reprova; só warn escrito avisa): Semgrep ausente ou com saída inválida (sast_*), Trivy falhou
(sbom_generation_failure), servidor de inventário fora do ar, lento ou com
erro HTTP (dtrack_unreachable, dtrack_timeout, dtrack_http_error),
métricas incompletas (dtrack_metrics_incomplete: ausente nunca é lido como
zero), secret ausente (dtrack_secret_missing), SBOM ausente
(dtrack_sbom_unavailable), falha do provedor do modelo (provider_failure),
verificação de dependências que não concluiu (dependencies_*: base OSV ou
fonte de metadados do registro (deps_dev_url) inalcançável, scanner ausente,
licença desconhecida).
Em produção, as métricas critical e high vêm dos espelhos de
vulnerabilidade do próprio servidor (NVD, GitHub Advisories, OSV...),
sincronizados pelo Dependency-Track. Um servidor recém-criado não os tem; por
isso a demonstração usa uma política de violação do Dependency-Track
(coordenadas do componente), que dá policy_violations > 0 de forma
determinística, sem depender de rede.
6. Onde ver auditoria, SARIF e métricas
- Parecer: no PR (status
aurumcode/policy-gate) e na saída do comando, com as linhaspolicy gate: ...nomeando regra, limiar e origem. - Auditoria:
--auditoria <arquivo>grava o JSON com o digest da política, o SHA do workflow, a decisão do gate e as exceções aplicadas. O workflow reutilizável envia o artefatoaurumcode-audit-<PR>. - SARIF:
--sarif <arquivo>; artefatoaurumcode-sarif-<PR>, publicável no code scanning. - Métricas e violações: na interface do Dependency-Track (projeto do
serviço) ou pela API (
/api/v1/metrics/project/<uuid>/current,/api/v1/violation/project/<uuid>), comorun.shfaz. - Assinatura: o bundle
<sbom>.sigstore.jsonsai como artefatoaurumcode-sbom-bundle-<PR>. Para verificar um SBOM assinado em CI (keyless) veja "Verificação por terceiros" em configuration.md; na demonstração a chave é efêmera:
cosign verify-blob --key cosign.pub --bundle sbom_app_cyclonedx.json.sigstore.json \
--insecure-ignore-tlog --insecure-ignore-sct sbom_app_cyclonedx.json
7. Demonstração executável
demo/gate-corporativo/run.sh sobe tudo em containers fixados por digest
(images.lock): imagem do produto (com Semgrep), Dependency-Track v5 +
PostgreSQL, Trivy e Cosign. No host só há bash, git, docker, curl e python3.
Precisa de rede para baixar as imagens e de uns 2 GB de disco.
demo/gate-corporativo/run.sh all # build up fail fix pass verify down
demo/gate-corporativo/run.sh --check # compara out/ com expected/ (sem docker)
Cada fase grava demo/gate-corporativo/out/<fase>.log; expected/<fase>.txt
tem os trechos que --check exige.
| Fase | O que acontece |
|---|---|
build |
constrói a imagem do produto, confere o Semgrep contra scanners.yml, fixa e extrai Trivy e Cosign |
up |
sobe Dependency-Track + PostgreSQL, troca a senha inicial, cria time, chave, projeto e a política de violação |
fail |
aurumcode sbom + aurumcode review: reprova no SAST e no inventário |
fix |
remove o eval e sobe o lodash 4.17.15 para 4.17.21 |
pass |
o gate aprova, o SBOM é enviado e assinado |
verify |
cosign verify-blob aceita a assinatura e rejeita um SBOM adulterado |
down |
remove containers, rede e volumes |
Defeitos plantados em repo-exemplo/: src/calc.js chama eval(...) (regra
local do Semgrep) e package-lock.json fixa lodash 4.17.15, que a política
demo-componente-proibido do Dependency-Track reprova.
Saída real de fail (trecho de out/fail.log):
sbom: CycloneDX 1.7, componentes: lodash@4.17.15
aurumcode review: policy gate: semgrep:github.policy.regras.demo-sem-eval - eval() executa texto como codigo; use um parser ou uma tabela de operacoes (rule semgrep:github.policy.regras.demo-sem-eval) (severidade error, limiar error, origem sast, secao policy)
aurumcode review: policy gate: ssor_dtrack: policy_violations 1 > policy_violations 0 (origem dtrack)
## AurumCode code review
> [!CAUTION]
> **Blocked: 2 problems must be fixed before merge.**
policy gate: failed · 5 files reviewed
src/calc.js:5: [error] eval() executa texto como codigo; use um parser ou uma tabela de operacoes (rule semgrep:github.policy.regras.demo-sem-eval)
aurumcode review: exit_code=3
dependency-track metricas: critical=0 high=0 policyViolationsTotal=1
dependency-track violacao: politica=demo-componente-proibido componente=lodash versao=4.17.15 estado=FAIL
RESULTADO: gate reprovou, como esperado
E de pass:
sbom: CycloneDX 1.7, componentes: lodash@4.17.21
aurumcode review: policy gate: ssor_dtrack: aprovado (critical=0, high=0, policy_violations=0)
## AurumCode code review
> [!TIP]
> **Approved: no problem found in the reviewed change.**
policy gate: passed · 5 files reviewed
No issues found.
aurumcode review: exit_code=0
dependency-track metricas: critical=0 high=0 policyViolationsTotal=0
dependency-track violacoes: nenhuma
RESULTADO: gate aprovou
sign: sbom /github/workspace/sbom_app_cyclonedx.json -> /github/workspace/sbom_app_cyclonedx.json.sigstore.json
O gate roda sem provedor de modelo real: AURUMCODE_LLM_FIXTURE aponta para
uma resposta vazia (fixture-llm.json) e --seguranca liga o passe
determinístico, de modo que o parecer vem só das ferramentas. No aceite, a linha
ssor_dtrack: aprovado (critical=0, high=0, policy_violations=0) é exigida:
sem ela um servidor inalcançável (inconclusivo) poderia passar por aprovação.
A prova completa (data, digests, log) está em specs/AUR-554.md.
8. Como pedir exceção
Uma exceção é uma entrada em exceptions na política central (a do
repositório do dev é ignorada), aprovada por quem é dono da política, para um
achado exato:
exceptions:
- repo: ORG/servico-exemplo
rule: semgrep:github.policy.regras.demo-sem-eval
path: src/calc.js
owner: time-seguranca
reason: expressao fixa em tempo de build, sem entrada do usuario
expires: 2026-12-31
Quando o modelo contesta um achado determinístico (ele recebe os achados do
passe de segurança, da análise embutida e do SAST antes de responder), o
parecer e a auditoria (proposed_exceptions) trazem uma exceção proposta
neste mesmo formato, com a regra, o caminho e o motivo do modelo, e
owner/expires a preencher. Ela nunca é aplicada: o achado continua
reprovando até que alguém copie a entrada para a política pelo fluxo abaixo.
A contestação do modelo nunca rebaixa um achado sob a política central
(gate.triage só vale sem política; veja docs/configuration.md).
Os seis campos são obrigatórios; path é exato (sem glob); expires é
YYYY-MM-DD em UTC. Vencida, a exceção para de valer sozinha e o achado volta a
reprovar. O fluxo: abra um PR no repositório da política com a entrada acima e
o motivo; o time de segurança aprova; depois do merge, o policy_ref do
workflow obrigatório é atualizado para a nova SHA.