Ir para o conteúdo

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 stdout e todo o ruído (logs de progresso e tabela de summary) vai para stderr, quorum scan img -f json > report.json produz 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 para stderr). Isso também vale sob --advice: o progresso consultivo e eventuais anexos de IA saem pelos mesmos canais, e sem --advice a 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 closure logf. Ele não altera: (a) a tabela de summary (printSummary sempre imprime texto legível — ver §4); (b) o relatório em stdout (governado por --format); nem (c) a linha de erro fatal de main() (quorum: <err>). Para uma saída 100% estruturada de máquina, combine --log-format json com --quiet? Não--quiet desliga os logs por completo; escolha um ou outro conforme a necessidade (logs estruturados ou silêncio total).

level é sempre info. No código atual todos os eventos de progresso — inclusive warning: unknown scanner …, skip … e gate: … — são emitidos pela mesma logf e serializados com "level":"info". A severidade semântica do evento vive no texto de msg, não no campo level. Consumidores que queiram filtrar por gravidade devem casar no conteúdo de msg.

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-severity descartam achados, o número é registrado em stderr. 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 brutos vs apó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 com DetectionCount > 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 o checkov têm cold start lento e podem ser SIGKILLed em runners com pouca memória). É esse probe que distingue unavailable/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 vira unavailable e 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-on com a severidade-limite (critical|high|medium|low).
  • [ ] Considere --min-severity para remover ruído abaixo de um piso (afeta relatório e gating).
  • [ ] Trate exit 2 como erro de configuração/infra (não como "achados encontrados").
  • [ ] Não confunda exit 0 com "seguro" — confira os status de scanner (unavailable/timeout mascaram cobertura).
  • [ ] Em runners com pouca RAM, se vir unavailable (OOM), aumente a memória do container ou restrinja --scanners.

Cuidado com exit 0. Um exit 0 só garante que nenhum achado atingiu o limiar; não garante que todos os scanners rodaram. Um scanner unavailable/timeout pode ter deixado uma classe inteira de achados de fora. A nota 0 findings is not proof of safety existe 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 writeMetricsFilereport.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 adicionalmente quorum_advice_enriched{kind=remediation|references|recommendation}, quorum_advice_provider{provider} e quorum_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:

  1. Os logs de progresso (a closure logf retorna no início, seja em text ou json).
  2. A tabela de summary (printSummary retorna no início se quiet).

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> com exit 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 --quiet por --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 --quiet como caminho previsível para automação e ferramentas assistivas.
  • [ ] Manter o envelope JSON de --log-format json está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_COLOR e --no-color, (b) só ativar quando stderr for 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 RunEmain(), 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-egress explí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 --advice nã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.sock em scan de imagem).
  • [ ] Confirmar permissão security-events: write no workflow.
  • [ ] Usar --quiet (ou --log-format json) no passo de scan para manter o log do runner controlado.
  • [ ] Opcional: combinar com --fail-on para 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:

  • [ ] stdout contém apenas o relatório (nenhum [quorum] nem JSON de log).
  • [ ] stderr contém o progresso e a tabela de summary quando sem --quiet.
  • [ ] Com --log-format json, cada linha de progresso em stderr é um objeto JSON válido (ts/level/msg); a tabela de summary segue em texto.
  • [ ] Com --quiet, stderr fica vazio (exceto erro fatal), inclusive sob --log-format json.
  • [ ] --fail-on produz exit 1 quando há achado no limiar; exit 0 caso contrário.
  • [ ] Flags inválidas produzem exit 2 com mensagem quorum: ….
  • [ ] Cada scanner aparece no summary com um dos 5 status.
  • [ ] Sem --advice, a saída é byte-idêntica; com --advice, só aparecem linhas advice* extras em stderr e campos consultivos dentro do relatório.

Premissas

  1. 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}.go e internal/severity/severity.go na versão atual do repositório (v0.8.3).
  2. Ausência de cor/ANSI foi inferida de uma busca por color/NO_COLOR/ isatty/IsTerminal no código Go, que não retornou implementação de coloração (a única ocorrência de color está em action.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.
  3. O exemplo de tabela de summary na §4 é ilustrativo (valores fictícios), mas o layout e os nomes de scanner seguem exatamente os Fprintf de printSummary e os Name() dos adapters.
  4. --log-format só governa os logs de progresso da closure logf. O summary (printSummary), o relatório (--format) e o erro fatal de main() são independentes de --log-format, conforme verificado no código.
  5. 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.
  6. 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".
  7. 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.
  8. Cross-links para outros documentos do diretório docs/ (ex.: alias, supply chain) usam o padrão NN-arquivo.md; alguns alvos podem ainda não existir no momento da escrita deste arquivo.