Frontend / Experiência de Terminal¶
Versão: v0.8.3 · Revisão: 2026-07-04
O Quorum não possui frontend gráfico (web, desktop ou mobile). Ele é, por
design, uma ferramenta CLI/Docker orientada a CI/CD: "configure via flags,
gate via exit code. No panel, no daemon" (cmd/quorum/root.go).
A "interface de usuário" do Quorum é, portanto, a experiência de terminal:
logs de progresso em stderr (em texto [quorum] … ou, a partir da v0.7.4, em
JSON estruturado via --log-format json), uma tabela de summary ao final,
exit codes determinísticos para gating, e o artefato SARIF — que, quando
consumido pelo GitHub Code Scanning, vira a superfície visual mais próxima de
uma GUI. Este documento descreve essa UX como ela existe no código (as-is),
trata o que não existe como N/A com justificativa, e separa claramente
eventuais propostas futuras.
1. Veredito: GUI / Web — N/A¶
| Item de template típico | Status | Justificativa técnica (as-is) |
|---|---|---|
| SPA / aplicação web (React, Vue, etc.) | N/A | Não há código de UI no repositório. O binário é um CLI Cobra (cmd/quorum). main() apenas executa o comando raiz e sai. |
| Servidor HTTP / API REST que sirva uma UI | N/A | Não há servidor; não há net/http handlers de aplicação. O Quorum é stateless e one-shot: roda, emite relatório, encerra. |
| Painel/dashboard, daemon, long-running | N/A | Princípio explícito: "No panel, no daemon" (root.go). Cada invocação é um processo efêmero. |
| Autenticação / contas / sessões de usuário | N/A | Não há multiusuário nem persistência de sessão. A única persistência é o cache local de aliases (~/.cache/quorum/aliases.json, criado com permissão 0600). |
| Componentes visuais, CSS, design system web | N/A | A renderização é texto puro em stderr/stdout. Sem ANSI/cores no código Go. |
| Responsividade / breakpoints / mobile | N/A | Conceito de viewport não se aplica a um TUI não-interativo. Ver §8 Responsividade. |
Por que isso é uma decisão, não uma lacuna. O alvo do Quorum é o runner de CI/CD e o terminal do desenvolvedor. Um painel web exigiria servidor, estado, autenticação e superfície de ataque — tudo contrário ao modelo de ameaça de uma ferramenta de segurança que roda dentro do pipeline. A "visualização" rica é delegada a sistemas já existentes (GitHub Code Scanning, qualquer viewer SARIF), via o artefato SARIF padronizado.
A superfície visual mais próxima de uma GUI é tratada em §11 GitHub Code Scanning.
1.1 Comandos disponíveis (superfície CLI)¶
A CLI expõe exatamente três subcomandos, todos não-interativos:
| Comando | Função |
|---|---|
quorum scan <target> |
Roda o pool de scanners, correlaciona por consenso e emite o relatório (foco deste documento). |
quorum list-scanners |
Lista os adapters registrados e os tipos de alvo que cada um suporta (image/repo/k8s). |
quorum advise-index |
Embeda o corpus OWASP de RAG para habilitar retrieval semântico, preservando o pin de digest (Fase 2 da camada consultiva — opt-in, desligada por padrão). |
list-scanners imprime em stdout uma linha por adapter (%-12s %v), útil
para descobrir quais dos 12 scanners estão disponíveis antes de restringir
o pool com --scanners.
2. Anatomia da experiência de terminal¶
A UX de terminal do Quorum tem três canais bem separados, o que é deliberado para permitir piping do relatório sem poluí-lo com logs:
flowchart LR
subgraph CLI["quorum scan <target>"]
A["Progresso / diagnóstico<br/>texto [quorum] … ou JSON (--log-format)"] --> E2["stderr"]
B["Tabela de summary<br/>── quorum summary ── (sempre texto)"] --> E2
C["Relatório SARIF/JSON/XML"] --> O1["stdout (ou --output arquivo)"]
D["Exit code 0 / 1 / 2"] --> X["$?"]
end
E2 --> H["Humano / logs do runner"]
O1 --> P["Pipe / upload (ex: SARIF → Code Scanning)"]
X --> G["Gate do pipeline"]
| Canal | Conteúdo | Quando aparece | Controlado por |
|---|---|---|---|
stdout |
Relatório (SARIF/JSON/XML) | Sempre que --output/-o não é dado |
--format, --output |
stderr |
Logs de progresso ([quorum] … ou JSON) + tabela de summary |
Durante o scan e ao final | --quiet/-q, --log-format |
| exit code | 0 ok, 1 gate disparou, 2 erro de uso/runtime |
Ao encerrar | --fail-on |
Garantia de separação. Como o relatório vai para
stdoute todo o ruído (logs de progresso e tabela de summary) vai parastderr,quorum scan img -f json > report.jsonproduz um JSON limpo mesmo sem--quiet. O canal de relatório nunca é contaminado — nem pelos logs em texto, nem pelos logs em JSON de--log-format json(que também vão parastderr). Isso também vale sob--advice: o progresso consultivo e eventuais anexos de IA saem pelos mesmos canais, e sem--advicea saída é byte-idêntica.
3. Logs de progresso (stderr)¶
Os logs de progresso são emitidos pela closure logf em
runScan. Desde a v0.7.4 ela tem dois modos de
renderização, escolhidos por --log-format (default text), e continua
sendo um no-op sob --quiet:
logf := func(format string, args ...any) {
if f.quiet {
return
}
msg := fmt.Sprintf(format, args...)
if f.logFormat == "json" {
b, _ := json.Marshal(struct {
TS string `json:"ts"`
Level string `json:"level"`
Msg string `json:"msg"`
}{time.Now().UTC().Format(time.RFC3339), "info", msg})
fmt.Fprintln(os.Stderr, string(b))
return
}
fmt.Fprintf(os.Stderr, "[quorum] %s\n", msg)
}
Essa mesma logf é injetada no orquestrador via orchestrator.Options.Logf,
de modo que o pipeline inteiro fala pelo mesmo canal, com o mesmo prefixo (em
text) ou no mesmo envelope (em json).
3.1 Formato de log (--log-format text|json)¶
--log-format é validado logo no início de runScan — um valor fora de
{text, json} é fatal (invalid --log-format "x" (want text|json), exit 2).
| Modo | Aparência de cada linha em stderr |
Uso típico |
|---|---|---|
text (default) |
[quorum] target=… type=image crosswalk=… offline=false |
Humano no terminal / leitura direta do log do runner |
json |
{"ts":"2026-07-04T12:00:00Z","level":"info","msg":"target=… type=image …"} |
Ingestão em coletores de log (Loki, ELK, CloudWatch, Datadog) |
Envelope JSON (uma linha por evento, newline-delimited / JSONL):
| Campo | Tipo | Conteúdo |
|---|---|---|
ts |
string | Timestamp UTC em RFC 3339 (time.Now().UTC()) |
level |
string | Sempre "info" (progresso não usa níveis distintos — ver nota) |
msg |
string | A mesma mensagem do modo text, sem o prefixo [quorum] |
Escopo do
--log-format. Ele afeta apenas os eventos de progresso da closurelogf. Ele não altera: (a) a tabela de summary (printSummarysempre imprime texto legível — ver §4); (b) o relatório emstdout(governado por--format); nem (c) a linha de erro fatal demain()(quorum: <err>). Para uma saída 100% estruturada de máquina, combine--log-format jsoncom--quiet? Não —--quietdesliga os logs por completo; escolha um ou outro conforme a necessidade (logs estruturados ou silêncio total).
levelé sempreinfo. No código atual todos os eventos de progresso — inclusivewarning: unknown scanner …,skip …egate: …— são emitidos pela mesmalogfe serializados com"level":"info". A severidade semântica do evento vive no texto demsg, não no campolevel. Consumidores que queiram filtrar por gravidade devem casar no conteúdo demsg.
3.2 Catálogo de mensagens de progresso¶
O texto de msg é idêntico nos dois formatos; muda apenas o envelope.
| Origem | Mensagem (formato) | Significado |
|---|---|---|
runScan (preâmbulo) |
target=… type=… crosswalk=N rules (dir) offline=bool |
Resumo da configuração resolvida antes do fan-out |
orchestrator.Run |
warning: unknown scanner "x" ignored (known: …) |
Nome passado em --scanners não corresponde a adapter registrado |
runOne (skip) |
skip <name>: does not support target <type> |
Adapter não suporta o tipo de alvo → status skipped |
runOne (probe lento) |
skip <name>: version probe timed out after 60s (slow start / low memory?) |
Probe de versão excedeu ProbeTime → status unavailable |
runOne (OOM) |
skip <name>: version probe killed (likely OOM — increase container memory) |
signal: killed no probe → provável OOM → status unavailable |
runOne (ausente) |
skip <name>: not installed/available |
Binário não instalado/encontrado → status unavailable |
runOne (execução) |
run <name> (<ver>) ... |
Scanner iniciou |
runOne (sucesso) |
done <name>: N findings in <dur> |
Scanner terminou com N achados brutos → status ran |
runOne (falha) |
fail <name>: <err> |
Scanner falhou (status error) ou estourou timeout (timeout) |
runScan (filtro) |
filtered: N suppressed by baseline (M entries), K below min-severity <sev> |
Pós-processamento por .quorumignore / --min-severity |
runScan (advice/enrich) |
advice: knowledge=N entries (dir) → K findings enriched |
Só sob --advice: templates determinísticos de remediação anexados (ver §4.2) |
runScan (advice/RAG) |
advice(rag): corpus=N chunks (dir, mode) → K findings gained OWASP references |
Só sob --advice: referências OWASP recuperadas do corpus pinado |
runScan (advice/IA) |
advice(ai): provider=local\|REMOTE model=… endpoint=… … |
Só sob --advice com --advice-provider=local\|remote: recomendação LLM opt-in |
runScan (métricas) |
metrics written to <path> |
--metrics gravou o arquivo Prometheus textfile (ver §6.1) |
runScan (gate) |
gate: found <sev> finding >= --fail-on <thr> → exit 1 |
Gating disparou; processo sairá com código 1 |
Supressões sempre logam. Quando o baseline ou o
--min-severitydescartam achados, o número é registrado emstderr. Isso atende ao princípio de transparência: nada é silenciosamente removido do relatório.
3.3 Sequência típica (sucesso, sem --quiet)¶
sequenceDiagram
participant U as Usuário/CI
participant Q as quorum scan
participant O as orchestrator
participant S as scanners (paralelos)
U->>Q: quorum scan img:tag --fail-on high
Q-->>U: [quorum] target=… type=image crosswalk=… offline=false
Q->>O: Run(target, Options{Logf})
O-->>U: [quorum] run trivy (0.5x) ...
O->>S: fan-out (goroutines)
S-->>O: findings
O-->>U: [quorum] done trivy: 42 findings in 3.1s
O-->>Q: Result{Runs, Merged}
Q-->>U: [quorum] filtered: 3 suppressed by baseline …
Q-->>U: (stdout) <SARIF>
Q-->>U: ── quorum summary ── (stderr)
Q-->>U: exit 0 | 1 | 2
Com
--log-format json, cada linha[quorum] …acima vira, em vez disso, um objeto JSON{"ts":…,"level":"info","msg":"…"}na mesma ordem e no mesmo canal (stderr).
4. Tabela de summary¶
Ao final de cada scan, printSummary escreve em
stderr um bloco legível para humanos. Ele é suprimido por --quiet
(retorna imediatamente quando quiet == true) e é sempre texto, independente
de --log-format (o --log-format json não estrutura o summary). Layout real
produzido pelo código, com o pool completo de 12 scanners:
── quorum summary ───────────────────────────
trivy ran 42 findings
grype ran 38 findings
checkov unavailable 0 findings (version probe killed — likely out…)
kics ran 12 findings
dockle ran 5 findings
kubescape timeout 0 findings (context deadline exceeded)
polaris ran 9 findings
kube-score ran 7 findings
terrascan ran 14 findings
tfsec ran 11 findings
regula ran 6 findings
conftest skipped 0 findings
----------------------------------------
57 findings after consensus (31 multi-detected)
CRIT 4 HIGH 12 MED 28 LOW 11 INFO 2
elapsed 7.412s
note: 0 findings is not proof of safety — see scanner statuses above.
4.1 Estrutura¶
| Bloco | Conteúdo | Fonte de dados |
|---|---|---|
| Linha por scanner | name, status, contagem de findings brutos, e (se houver) erro truncado em 60 chars |
res.Runs ([]ScannerRun) |
| Erro inline | (…) com truncate(err, 60) quando ScannerRun.Error != "" |
ScannerRun.Error |
| Total pós-consenso | N findings after consensus (M multi-detected) |
len(res.Merged) + DetectionCount>1 |
| Distribuição | CRIT … HIGH … MED … LOW … INFO … |
contagem por m.Severity |
| Tempo | elapsed <dur> arredondado a milissegundos |
res.Duration |
| Nota de cautela | note: 0 findings is not proof of safety … |
literal (DESIGN §14) |
findings brutosvsapós consenso. A coluna por scanner mostra o número de achados que aquele scanner emitiu antes de correlação. A linha de total mostra os achados após o merge de consenso. Por isso a soma das colunas é normalmente maior que o total — é esperado e desejável (vários scanners detectando o mesmo CVE ou o mesmo controle IaC colapsam em um achado comDetectionCount > 1). Com 12 scanners e crosswalk ativo (SCA e MISCONFIG/K8S_POSTURE), o consenso multi-scanner é a regra, não a exceção.
4.2 A camada consultiva (--advice)¶
Desde a v0.8.3, --advice (opt-in, desligada por padrão) anexa uma camada
consultiva ao relatório. Ela é apenas de apresentação: nunca toca em
correlationKey, fingerprint, confidence, severidade agregada ou o gate
--fail-on. Sem --advice, a saída é byte-idêntica às versões anteriores,
de modo que a UX de terminal descrita acima permanece inalterada no caminho
padrão. Quando habilitada, o único efeito visível no terminal são as linhas de
progresso advice* adicionais em stderr (ver §3.2)
e os campos consultivos carregados dentro do artefato de relatório.
| Fase / flag | O que adiciona (apenas consultivo) | Modelo / egress |
|---|---|---|
Fase 0 — --advice |
Templates curados de remediação + referências OWASP, casados por canonicalControl/ruleId/category/type. |
Determinístico, sem modelo. |
Fase 2 — --advice |
Retrieval de um corpus OWASP versionado e pinado por digest; semântico quando embedado via quorum advise-index. |
Determinístico; léxico por padrão. |
Fase 1 — --advice-provider=local |
Recomendação LLM via endpoint OpenAI-compatível on-host; --fix=suggest propõe um patch verify-the-fix (nunca auto-aplicado). |
Endpoint local (ex.: Ollama); nada sai do host. |
Fase 3 — --advice-provider=remote |
Recomendação via API externa. Envia só o achado normalizado, nunca o código; recusa --fix. |
Egress: exige --advice-allow-egress + QUORUM_ADVICE_API_KEY; bloqueado por --offline. |
Enquadramento honesto. O núcleo determinístico não tem IA. As partes de IA (Fases 1 e 3) são estritamente opt-in e desligadas por padrão; se o modelo estiver inacessível, o relatório sai sem o conselho de IA e o scan nunca falha. Todo anexo de IA é rotulado "AI-generated, advisory only". Ver 13-ia.md e 21-proposta-ia.md para o design completo.
5. Estados de scanner (status)¶
Cada scanner termina em exatamente um de cinco estados, definidos em
orchestrator.ScannerRun.Status.
Eles são a peça central da transparência da UX — "0 vulns" nunca pode parecer
"o scan não rodou" (DESIGN §14).
stateDiagram-v2
[*] --> Supports?
Supports? --> skipped: não suporta o alvo
Supports? --> Probe
Probe --> unavailable: probe falhou\n(timeout 60s / OOM / não instalado)
Probe --> Run
Run --> ran: terminou OK
Run --> timeout: estourou --timeout
Run --> error: falha de execução
ran --> [*]
skipped --> [*]
unavailable --> [*]
timeout --> [*]
error --> [*]
| Status | Quando ocorre | Diagnóstico ao usuário |
|---|---|---|
ran |
Supports==true, probe OK, Run retornou sem erro |
Contagem de findings na tabela |
skipped |
Supports(target)==false |
"does not support target …" — não é falha, só não se aplica |
unavailable |
Version() (probe) falhou: timeout (60s), signal: killed (OOM), ausente |
Mensagem distingue slow start, OOM e binário ausente, com sugestão de correção |
timeout |
Run excedeu --timeout (default 5m) → context.DeadlineExceeded |
status timeout + erro inline |
error |
Run retornou erro que não é deadline |
status error + erro inline (truncado a 60 chars no summary) |
Probe de versão de 60s. O orquestrador roda um probe de versão antes de cada scanner com
ProbeTime = 60s(defaultProbeTime, generoso de propósito, porque tools Python como ocheckovtêm cold start lento e podem ser SIGKILLed em runners com pouca memória). É esse probe que distingueunavailable/timeout/OOM de uma execução real. Com 12 scanners no pool, esse probe também evita que um único binário ausente derrube o scan inteiro — o adapter faltante viraunavailablee o resto continua.
6. Exit codes e gating¶
Os exit codes são a API de máquina da UX — é como o pipeline lê o resultado.
| Código | Significado | Origem no código |
|---|---|---|
0 |
OK — nenhum achado atingiu --fail-on (ou sem --fail-on) |
runScan retorna nil |
1 |
Gate disparou — algum achado ≥ --fail-on |
os.Exit(1) em runScan após severity.AtLeast(worst, thr) |
2 |
Erro de uso ou runtime (flag inválida, alvo ruim, etc.) | main(): fmt.Fprintln(os.Stderr, "quorum:", err); os.Exit(2) |
Fluxo de decisão do gate:
flowchart TD
A[scan concluído] --> B{--fail-on definido?}
B -- não --> Z[exit 0]
B -- sim --> C[worst = maior severidade entre Merged]
C --> D{worst >= threshold?}
D -- não --> Z
D -- sim --> E["[quorum] gate: … → exit 1"]
E --> F[exit 1]
Checklist para usar gating em CI:
- [ ] Defina
--fail-oncom a severidade-limite (critical|high|medium|low). - [ ] Considere
--min-severitypara remover ruído abaixo de um piso (afeta relatório e gating). - [ ] Trate
exit 2como erro de configuração/infra (não como "achados encontrados"). - [ ] Não confunda
exit 0com "seguro" — confira os status de scanner (unavailable/timeoutmascaram cobertura). - [ ] Em runners com pouca RAM, se vir
unavailable (OOM), aumente a memória do container ou restrinja--scanners.
Cuidado com
exit 0. Umexit 0só garante que nenhum achado atingiu o limiar; não garante que todos os scanners rodaram. Um scannerunavailable/timeoutpode ter deixado uma classe inteira de achados de fora. A nota0 findings is not proof of safetyexiste exatamente por isso.
6.1 Telemetria opcional (--metrics)¶
Ortogonal aos exit codes, a flag --metrics <arquivo> grava métricas no
formato texto do Prometheus (via writeMetricsFile → report.WriteMetrics),
com permissão 0644 (contagens não-sensíveis, feitas para scraping por um
node exporter textfile collector). O gate e o relatório não dependem dela; ao
gravar, o Quorum loga metrics written to <path>. Se a gravação falhar, o erro
é fatal (writing metrics: …, exit 2).
Métricas consultivas. Sob
--advice, o Quorum exporta adicionalmentequorum_advice_enriched{kind=remediation|references|recommendation},quorum_advice_provider{provider}equorum_advice_fix{stage=proposed|verified}(a razão verified/proposed é a taxa de verify-the-fix). Elas aparecem só quando a camada consultiva está habilitada e nunca influenciam o gating. Ver 14-observabilidade.md.
7. Modo silencioso (--quiet / -q)¶
--quiet desliga tudo que vai para stderr, nos dois formatos de log:
- Os logs de progresso (a closure
logfretorna no início, seja emtextoujson). - A tabela de summary (
printSummaryretorna no início sequiet).
O que não é afetado por --quiet:
- O relatório em
stdout/--output(continua sendo emitido normalmente). - O exit code (gating continua funcionando).
- O arquivo de
--metrics(continua sendo gravado, se pedido). - A linha de erro fatal em
main()(quorum: <err>comexit 2).
| Cenário | Sem --quiet |
Com --quiet |
|---|---|---|
| Progresso (texto ou JSON) | sim (stderr) |
não |
| Tabela de summary | sim (stderr) |
não |
Relatório (stdout/-o) |
sim | sim |
Arquivo de --metrics |
sim (se pedido) | sim (se pedido) |
| Exit code / gate | sim | sim |
| Erro fatal de uso/runtime | sim (exit 2) |
sim (exit 2) |
Recomendação CI. Use
--quiet -o report.sarif --fail-on high. Você ainda recebe o gate e o arquivo SARIF para upload, sem poluir o log do runner. Se, em vez de silêncio, você quiser logs ingeríveis por um coletor, troque--quietpor--log-format json(mutuamente exclusivos na prática: um cala, o outro estrutura).
8. Responsividade — N/A¶
| Conceito | Status | Justificativa |
|---|---|---|
| Breakpoints | N/A | Não há layout web; nada se reflui por largura de viewport. |
| Layout fluido | N/A | A tabela de summary usa colunas de largura fixa (%-10s %-12s %3d), pensadas para ≥ ~50 colunas de terminal. |
| Mobile/tablet | N/A | O alvo é terminal de CI/desenvolvedor; não há cliente móvel. |
A única consideração de "largura" é o summary: erros são truncados a 60
caracteres (truncate(r.Error, 60)) para não estourar o terminal. Não há
detecção de largura de terminal (COLUMNS) no código. Nomes de scanner cabem na
coluna de 10 caracteres — inclusive o mais longo, kube-score (10 chars).
9. Acessibilidade¶
A UX é texto puro, o que já elimina muitas barreiras (compatível com leitores de tela e braille displays via terminal). Estado atual (as-is):
| Aspecto | Estado (as-is) |
|---|---|
| Cor / ANSI | Não há cor. Nenhum código de escape ANSI é emitido pelo Go. Logo, não há dependência de cor para entender o output. |
NO_COLOR |
Não é necessário tratar NO_COLOR: como já não há cor, a saída é estável independentemente da variável. |
| Texto sem cor | Status (ran/unavailable/…) e severidades (CRIT/HIGH/…) são rótulos textuais, nunca apenas cor. |
| Modo silencioso | --quiet oferece uma saída mínima/determinística para quem quer apenas o arquivo + exit code. |
| Logs estruturados | --log-format json oferece um fluxo machine-readable (JSONL) em stderr, útil para ferramentas assistivas e coletores que preferem dados a texto livre. |
| Unicode | O cabeçalho usa box-drawing (──) e reticências (…). Requer terminal UTF-8; o conteúdo informativo é ASCII. |
| Idioma das mensagens | As mensagens de runtime são em inglês (no código); há também uma versão inglesa desta documentação em 08-frontend.md (idioma default do site). |
Checklist de acessibilidade (validação contínua):
- [ ] Garantir que nenhuma informação dependa de cor (já satisfeito: sem ANSI).
- [ ] Manter rótulos textuais para severidade e status (não substituir por ícones-only).
- [ ] Manter o conteúdo de severidade/contagens em ASCII para terminais sem UTF-8.
- [ ] Manter
--quietcomo caminho previsível para automação e ferramentas assistivas. - [ ] Manter o envelope JSON de
--log-format jsonestável (ts/level/msg) para consumidores programáticos.
Proposta futura (claramente separada — não existe hoje). Se cores forem adicionadas algum dia, devem (a) respeitar
NO_COLORe--no-color, (b) só ativar quandostderrfor um TTY, e (c) nunca codificar significado apenas por cor. Nada disso está implementado atualmente.
10. Mensagens de erro e validação¶
A validação acontece cedo, em runScan, e erros
retornam pela cadeia RunE → main(), que imprime quorum: <err> em stderr
e sai com exit 2. Como o comando raiz usa SilenceUsage/SilenceErrors,
não há dump de help a cada erro — só a mensagem objetiva.
| Validação / erro | Mensagem | Exit |
|---|---|---|
--type inválido |
invalid --type "x" (want image\|repo\|k8s) |
2 |
--log-format inválido |
invalid --log-format "x" (want text\|json) |
2 |
--fail-on inválido |
invalid --fail-on "x" (want critical\|high\|medium\|low) |
2 |
--min-severity inválido |
invalid --min-severity "x" (want critical\|high\|medium\|low) |
2 |
--format inválido |
unknown format "x" (want sarif\|json\|xml) |
2 |
--advice-provider inválido |
invalid --advice-provider "x" (want none\|local\|remote) |
2 |
--fix inválido |
invalid --fix "x" (want off\|suggest) |
2 |
--advice-provider=remote sob --offline |
--advice-provider=remote is disabled by --offline …; use --advice-provider=local |
2 |
--advice-provider=remote sem consentir egress |
--advice-provider=remote sends your findings … re-run with --advice-allow-egress to consent … |
2 |
--advice-provider=remote sem API key |
--advice-provider=remote needs an API key in QUORUM_ADVICE_API_KEY |
2 |
--fix=suggest com --advice-provider=remote |
--fix is not allowed with --advice-provider=remote: it would upload file source … |
2 |
Alvo iniciando com - |
invalid target "-x": must not start with '-' (use "./-x" for a path) |
2 |
| Alvo (repo/k8s) acima do cap de tamanho | target "…" exceeds the N-byte size cap (raise or disable with QUORUM_MAX_TARGET_BYTES) |
2 |
QUORUM_MAX_TARGET_BYTES não-numérico |
invalid QUORUM_MAX_TARGET_BYTES "x" |
2 |
--baseline explícito mas inexistente |
baseline file not found: <path> |
2 |
| Falha ao carregar baseline/crosswalk | loading baseline: … / loading crosswalk: … |
2 |
Falha ao gravar --metrics |
writing metrics: … |
2 |
| Nº de argumentos errado | erro do Cobra (cobra.ExactArgs(1)) |
2 |
Scanner desconhecido em --scanners |
warning: unknown scanner "x" ignored (known: …) (warning, não erro) |
— |
Princípio: erros de configuração são fatais e explícitos (exit 2); situações degradadas mas recuperáveis (scanner ausente, OSV offline, scanner desconhecido na lista, modelo consultivo inacessível) são avisos que não interrompem o scan. Ver degradação graciosa em 09-backend.md quando aplicável.
Nota de segurança (as-is). Diversas validações de entrada aparecem na tabela porque são visíveis ao usuário no terminal: a recusa de alvos que começam com
-(defesa contra argument injection nos scanners downstream), o cap de tamanho de alvo (QUORUM_MAX_TARGET_BYTES, default 20 GiB — guarda anti-DoS contra apontar o Quorum para uma árvore patologicamente grande) e o gate de consentimento de egress para--advice-provider=remote(dados só saem do host com--advice-allow-egressexplícito).
11. GitHub Code Scanning como superfície visual¶
A representação visual mais rica do Quorum não é dele — é a UI de Code
Scanning do GitHub, alimentada pelo SARIF que o Quorum emite como formato
primário (internal/report/sarif.go).
flowchart LR
Q["quorum scan -f sarif -o results.sarif"] --> A["actions/upload-sarif\n(ou GitHub Action composite do Quorum)"]
A --> CS["GitHub Code Scanning"]
CS --> UI["Aba Security → Code scanning alerts\n(visual, dedup por fingerprint, anotações inline em PR)"]
O que o SARIF do Quorum carrega para essa UI:
| Campo SARIF | Conteúdo do Quorum | Efeito na UI do GitHub |
|---|---|---|
level |
error (CRIT/HIGH), warning (MED), note (resto) |
Severidade/ícone do alerta |
ruleId |
VulnID (CVE), CanonicalControl, RuleID ou correlationKey (fallback) |
Agrupamento por regra |
partialFingerprints["quorum/v1"] |
m.Fingerprint = sha256(correlationKey) |
Dedup estável de alertas entre execuções |
locations |
arquivo + região (linhas) por membro com Location.File |
Anotação inline no diff do PR / navegação para o ponto |
properties.detectedBy / detectionCount |
quais e quantos scanners detectaram | Evidência de consenso (visível ao expandir o alerta) |
properties.confidence |
score de consenso (2 casas) | Sinal de confiança |
properties.correlationKey / unmapped |
chave determinística e flag de não-mapeado | Rastreabilidade e triagem |
run.properties.scanners |
nome/status/versão de cada scanner | Transparência de cobertura no artefato |
partialFingerprintsé o que torna a UI utilizável ao longo do tempo: o GitHub usa esse fingerprint para reconhecer "o mesmo alerta" entre commits, evitando que cada execução crie alertas duplicados. Como o fingerprint do Quorum é determinístico (sha256(correlationKey)), o dedup é estável. A camada consultiva nunca toca esse fingerprint, então--advicenão perturba o dedup.
Checklist de integração com Code Scanning:
- [ ] Rodar
quorum scan … -f sarif -o results.sarif(SARIF é o default de--format). - [ ] Fazer upload via
github/codeql-action/upload-sarif(ou a Action composite do Quorum, que verifica a imagem com cosign antes e auto-monta/var/run/docker.sockem scan de imagem). - [ ] Confirmar permissão
security-events: writeno workflow. - [ ] Usar
--quiet(ou--log-format json) no passo de scan para manter o log do runner controlado. - [ ] Opcional: combinar com
--fail-onpara bloquear o PR via exit code, independentemente da UI.
JSON e XML existem como formatos alternativos de máquina (ver
json.go e xml.go), úteis para
ferramentas próprias — mas nenhum deles é uma "tela".
12. Como testar / verificar a UX¶
# 1) Summary + progresso completos, relatório SARIF no stdout
quorum scan alpine:3.18 --fail-on high
# 2) Saída limpa para CI: SARIF em arquivo, sem ruído, com gate
quorum scan alpine:3.18 -q -o results.sarif --fail-on critical
echo "exit=$?"
# 3) Confirmar separação de canais: stdout puro, stderr descartado
quorum scan . -f json 2>/dev/null | jq '.summary'
# 4) Logs estruturados (JSONL em stderr) para ingestão em coletor
quorum scan . --log-format json 2> quorum.log
jq -c 'select(.msg | startswith("done"))' quorum.log
# 5) Forçar erro de validação (exit 2) e ver a mensagem
quorum scan x --fail-on banana # → quorum: invalid --fail-on "banana" (...)
quorum scan x --log-format yaml # → quorum: invalid --log-format "yaml" (want text|json)
# 6) Inspecionar estados de scanner (restringindo o pool)
quorum scan . --scanners trivy,inexistente # → warning: unknown scanner ...
# 7) Descobrir os 12 scanners e o que cada um suporta
quorum list-scanners
# 8) Telemetria Prometheus em arquivo
quorum scan . --metrics quorum.prom && cat quorum.prom
# 9) Camada consultiva opt-in (saída byte-idêntica sem --advice)
quorum scan . --advice # remediação determinística + refs OWASP
quorum scan . --advice --advice-provider=local --fix=suggest # LLM local opt-in + patch verificado
Checklist de verificação manual:
- [ ]
stdoutcontém apenas o relatório (nenhum[quorum]nem JSON de log). - [ ]
stderrcontém o progresso e a tabela de summary quando sem--quiet. - [ ] Com
--log-format json, cada linha de progresso emstderré um objeto JSON válido (ts/level/msg); a tabela de summary segue em texto. - [ ] Com
--quiet,stderrfica vazio (exceto erro fatal), inclusive sob--log-format json. - [ ]
--fail-onproduzexit 1quando há achado no limiar;exit 0caso contrário. - [ ] Flags inválidas produzem
exit 2com mensagemquorum: …. - [ ] Cada scanner aparece no summary com um dos 5 status.
- [ ] Sem
--advice, a saída é byte-idêntica; com--advice, só aparecem linhasadvice*extras emstderre campos consultivos dentro do relatório.
Premissas¶
- Fonte da verdade é o código. Tudo aqui foi verificado em
cmd/quorum/{main.go,root.go,scan.go},internal/orchestrator/orchestrator.go,internal/adapter/*.go(12 adapters),internal/report/{report,sarif,json,metrics}.goeinternal/severity/severity.gona versão atual do repositório (v0.8.3). - Ausência de cor/ANSI foi inferida de uma busca por
color/NO_COLOR/isatty/IsTerminalno código Go, que não retornou implementação de coloração (a única ocorrência decolorestá emaction.yml, que é a cor do badge da Action no Marketplace, não saída de terminal). Se cor for adicionada no futuro, a seção de Acessibilidade precisará ser revista. - O exemplo de tabela de summary na §4 é
ilustrativo (valores fictícios), mas o layout e os nomes de scanner
seguem exatamente os
FprintfdeprintSummarye osName()dos adapters. --log-formatsó governa os logs de progresso da closurelogf. O summary (printSummary), o relatório (--format) e o erro fatal demain()são independentes de--log-format, conforme verificado no código.- Detalhes de degradação graciosa (OSV offline, cache de alias, crosswalk
fallback,
--metrics, modelo consultivo inacessível) são tocados aqui só onde afetam a UX de terminal; o tratamento completo pertence aos documentos de orquestração/alias/telemetria. - A camada consultiva é opt-in e desligada por padrão. Sem
--advice, a saída é byte-idêntica e o núcleo determinístico sem IA fica inalterado; só as Fases 1 e 3 envolvem um modelo, e ambas degradam graciosamente. Todo anexo de IA é rotulado "AI-generated, advisory only". - Mensagens de runtime estão em inglês no código; esta documentação é
pt-BR e traduz o significado, não o literal das strings emitidas. Há também
uma versão inglesa (idioma default) em
08-frontend.md. - Cross-links para outros documentos do diretório
docs/(ex.: alias, supply chain) usam o padrãoNN-arquivo.md; alguns alvos podem ainda não existir no momento da escrita deste arquivo.