Ir para o conteúdo

Melhorias e Recomendações

Este documento reúne sugestões de evolução para o Quorum (quorum-sec-scan, v0.8.3), priorizadas por eixo (Arquitetura, Segurança, UX, Performance, Custos, Escalabilidade, Qualidade, DevOps e Automação). Toda recomendação está ancorada no código real do repositório (arquivos e funções citados explicitamente), seguindo o princípio de produto de que "false split > false merge" e "0 findings não é prova de segurança". Cada item traz status → problema → recomendação → esforço → impacto, e o documento encerra com uma tabela priorizada por impacto × esforço, um roadmap sugerido e uma seção de premissas.

Revisão: 2026-07-04 · versão do produto v0.8.3 (documento originalmente escrito em v0.2.3 e agora atualizado). Entre essas duas versões o pool cresceu de 6 para 12 scanners (trivy, grype, checkov, kics, dockle, kubescape, polaris, kube-score, terrascan, tfsec, regula, conftest — adapters em internal/adapter/), o consenso passou a se aplicar além do SCA (crosswalk multi-cloud + k8s) e boa parte das recomendações abaixo foi implementada. Itens implementados são mantidos para rastreabilidade, com o marcador [IMPLEMENTADO v0.8.3]; itens parciais levam [PARCIAL] e os pendentes [ABERTO].

Escopo: o Quorum é uma ferramenta CLI/Docker de consensus security scanning. Itens que tocariam um frontend web, banco de dados relacional ou API REST de runtime são marcados como N/A com justificativa, e qualquer ideia nessa direção aparece como Proposta futura claramente separada. Desde a v0.8.3 também existe uma camada consultiva opt-in (--advice) — ela é apenas de apresentação e desligada por padrão, então o núcleo de consenso determinístico continua sem IA. Ver DESIGN.md e o README.


0. O que já foi entregue desde a v0.2.3 (resumo)

Estas recomendações do documento original já estão no código v0.8.3 — os detalhes ficam inline em cada eixo, com o marcador [IMPLEMENTADO v0.8.3]:

  • [x] Observabilidade de execução. --metrics <file> emite métricas em Prometheus text-format (report.WriteMetrics, internal/report/metrics.go) e --log-format text|json estrutura o log de progresso no stderr (scan.go, logf). (AU1, U1-parcial)
  • [x] Caps de DoS. QUORUM_MAX_OUTPUT_BYTES (padrão 512 MiB, internal/adapter/adapter.go capWriter/maxOutputBytes) aborta scanners que despejam uma saída gigante; QUORUM_MAX_TARGET_BYTES (padrão 20 GiB, scan.go checkTargetSize) recusa árvores patológicas antes do scan. (mitiga A1/A2)
  • [x] Cadeia de suprimentos endurecida. SLSA build-provenance e SBOM SPDX atestado (actions/attest-build-provenance, actions/attest-sbom) para a imagem e por binário (GoReleaser + syft), além do sbom: true do BuildKit; cosign keyless com retry; bases pinadas por @sha256:; kubescape/tfsec/terrascan/regula/conftest verificados por checksum; o knowledge pack + crosswalk agora também carregam uma atestação SLSA build-provenance a cada release (release.yml job knowledge); THIRD_PARTY_NOTICES.md. (S3/D2, S1-parcial, S2-parcial)
  • [x] Consenso multi-cloud e k8s. Crosswalk derivado de saída real cobre AWS/Azure/GCP (crosswalk/{aws,azure,gcp}.yaml, hub AVD) e Kubernetes (crosswalk/k8s.yaml, hub C-#### do kubescape); tfsec auto-correlaciona com trivy (AVD nativo). (cumpre a promessa E2)
  • [x] Auto-mount do socket Docker. A Action (action.yml) monta /var/run/docker.sock automaticamente em type: image, evitando o falso-zero ao escanear uma imagem local. (fecha uma classe de U4)
  • [x] Tag móvel v0 automatizada. tag-major.yml avança v0 a cada release semver. (D4)
  • [x] Endurecimento de I/O. --output com filepath.Clean e perm 0600; cache aliases.json 0600 + schemaVersion (internal/cache/store.go); id OSV validado + url.PathEscape (internal/alias/osv.go); target começando com - recusado (validateTargetRef); redação de segredos (redactSecretText); Grype DB com GRYPE_DB_VALIDATE_AGE=false (não expira).
  • [x] Camada consultiva opt-in (--advice). Uma camada apenas de apresentação que nunca toca correlationKey/fingerprint/confidence/severidade agregada ou o gate --fail-on (sem --advice a saída é byte-idêntica). Quatro fases, todas implementadas: Fase 0 — templates determinísticos e curados de remediação + referências OWASP (internal/enrich, knowledge/*.yaml); Fase 2 — RAG-as-artifact a partir de um corpus OWASP pinado por digest (knowledge/owasp/corpus.yaml, internal/rag), lexical por padrão, semântico quando embutido via quorum advise-index; Fase 1 — LLM local opt-in (--advice-provider=local) mais --fix=suggest guardado por um re-scan verify-the-fix (internal/advisor); Fase 3 — provider remoto opt-in (--advice-provider=remote, guardado por --advice-allow-egress). Novos evals (internal/evals) rodam no CI. (ver §11, 13-ia.md, 21-proposta-ia.md)
  • [x] Cobertura de testes no CI (ci.yml, go test -race -coverprofile) e docs no GitHub Pages (MkDocs Material, docs.yml).

Ainda abertos (detalhados abaixo): consenso de RBAC (single-engine — o RBAC do kubescape precisa de contexto de cluster), pin do Checkov pip com hashes, mais cobertura de crosswalk, um peer de consenso para o Dockle (postura de imagem sem um segundo engine), concorrência limitada (A1), --format table (U2), --require-scanners (U4), pin de actions por SHA (D1) e imagem non-root (S4).


1. Como ler este documento

Cada recomendação usa esta convenção:

  • Status: [IMPLEMENTADO v0.8.3], [PARCIAL] ou [ABERTO].
  • Esforço: S (pequeno, ≤ 1 dia), M (médio, 2–5 dias), L (grande, > 1 semana).
  • Impacto: Alto, Médio, Baixo — efeito sobre confiabilidade, segurança da cadeia, experiência de CI ou custo operacional.
  • Âncora de código: arquivo/função onde a mudança aterrissa.
quadrantChart
    title Prioritization impact x effort (open backlog v0.8.3)
    x-axis "Low effort" --> "High effort"
    y-axis "Low impact" --> "High impact"
    quadrant-1 "Plan"
    quadrant-2 "Do now (quick wins)"
    quadrant-3 "Backlog"
    quadrant-4 "Weigh cost/benefit"
    "Limit concurrency (A1)": [0.30, 0.82]
    "require-scanners (U4)": [0.18, 0.80]
    "Pin Checkov pip (S1)": [0.30, 0.78]
    "Pin actions by SHA (D1)": [0.20, 0.72]
    "Validate empty output (A2)": [0.40, 0.75]
    "NO_COLOR / TTY (U1)": [0.15, 0.45]
    "Batched cache flush (P1)": [0.25, 0.55]
    "--format table (U2)": [0.45, 0.60]
    "Concurrent alias (P3)": [0.55, 0.66]
    "RBAC consensus": [0.70, 0.62]
    "More crosswalk (E2)": [0.55, 0.68]
    "Non-root image (S4)": [0.45, 0.55]

2. Arquitetura

A1. Limitar a concorrência de scanners pesados (fan-out sem teto)

  • Status. [ABERTO] (mitigação parcial via caps de DoS). O orquestrador ainda faz fan-out sem teto.
  • Problema. orchestrator.Run (internal/orchestrator/orchestrator.go) ainda lança uma goroutine por adapter de uma vez (for _, a := range adapters { go func() {...} }, sem MaxConcurrency/semáforo). Com os 12 scanners do :full — agora incluindo três engines Python pesados (Checkov, KICS+assets e o próprio ecossistema IaC) — a pressão de memória num runner pequeno é maior do que na v0.2.3. O comentário defaultProbeTime = 60s ainda reconhece "while every scanner launches at once on a memory-constrained runner", e runOne ainda diagnostica OOM post-mortem (killedSignal, "version probe killed — likely out of memory"). O que mudou: os caps QUORUM_MAX_OUTPUT_BYTES/QUORUM_MAX_TARGET_BYTES já bloqueiam uma classe de OOM (saída/target gigante), mas não limitam o número de processos simultâneos.
  • Recomendação. Introduzir Options.MaxConcurrency (padrão = runtime.NumCPU(), configurável via flag --max-concurrency ou env QUORUM_MAX_CONCURRENCY) e usar um semáforo bufferizado (chan struct{}) ou golang.org/x/sync/errgroup com SetLimit. Tratar mitigação de OOM como controle de admissão, não apenas como diagnóstico post-mortem.
  • Esforço: S–M. Impacto: Alto (estabilidade do :full com 12 scanners em runners pequenos — o caso de uso primário de CI).

A2. Validar a saída de cada scanner antes de fazer parsing (contrato de runtime)

  • Status. [PARCIAL]. Truncamento por overflow já é detectado; JSON vazio ainda passa.
  • Problema. runCmd (internal/adapter/adapter.go) trata "exit != 0 mas com stdout" como sucesso (convenção findings-found), o que está correto. Avanço v0.8.3: capWriter sinaliza over quando a saída excede o cap e runCmd falha de forma limpa ("output exceeded N bytes — aborting to avoid OOM"), evitando fazer parsing de um dump truncado por overflow. Ainda aberto: um JSON sintaticamente válido mas vazio ({}) — ou um morto no meio do dump sem exceder o cap — produz 0 findings com status ran, indistinguível de "o scanner rodou e não encontrou nada".
  • Recomendação. Adicionar validação leve por adapter: (a) checar que a saída decodifica na estrutura esperada e que os campos-âncora existem (ex.: Results presente no Trivy); (b) quando o exit foi != 0 e o JSON não tem o formato esperado, marcar o run como error com uma mensagem clara em vez de ran/0 findings. Reaproveitar as fixtures de contrato (internal/adapter/testdata, realdata_test.go) como arquivos golden dessa validação — agora com 12 adapters cobertos.
  • Esforço: M. Impacto: Alto (evita o falso negativo silencioso — o anti-objetivo declarado do produto, DESIGN §14).

A3. Tornar o pipeline de enriquecimento streamable e observável

  • Status. [PARCIAL]. Métricas de pipeline já existem via --metrics; paralelizar o enriquecimento, não.
  • Problema. Correlator.Enrich (internal/correlate/correlate.go) percorre todos os findings num único loop sequencial e, para VULN, chama Alias.Canonical um id por vez, cada um podendo bater na rede (OSV). Resolução de alias, crosswalk e keying acontecem juntos. O que mudou: o reporter --metrics já expõe agregados por scanner e de consenso, fornecendo o ponto de instrumentação que faltava.
  • Recomendação. Separar "resolução de alias" (I/O-bound, paralelizável — ver P3) de "crosswalk + keying" (CPU puro, determinístico). Estender --metrics com contadores de resolução (cache vs OSV vs local) e unmapped.
  • Esforço: M. Impacto: Médio.

A4. Perfis de imagem (:sca, :iac, :k8s)

  • Status. [ABERTO]. Só existem :full e :slim.
  • Problema. Hoje há :full (todos os 12 scanners, linux/amd64) e :slim (orquestrador, amd64+arm64), ambos no GHCR. :full embute Python+Checkov+KICS, terrascan/tfsec/regula/ conftest, um Grype DB pré-cacheado etc., resultando numa imagem grande (DESIGN §12 "avoid the monster image"). Muitos pipelines só fazem SCA.
  • Recomendação. Adicionar variantes por perfil em release.yml (matrix) que embutam só o subconjunto necessário de binários, reaproveitando o mesmo Dockerfile.full parametrizado por stage. Cada perfil mapeia para o conjunto de adapters daquele tipo (SCA = trivy+grype; IaC = checkov+kics+terrascan+tfsec+regula+conftest; K8s = kubescape+polaris+kube-score).
  • Esforço: M. Impacto: Médio (custo de pull/storage no CI; ver Custos).

3. Segurança (da própria cadeia)

S1. Pinar TODAS as ferramentas do :full por digest/checksum imutável

  • Status. [PARCIAL]. Bases e a maioria dos scanners já pinados/verificados; Checkov (pip) ainda pendente.
  • Problema. Avanço v0.8.3: as bases do Dockerfile.full estão pinadas por @sha256: e kubescape, tfsec, terrascan, regula e conftest agora são verificados por um checksum fixo (além de Trivy/KICS já pinados na v0.2.3). Ainda aberto: o Checkov é instalado via pip install "checkov==..." sem --require-hashes, então a resolução transitiva do pip continua mutável — um pacote comprometido entra na trust boundary (DESIGN §12).
  • Recomendação.
  • [x] Substituir curl | sh por um artefato versionado + verificação por checksum para Grype/Syft/Kubescape/tfsec/terrascan/regula/conftest.
  • [ ] Para o Checkov, pinar checkov==<ver> com hashes (pip install --require-hashes + requirements.txt gerado por pip-compile --generate-hashes).
  • [x] Documentar re-resolução de digest (docker buildx imagetools inspect).
  • Esforço: S–M (restante). Impacto: Alto (integridade da cadeia distribuída aos usuários).

S2. Verificar os binários dos scanners com cosign no build (não apenas confiar no instalador)

  • Status. [PARCIAL]. Integridade por checksum coberta; procedência por assinatura upstream, não.
  • Problema. Avanço v0.8.3: o checksum fixo do build garante integridade dos binários baixados. Ainda aberto: Anchore (Grype/Syft) e Kubescape publicam suas próprias assinaturas cosign que o build não verifica — o gate de procedência upstream está ausente.
  • Recomendação. No stage de runtime do Dockerfile.full, após baixar cada binário, rodar cosign verify-blob/cosign verify com a identidade OIDC do projeto upstream, falhando o build se a verificação falhar. Complementa S1 (checksum = integridade; cosign = procedência).
  • Esforço: M. Impacto: Médio–Alto.

S3. Estender SLSA/atestação aos artefatos do GoReleaser (e ao knowledge pack)

  • Status. [IMPLEMENTADO v0.8.3].
  • O que foi feito. release.yml agora gera SLSA build-provenance e um SBOM SPDX atestado tanto para a imagem (actions/attest-build-provenance@v2, actions/attest-sbom@v2, anchore/sbom-action, mais sbom: true/provenance: true do BuildKit) quanto por binário no canal GoReleaser (anchore/sbom-action/download-syft + sboms: no .goreleaser.yaml), re-verificados ponta a ponta com gh attestation verify. Além disso, o knowledge pack + crosswalk agora ganham sua própria atestação SLSA build-provenance a cada release (o job knowledge em release.yml), verificável com gh attestation verify knowledge/owasp/corpus.yaml. A assimetria entre os canais de distribuição foi fechada.
  • Follow-up. Manter a paridade quando novos artefatos entrarem no release.
  • Esforço: — (feito). Impacto: Médio. (Ver Q4 / DevOps.)

S4. Endurecer a imagem em runtime (usuário non-root, capabilities, FS read-only)

  • Status. [ABERTO]. O Dockerfile.full ainda não declara USER (roda como root).
  • Problema. O Dockerfile.full termina como root, com docker-cli instalado (para scan de imagem). Rodar como root amplia o raio de dano se um scanner embutido tiver um RCE ao processar entrada hostil. Com 12 scanners a superfície de parsing cresceu.
  • Recomendação. Criar um usuário non-root e USER quorum; documentar rodar com --read-only, --cap-drop ALL e o mínimo de bind-mounts. Quando o socket do Docker for necessário para --type image, documentar o trade-off (a Action já auto-monta o socket — tornar explícito o contrato de confiança desse mount).
  • Esforço: M. Impacto: Médio.

S5. Auto-escanear o próprio Quorum no CI

  • Status. [PARCIAL]. Cobertura de testes no CI já roda; um scanner de vulnerabilidades sobre o próprio repo/imagem, não.
  • Problema. Avanço v0.8.3: ci.yml agora roda go test -race -covermode=atomic -coverprofile=coverage.out ./... e publica a cobertura no summary. Ainda aberto: o CI não roda govulncheck, golangci-lint, nem o próprio Quorum/Trivy contra o repositório/imagem. "A ferramenta de segurança que não escaneia a si mesma" continua sendo um sinal ruim.
  • Recomendação. Adicionar um step que rode Trivy/govulncheck contra o repo e a imagem, e faça upload do SARIF para o GitHub code scanning — dogfooding do próprio Quorum.
  • Esforço: S. Impacto: Médio. (Ver Q2 e Automação.)

S6. Consenso de RBAC (single-engine) — uma lacuna conhecida e documentada

  • Status. [ABERTO] (documentada como limitação do design atual).
  • Problema. O crosswalk de k8s (crosswalk/k8s.yaml) já cruza kubescape × polaris × kube-score para postura de workload (privilege-escalation, privileged, non-root, cpu/mem limits, probes, read-only-fs, linux-hardening, automount-SA, network-policy, host-network, host-PID/IPC, capabilities, secrets). Mas o RBAC continua single-engine: o consenso de RBAC do kubescape precisa de um contexto de cluster ativo (não derivável só de manifestos estáticos), então não há um segundo engine para corroborar — findings de RBAC saem sem multi-detecção.
  • Recomendação. Documentar explicitamente a lacuna no relatório (uma marca single-engine para a família RBAC) e, como proposta, avaliar um adapter com contexto de cluster opcional (--type cluster) que habilite o cross-check — mantendo o modo estático como padrão.
  • Esforço: M–L. Impacto: Médio (transparência do consenso de k8s).

4. UX (experiência de CI/linha de comando)

U1. Respeitar NO_COLOR e detecção de TTY na saída de progresso

  • Status. [PARCIAL]. O log de progresso já é estruturável (--log-format json); uma política de cor/TTY, não.
  • Problema. Avanço v0.8.3: --log-format text|json (scan.go, logf) permite emitir o progresso como JSON estruturado no CI, o que ajuda a separar o ruído de stderr dos 12 scanners. Ainda aberto: a saída humana (logf prefixo [quorum], printSummary) usa texto puro sem política de cor, e printSummary desenha box-drawing Unicode (──) que pode renderizar ilegível em terminais Windows legados/não-UTF.
  • Recomendação. Centralizar a saída humana atrás de um helper que: (a) honre NO_COLOR (https://no-color.org) e --no-color; (b) detecte se stderr é um TTY (golang.org/x/term.IsTerminal); (c) faça fallback para ASCII puro quando non-TTY ou NO_COLOR. Adotá-lo antes de introduzir cores.
  • Esforço: S. Impacto: Médio (legibilidade do log no CI e no Windows).

U2. Saída legível por humanos além de SARIF/JSON/XML (--format table)

  • Status. [ABERTO]. report.ParseFormat ainda aceita só sarif|json|xml.
  • Problema. O único resumo humano é o printSummary no stderr (contagem por severidade + status por scanner). Para uso interativo/local não há uma tabela legível de findings no stdout.
  • Recomendação. Adicionar --format table (e talvez markdown) que liste os MergedFindings com severity, detectedBy, detectionCount, confidence e localização — ótimo para comentários de PR e runs locais (habilita AU3).
  • Esforço: M. Impacto: Médio.

U3. Mensagens de erro acionáveis em mais pontos do fluxo

  • Status. [PARCIAL]. Alguns pontos melhoraram; a padronização geral, não.
  • Problema. Avanço v0.8.3: a probe já tem mensagens excelentes (OOM, start lento) e o crosswalk ganhou um fallback para /opt/quorum/crosswalk quando ./crosswalk está ausente (resolveCrosswalkDir), evitando o silencioso "0 rules loaded" ao rodar docker run … scan .. Ainda aberto: um crosswalk.Load que falha ainda retorna o cru "loading crosswalk: %w"; LoadBaseline retorna o erro cru.
  • Recomendação. Padronizar erros de runtime/uso (exit 2) com a tríade o que falhou → por quê → como corrigir, no estilo da probe. Ex.: um aviso de crosswalk vazio "0 rules loaded — misconfigs will be unmapped".
  • Esforço: S. Impacto: Médio.

U4. Aviso explícito quando nenhum scanner rodou

  • Status. [ABERTO] no CLI; parcialmente coberto na Action.
  • Problema. printSummary já imprime "0 findings is not proof of safety". Mas se todos os scanners terminarem unavailable/skipped/timeout, o relatório sai com 0 findings e exit 0. Avanço v0.8.3: a Action (action.yml) auto-monta /var/run/docker.sock em type: image e avisa quando o socket está ausente, fechando a causa mais comum do falso-zero de imagem local. Ainda aberto: não há um --require-scanners no CLI que transforme "0 scanners rodaram" em falha.
  • Recomendação. Quando count(status==ran) == 0, emitir um aviso destacado e oferecer uma flag opt-in --require-scanners (ou --fail-if-no-scanner) que retorne exit 2 — sem mudar o padrão.
  • Esforço: S. Impacto: Alto (corrige a classe mais perigosa de falso-negativo).

5. Performance

P1. Persistir o cache de aliases em lote (evitar reescrever o arquivo inteiro por chave)

  • Status. [ABERTO] (o endurecimento do cache aterrissou; o flush em lote não).
  • Problema. cache.Store.Put (internal/cache/store.go) ainda serializa e reescreve o JSON inteiro a cada chamada (snapshot completo → MarshalIndent → write-temp → rename). Num scan de imagem grande com centenas de CVEs novos é O(n²) em I/O. Avanço v0.8.3: o arquivo ganhou schemaVersion e perm 0600 (write-temp+rename atômico mantido) — mais robusto, mas ainda com flush-por-Put.
  • Recomendação. Adicionar um modo batched: acumular os Puts em memória e fazer um único flush ao final (Store.Flush() chamado em runScan após Enrich), mantendo flush-por-Put como fallback. Preserva a atomicidade do rename e o schemaVersion.
  • Esforço: S. Impacto: Médio.

P2. Reaproveitar a versão já obtida na probe (evitar Version() duas vezes)

  • Status. [ABERTO].
  • Problema. runOne chama a.Version(verCtx) na probe; depois vários adapters chamam Version de novo dentro de Run para estampar ScannerVersion. Dois exec do binário por scan — agora multiplicado por 12 scanners no :full.
  • Recomendação. Passar a versão já resolvida para Run (via Target/RunContext) ou cachear a versão por adapter dentro de uma execução.
  • Esforço: S. Impacto: Baixo–Médio.

P3. Resolver aliases de VULN de forma concorrente (com pool limitado)

  • Status. [ABERTO].
  • Problema. Enrich resolve aliases um id por vez e cada um pode bater no OSV (OSVClient.Aliases, timeout + retries com backoff; agora com id validado + url.PathEscape). Centenas de CVEs únicos não cacheados = centenas de round-trips sequenciais. Em --offline o ponto é irrelevante; online (padrão) é o maior gargalo do pipeline.
  • Recomendação. Deduplicar ids antes da resolução, resolver o conjunto único em paralelo com um pool limitado (respeitando o rate-limit do OSV) e mapear de volta. Combinar com P1 (flush único).
  • Esforço: M. Impacto: Médio–Alto (latência de scans online no CI).

P4. Reaproveitar o DB do Grype/Trivy entre runs

  • Status. [PARCIAL]. Resolvido no :full; orientação para :slim/binário nativo, não.
  • Problema. Avanço v0.8.3: o :full pré-cacheia o Grype DB em /opt/grype/db com GRYPE_DB_AUTO_UPDATE=false e GRYPE_DB_VALIDATE_AGE=false (o DB congelado não expira, então não força re-download num runner offline). Ainda aberto: no uso :slim/ binário-nativo cada runner pode re-baixar o DB, sem orientação na doc para montar/persistir esse cache.
  • Recomendação. Documentar (e talvez expor via flag/env) o reaproveitamento de GRYPE_DB_CACHE_DIR e do cache do Trivy DB montado como cache de CI.
  • Esforço: S (doc). Impacto: Médio (latência de cold-start).

6. Custos

C1. Reduzir o tamanho do :full (multi-stage agressivo / perfis)

  • Status. [ABERTO].
  • Problema. O :full carrega Python+pip+venv (Checkov), a stack IaC (kics, terrascan, tfsec, regula, conftest), git, docker-cli, tar/curl e o Grype DB congelado. Com 12 scanners a imagem cresceu; pesa no egress do GHCR e no tempo de pull a cada job.
  • Recomendação. (a) Remover ferramentas de build (curl, tar, headers pip) do stage final via multi-stage; (b) oferecer perfis (A4) para que pipelines de SCA não puxem a stack Python; (c) limpar *.pyc/--no-cache-dir.
  • Esforço: M. Impacto: Médio (custo recorrente de CI).

C2. Cachear layers de build no release (já parcialmente feito)

  • Status. [PARCIAL].
  • Problema. release.yml já usa cache-from/cache-to: type=gha. O custo restante é o grype db a cada build.
  • Recomendação. Cachear o Grype DB como layer estável (versão pinada) para que rebuilds sem bump não re-baixem; documentar a cadência de refresh (relaciona com AU4).
  • Esforço: S. Impacto: Baixo–Médio.

7. Escalabilidade

E1. Limitar e priorizar o fan-out por classe de scanner

  • Status. [ABERTO].
  • Problema. Mesmo com A1 (cap global), os 12 scanners têm perfis de recurso muito diferentes: Checkov/KICS (Python) são lentos para iniciar e pesados em memória; Dockle e kube-score são leves. Um único cap trata todos igual.
  • Recomendação. Permitir pesos por adapter (via Capabilities/metadata) e um scheduler que nunca rode dois scanners "pesados" ao mesmo tempo em runners pequenos. Começar simples: uma classe heavy|light e um cap separado para heavy.
  • Esforço: M. Impacto: Médio.

E2. Crosswalk como ativo escalável (validação + cobertura)

  • Status. [PARCIAL]. A cobertura cresceu muito; validação/automação de relatório, não.
  • Problema. Avanço v0.8.3: o crosswalk deixou de ser só-SCA e agora habilita o consenso multi-cloud e k8s, derivado de saída real com o viés "false split > false merge": crosswalk/{aws,azure,gcp}.yaml (hub AVD: S3/IAM/EBS/SG/RDS/KMS/CloudTrail/VPC-flow-logs, Azure Storage/Key Vault, GCP bucket/firewall/SQL) e crosswalk/k8s.yaml (hub C-#### do kubescape, cruzando kubescape × polaris × kube-score). tfsec auto-correlaciona com trivy (AVD nativo). Ainda aberto: a cobertura é dívida conhecida (DESIGN §14) e manter a consistência do YAML à mão não escala; faltam um validador de CI, um relatório de cobertura e a lacuna do Dockle (ver E3).
  • Recomendação. (a) Um validador de schema do crosswalk no CI (campos obrigatórios, IDs únicos, AVD/C-#### bem-formados); (b) um relatório de cobertura (% das regras de cada scanner com mapeamento, gerado das fixtures reais); (c) uma métrica unmapped exposta em --metrics.
  • Esforço: M. Impacto: Médio (qualidade do consenso MISCONFIG/K8S).

E3. Peer de consenso para o Dockle (postura de imagem single-engine)

  • Status. [ABERTO] (lacuna conhecida).
  • Problema. A postura de configuração de imagem (CIS Docker Benchmark) hoje é coberta apenas pelo Dockle — não há um segundo engine para corroborar esses findings, então eles saem sem multi-detecção (análogo ao RBAC em S6). Diferente de MISCONFIG/IaC e workload k8s, que já têm 2–3 engines cruzados.
  • Recomendação. Avaliar um peer para a família de postura-de-imagem (ex.: Trivy config/image checks ou hadolint para o Dockerfile) e mapeá-lo no crosswalk contra os controles do Dockle; até lá, marcar a família como single-engine no relatório.
  • Esforço: M. Impacto: Médio (transparência do consenso de imagem).

E4. Saída para targets com alto volume de findings

  • Status. [ABERTO].
  • Problema. Hoje report.Write monta tudo num bytes.Buffer antes de escrever (emit em scan.go). Para imagens com milhares de vulns, mantém o relatório inteiro em memória.
  • Recomendação. Streaming opcional do writer para o io.Writer de destino (JSON Lines / SARIF streaming) quando o volume justificar; manter o buffer como padrão.
  • Esforço: M. Impacto: Baixo (caso de cauda).

8. Qualidade

Q1. Cobertura de erro nos parsers (fuzz + golden de saída malformada)

  • Status. [ABERTO]. A base de testes de contrato cresceu para 12 adapters; entrada hostil falta.
  • Problema. Há testes de contrato por adapter contra fixtures reais (internal/adapter/testdata, realdata_test.go) — agora cobrindo os 12 scanners. Falta cobertura de entrada hostil/malformada (JSON truncado, campos ausentes, números fora de faixa).
  • Recomendação. Adicionar go test -fuzz nos parsers e golden tests com fixtures deliberadamente quebradas, pareados com a validação A2.
  • Esforço: M. Impacto: Médio.

Q2. golangci-lint e govulncheck no CI

  • Status. [PARCIAL]. Cobertura de testes aterrissou; um linter abrangente e vuln check, não.
  • Problema. Avanço v0.8.3: ci.yml roda go test -race -coverprofile e publica a cobertura. Ainda aberto: não há golangci-lint nem govulncheck das dependências Go.
  • Recomendação.
  • [ ] Adicionar golangci-lint (errcheck, staticcheck, gocritic, gosec).
  • [ ] Adicionar govulncheck ./... como step obrigatório.
  • [x] Reportar cobertura (go test -coverprofile) no summary.
  • Esforço: S. Impacto: Médio.

Q3. Teste determinístico ponta a ponta do consenso (além do e2e.yml)

  • Status. [ABERTO].
  • Problema.e2e.yml (consenso). Vale garantir que a matemática do consenso (confidence, detectionCount, aggregateSeverity) tenha table tests cobrindo os pesos do DESIGN §9 e os limites (clamp 0..1, log normalizado), agora com mais famílias de engine (consensus.scannerCategory cruzando 12 scanners).
  • Recomendação. Testes unitários explícitos para confidence(...) com casos-âncora (3 linters na mesma linha vs SCA+IaC concordando; kubescape+polaris+kube-score no mesmo controle k8s).
  • Esforço: S–M. Impacto: Médio.

Q4. Documentar e versionar o schema de saída (JSON/SARIF) como contrato

  • Status. [ABERTO].
  • Problema. O JSON é um "dump direto de []MergedFinding"; consumidores (DefectDojo, scripts) dependem desse formato. Não há versionamento de schema explícito além de partialFingerprints["quorum/v1"].
  • Recomendação. Publicar um JSON Schema da saída e um teste que falhe se o formato mudar sem um bump da versão do schema. Tratar a saída como uma API pública (mesmo sem REST).
  • Esforço: M. Impacto: Médio.

Q5. Evals da camada consultiva como gate de qualidade (entregue)

  • Status. [IMPLEMENTADO v0.8.3].
  • O que foi feito. O harness internal/evals mede cobertura determinística de remediação, relevância das referências OWASP e a taxa verify-the-fix, e roda no CI sem modelo pesado (Fase 0/Fase 2 são determinísticas, então o eval é reproduzível). Isso mantém a camada consultiva opt-in honesta sem acoplar o CI a um LLM externo.
  • Follow-up. Estender o corpus de eval conforme o knowledge pack cresce; conectar a taxa verify-the-fix à métrica quorum_advice_fix{stage=verified|proposed} para acompanhar tendência.
  • Esforço: — (feito). Impacto: Médio.

9. DevOps

D1. Pinar actions de terceiros por SHA

  • Status. [ABERTO].
  • Problema. Os workflows ainda usam tags móveis (actions/checkout@v4, actions/setup-go@v5, docker/build-push-action@v6, sigstore/cosign-installer@v3, actions/attest-*@v2, etc.). Tags de action são mutáveis — o mesmo risco de supply-chain que o produto combate (DESIGN §12), agora mais crítico porque o release faz atestação/assinatura.
  • Recomendação. Pinar cada uses: por @<sha40> e usar Dependabot/Renovate para bumps controlados. Consistente com a postura de segurança de cadeia do próprio Quorum.
  • Esforço: S. Impacto: Médio–Alto.

D2. SBOM dos binários no release (paridade com a imagem)

  • Status. [IMPLEMENTADO v0.8.3]. Ver S3 — sboms: no .goreleaser.yaml + attest-sbom por binário fecham a paridade com a imagem.
  • Follow-up. Manter a paridade ao adicionar novos artefatos de release.
  • Esforço: — (feito). Impacto: Médio.

D3. Verificação de reprodutibilidade / targets make completos

  • Status. [ABERTO].
  • Problema. O Makefile cobre test/vet/build/docker-full. Falta um único target de pré-release (lint + vuln + sbom local + smoke do :full) para reduzir surpresas no CI.
  • Recomendação. Um target make ci que reproduza o pipeline localmente, incluindo cosign verify da imagem recém-buildada (a Action já faz cosign-verify da imagem antes de rodar).
  • Esforço: S. Impacto: Baixo–Médio.

D4. Tag móvel v0 da Action: automatizar e proteger

  • Status. [IMPLEMENTADO v0.8.3].
  • O que foi feito. O workflow dedicado tag-major.yml avança a tag móvel v0 (major/minor) para o commit recém-lançado a cada release semver, separado do build da imagem. O README recomenda uses: Martinez1991/quorum-sec-scan@v0 (pinar por @<sha> em produção).
  • Follow-up. Documentar a política de pin no README/docs de release.
  • Esforço: — (feito). Impacto: Médio.

10. Automação

AU1. --metrics (telemetria de execução em formato consumível)

  • Status. [IMPLEMENTADO v0.8.3].
  • O que foi feito. --metrics <file> (scan.go, writeMetricsFilereport.WriteMetrics, internal/report/metrics.go) emite métricas em Prometheus text-format (para o textfile collector do node_exporter ou Pushgateway): por scanner (status, durationMs, findings, version — dados de ScannerRun), agregados de consenso (merged, multiDetected, bySeverity) e de pipeline. Complementado por --log-format text|json para o log de progresso. Sob --advice o reporter também emite as séries consultivas 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 verify-the-fix) — desligadas por padrão e ausentes sem --advice.
  • Follow-up. Estender com contadores de cache de alias (hits vs OSV) e unmapped (ver A3/E2).
  • Esforço: — (feito). Impacto: Médio (observabilidade de CI sem inventar um daemon).

AU2. Geração assistida de baseline (--write-baseline)

  • Status. [ABERTO].
  • Problema. Adotar --fail-on exige um baseline (.quorumignore) montado à mão a partir dos fingerprints do relatório (README "Baseline"). Fricção de adoção.
  • Recomendação. Uma flag --write-baseline <file> que escreva os fingerprints atuais (com comentários: título, severidade, scanners) para o usuário triar. Nunca suprime silenciosamente; só materializa o que já está no relatório.
  • Esforço: S–M. Impacto: Médio (adoção do gate de CI).

AU3. Decoração de PR / comentário automático

  • Status. [ABERTO].
  • Problema. A Action faz upload de SARIF (GitHub code scanning), mas não há um resumo legível no PR.
  • Recomendação. Um step opcional da Action que poste um comentário com o resumo do consenso (reaproveitando --format table/markdown de U2). Mantém o produto CLI-only; a automação vive na Action.
  • Esforço: M. Impacto: Médio.

AU4. Atualização agendada do DB de vulnerabilidades / rebuild do :full

  • Status. [ABERTO] (urgência reduzida por GRYPE_DB_VALIDATE_AGE=false).
  • Problema. O Grype DB no :full fica "congelado no momento do build"; sem rebuild ele envelhece. Avanço v0.8.3: GRYPE_DB_VALIDATE_AGE=false impede que o DB congelado quebre scans offline por expiração — mas ele de fato continua envelhecendo.
  • Recomendação. Um workflow agendado (cron) que rebuilde e re-publique o :full periodicamente (sem um novo semver), atualizando só o DB; assinatura/atestação seguem o mesmo fluxo (SLSA + SBOM atestado + cosign).
  • Esforço: M. Impacto: Médio.

AU5. Passthrough por scanner e auto-mount de socket (entregue)

  • Status. [IMPLEMENTADO v0.8.3].
  • O que foi feito. (a) QUORUM_<SCANNER>_ARGS (internal/adapter/adapter.go, extraArgs) permite ampliar cobertura ou passar uma chave de plataforma sem tocar o adapter — ex.: QUORUM_CHECKOV_ARGS="--bc-api-key <key>" destrava políticas Prisma Cloud/Bridgecrew através do Checkov OSS embutido; a Action expõe trivy-args/checkov-args/etc. (b) A Action (action.yml) auto-monta /var/run/docker.sock em type: image (input docker-socket para forçar/desabilitar), eliminando o falso-zero ao escanear uma imagem buildada localmente, e faz cosign-verify da imagem antes de rodar. A Action agora também expõe todos os inputs consultivos (advice, advice-provider, advice-endpoint, advice-model, advice-embed-model, advice-max, advice-cache, advice-allow-egress, advice-api-key, fix), auto-adicionando host-gateway para local e encaminhando a API key via env para remote.
  • Follow-up. Nenhum imediato.
  • Esforço: — (feito). Impacto: Médio–Alto (cobertura e correção de falso-zero).

AU6. quorum advise-index — embedding semântico do corpus OWASP (entregue)

  • Status. [IMPLEMENTADO v0.8.3].
  • O que foi feito. O novo subcomando quorum advise-index faz embedding do corpus OWASP pinado por digest (knowledge/owasp/corpus.yaml, internal/rag), preservando o pin, para que a recuperação da Fase 2 possa rodar semântica (embeddings) em vez da busca lexical padrão — scan escolhe semântica automaticamente quando o corpus carrega vetores. A recuperação continua determinística e RAG-as-artifact (sem modelo no momento do scan para lexical), e a atestação do corpus permite aos consumidores verificar o pack com gh attestation verify knowledge/owasp/corpus.yaml.
  • Follow-up. Publicar um artefato de corpus embutido por release para que os usuários tenham recuperação semântica sem rodar advise-index localmente.
  • Esforço: — (feito). Impacto: Médio.

11. Itens marcados N/A (com justificativa)

Esta fronteira arquitetural fica em grande parte inalterada da v0.2.3 à v0.8.3: o produto cresceu em profundidade (mais scanners, consenso multi-cloud/k8s, uma cadeia de suprimentos endurecida, uma camada consultiva opt-in), não em superfície.

Tema do template Status Justificativa
Frontend web / SPA N/A O Quorum é só CLI/Docker ("no panel, no daemon", README). Não há UI.
Banco de dados relacional N/A A persistência é apenas o cache JSON de aliases (internal/cache, schemaVersion+0600) e o cache on-disk de advice (chaveado por fingerprint+provider+model). Sem RDBMS por design (DESIGN §7 evita CGO/DB).
API REST HTTP de runtime N/A A integração é por exit code (0 ok / 1 gate / 2 error) + SARIF/JSON/XML no CI. A rede de saída é o OSV.dev (id validado + PathEscape) e, só sob --advice-provider, um endpoint compatível com OpenAI (local por padrão; remote guardado por --advice-allow-egress).
Autenticação / contas N/A Sem multiusuário ou sessão. A confiança vem de cosign/SLSA/SBOM atestado (e o knowledge pack atestado) na distribuição.
IA / LLM no núcleo N/A (camada consultiva opt-in existe, desligada por padrão) O núcleo de consenso é determinístico (correlationKey, a fórmula de confidence, crosswalk YAML) — sem ML. As partes de IA são a camada consultiva apenas de apresentação (--advice, Fases 1/3), desligada por padrão e nunca tocando o score ou o gate; o OWASP LLM Top 10 aplica-se apenas quando essa camada está habilitada. Ver 13-ia.md e 21-proposta-ia.md.
Orquestração K8s de runtime (Falco/Tetragon) N/A (futuro separado) Um modelo de stream, não um scan estático — um produto separado (DESIGN §2, §13).

Proposta futura (claramente separada): se algum dia houver demanda por agregação centralizada de resultados entre muitos pipelines, o caminho coerente com o design seria um consumidor externo do SARIF/JSON (ex.: DefectDojo), não embutir um banco/API no Quorum. O fingerprint portável (quorum/v1) foi projetado exatamente para isso (DESIGN §11).


12. Tabela priorizada (impacto × esforço)

Ordenada por prioridade sugerida. Itens concluídos na v0.8.3 ficam no fim, para rastreabilidade.

# Eixo Recomendação Esforço Impacto Status Prioridade
A1 Arquitetura Limitar concorrência de scanners (semáforo/errgroup) S–M Alto Aberto P0
U4 UX Aviso/flag quando nenhum scanner rodou (--require-scanners) S Alto Aberto P0
A2 Arquitetura Validar saída (JSON vazio) antes do parsing M Alto Parcial P0
S1 Segurança Pinar o Checkov pip com hashes (--require-hashes) S–M Alto Parcial P0
D1 DevOps Pinar actions de terceiros por SHA S Médio–Alto Aberto P1
Q2 Qualidade golangci-lint + govulncheck no CI S Médio Parcial P1
S5 Segurança Auto-scan (dogfooding: trivy/govulncheck) no CI S Médio Parcial P1
P1 Performance Cache de alias com flush em lote S Médio Aberto P1
U1 UX NO_COLOR/TTY antes de qualquer cor S Médio Parcial P1
P3 Performance Resolução concorrente de alias + dedup M Médio–Alto Aberto P1
U2 UX --format table/markdown M Médio Aberto P2
U3 UX Mensagens de erro acionáveis (crosswalk vazio, baseline) S Médio Parcial P2
AU2 Automação --write-baseline S–M Médio Aberto P2
P2 Performance Reaproveitar a versão da probe (não chamar Version 2x) S Baixo–Médio Aberto P2
S2 Segurança Cosign-verify de binários upstream no build M Médio–Alto Parcial P2
S4 Segurança Imagem non-root + hardening de runtime M Médio Aberto P2
S6 Segurança Consenso de RBAC (2º engine / contexto de cluster) M–L Médio Aberto P2
E3 Escalabilidade Peer de consenso para o Dockle (postura de imagem) M Médio Aberto P2
A4/C1 Arquitetura/Custos Perfis de imagem (:sca/:iac/:k8s) + redução de tamanho M Médio Aberto P2
E2 Escalabilidade Validador de crosswalk + cobertura no CI M Médio Parcial P2
Q1 Qualidade Fuzz/golden de saída malformada nos parsers M Médio Aberto P2
Q3 Qualidade Table tests da fórmula de consenso S–M Médio Aberto P2
Q4 Qualidade JSON Schema versionado da saída M Médio Aberto P2
AU4 Automação Rebuild agendado do :full (DB fresco) M Médio Aberto P2
AU6+ Automação Publicar um corpus OWASP embutido por release S Médio Aberto P3
P4 Performance Reuso documentado do DB Grype/Trivy no CI (:slim) S Médio Parcial P3
AU3 Automação Decoração de PR via Action M Médio Aberto P3
E1 Escalabilidade Pesos por classe de scanner (heavy/light) M Médio Aberto P3
A3 Arquitetura Separar/observar o enriquecimento M Médio Parcial P3
E4 Escalabilidade Writer em streaming para relatórios enormes M Baixo Aberto P3
C2 Custos Cache do layer do Grype DB no build S Baixo–Médio Parcial P3
D3 DevOps Target make ci de pré-release S Baixo–Médio Aberto P3
Concluídos na v0.8.3
AU1 Automação --metrics (Prometheus textfile) + --log-format + séries de advice M Médio Feito
AU5 Automação Passthrough QUORUM_<SCANNER>_ARGS + auto-socket + inputs consultivos M Médio–Alto Feito
AU6 Automação quorum advise-index (corpus OWASP semântico) M Médio Feito
Q5 Qualidade Evals da camada consultiva no CI (internal/evals) M Médio Feito
S3/D2 Segurança/DevOps SLSA + SBOM SPDX atestado (imagem + binários + knowledge pack) S–M Médio Feito
D4 DevOps Automatizar a tag móvel v0 (tag-major.yml) S Médio Feito
Produto Camada consultiva opt-in (--advice, Fases 0–3) L Alto Feito
Segurança Caps de DoS (MAX_OUTPUT_BYTES/MAX_TARGET_BYTES) S Médio Feito
Segurança Consenso multi-cloud/k8s (crosswalk aws/azure/gcp/k8s) L Alto Feito

13. Roadmap sugerido por release

O bloco entregue reúne o que já está na v0.8.3; os blocos seguintes são backlog, não comportamento atual.

flowchart LR
    subgraph delivered["Delivered through v0.8.3"]
        m1["AU1 --metrics + --log-format"]
        m2["AU5 args passthrough + auto-socket"]
        m3["S3/D2 SLSA + attested SBOM"]
        m4["DoS caps (bytes)"]
        m5["Multi-cloud/k8s consensus"]
        m6["D4 moving v0 tag"]
        m7["Advisory layer --advice (0-3)"]
        m8["AU6 advise-index + Q5 evals"]
    end
    subgraph v0_9["v0.9 (stability + gate)"]
        a1["A1 limit concurrency"]
        u4["U4 require-scanners"]
        a2["A2 validate empty output"]
        s1["S1 pin Checkov pip (hashes)"]
        d1["D1 pin actions SHA"]
    end
    subgraph v0_10["v0.10 (supply chain++ / consensus)"]
        s2["S2 cosign upstream"]
        s4["S4 non-root image"]
        s6["S6 RBAC consensus"]
        e3["E3 Dockle peer"]
        e2["E2 crosswalk coverage in CI"]
    end
    subgraph v1_0["v1.0 (stable contract)"]
        a4["A4 image profiles"]
        q4["Q4 versioned schema"]
        u2["U2 --format table"]
        au3["AU3 PR decoration"]
    end
    delivered --> v0_9 --> v0_10 --> v1_0

Premissas

  • A versão atual analisada é a v0.8.3 (revisão 2026-07-04); as afirmações refletem o código no estado atual do repositório (branch main), em particular: internal/orchestrator/orchestrator.go, cmd/quorum/scan.go, cmd/quorum/root.go, cmd/quorum/advise_index.go, internal/adapter/{adapter.go,trivy.go,conftest.go,…} (12 adapters), internal/cache/store.go, internal/alias/{osv.go,resolver.go}, internal/correlate/correlate.go, internal/report/metrics.go, internal/{enrich,rag,advisor,evals}, knowledge/{aws,azure,gcp,k8s,image,categories}.yaml, knowledge/owasp/corpus.yaml, crosswalk/{aws,azure,gcp,k8s}.yaml, Dockerfile.full, action.yml, .github/workflows/{ci,release,tag-major}.yml, .goreleaser.yaml.
  • Os marcadores [IMPLEMENTADO v0.8.3] / [PARCIAL] / [ABERTO] foram atribuídos pela leitura do código tal como está: por exemplo, --metrics/--log-format existem em scan.go; os caps de DoS existem em adapter.go/scan.go; a camada consultiva (--advice, Fases 0–3) existe em internal/{enrich,rag,advisor} e é desligada por padrão; o fan-out em orchestrator.Run ainda não tem cap de concorrência; report.ParseFormat ainda aceita só sarif|json|xml; ci.yml roda cobertura mas não govulncheck/golangci-lint; Dockerfile.full não declara USER; o Checkov é instalado por pip install sem --require-hashes.
  • "Esforço" é uma estimativa de engenharia relativa, sem contabilizar review/QA externos; serve para priorização, não para planejamento preciso de sprint.
  • Assumi que o objetivo do produto permanece só CLI/Docker (sem painel, daemon, API REST ou banco relacional), e que o núcleo determinístico continua sem IA — a camada consultiva é opt-in e desligada por padrão (--advice), apenas de apresentação, e nunca afeta o score ou o gate. Conforme README/DESIGN, recomendações rumo a painel/daemon/RDBMS foram tratadas como N/A ou propostas futuras externas. Essa fronteira permanece inalterada da v0.2.3 à v0.8.3.
  • Assumi que o público primário é CI/CD (gating por exit code, consumo de SARIF), o que pesa na priorização de estabilidade em runners pequenos, segurança de cadeia e UX de log.
  • Não rodei os scanners nem o pipeline completo; as conclusões de performance (P1–P4) derivam de leitura de código (ex.: cache.Store.Put reescrevendo o snapshot completo a cada chamada; Version chamado na probe e em Run; resolução sequencial de aliases em Enrich), não de profiling empírico.
  • A ausência de cores hoje (U1) é uma observação de código (printSummary/logf usam texto puro, agora com --log-format json disponível); o item de cor/TTY fica preventivo para terminais legados.
  • Os nomes de flag propostos (--max-concurrency, --require-scanners, --format table, --write-baseline, --no-color) são sugestões; os definitivos devem seguir a convenção cobra existente em scan.go (onde --metrics, --log-format, --min-severity, --advice, etc. já vivem).

Ver também

  • DESIGN.md — modelo de dados, matriz de correlação (§6), matemática do consenso (§9), cadeia de suprimentos (§12), status dos scanners (§14).
  • README.md — uso, exit codes, baseline, distribuição assinada.
  • THIRD_PARTY_NOTICES.md — inventário de licenças das ferramentas embutidas.