Skip to content

Non-Functional Requirements

This document specifies the Non-Functional Requirements (NFRs) of Quorum (quorum-sec-scan), version v0.8.3 (revision 2026-07-04). Quorum is a consensus security scanning tool distributed as a CLI and Docker image — no web frontend, no database, no REST API, and no user authentication. It orchestrates a pool of 12 OSS scanners (Trivy, Grype, Checkov, KICS, Dockle, Kubescape, Polaris, kube-score, Terrascan, tfsec, Regula and Conftest), normalizes everything to a canonical model (model.Finding), resolves vulnerability aliases, correlates equivalent findings, computes a (consensus) confidence score, and emits SARIF/JSON/XML.

The deterministic core has no AI. As of v0.8.3 there is an opt-in advisory layer (enabled with --advice) that is presentation-only: it never touches correlationKey, Fingerprint, confidence, aggregated severity, or the --fail-on gate. Without --advice the output is byte-identical to earlier releases, and every AI attachment is labeled "AI-generated, advisory only". See 13-ia.md and 21-proposta-ia.md for the full framing; its impact on the NFRs (determinism, egress, supply chain) is called out inline below.

Being an ephemeral binary/image (not a long-running service), several classic SaaS NFRs do not apply directly. Where that is the case, the requirement is explicitly declared N/A with a technical justification and — when it adds value — accompanied by a clearly separated Future proposal. The product's architectural nature is described in 01-visao-geral.md and 04-arquitetura.md; the data model, the correlation matrix and the consensus math are in DESIGN.md.

NFR criticality convention: P0 = essential (release blocks if violated), P1 = important, P2 = desirable.


1. NFR Overview

mindmap
  root((Quorum NFRs))
    Performance
      Parallel fan-out 12 scanners
      Per-scanner timeout
      Version probe 60s
      Pre-cached Grype DB
      DoS caps (output/target)
    Scalability
      Stateless CLI
      Horizontal scale on runners
    Availability
      N/A service
      GHCR/Releases distribution
    Security
      Supply chain
      cosign-signed images
      SLSA provenance
      Attested SPDX SBOM
      Knowledge-pack attestation
      sha256 pinning
      No secrets
    Observability
      stderr logs text/json
      Prometheus metrics
      Per-scanner status
    Auditability
      Report as evidence
      Fingerprints
    Compliance
      LGPD
      PCI DSS
      ISO 27001
      OWASP ASVS / Top 10
    Resilience
      Graceful degradation
      cosign retry
      Grype DB never expires
    Continuity
      Stateless
      Reproducible artifacts
# Category Applicability Priority Summary
2 Performance Applicable P0 Parallelism, timeouts, pre-cached DB, DoS caps
3 Scalability Applicable P1 Stateless CLI, horizontal scale in CI
4 Availability Reinterpreted P1 Not a service; distribution matters
5 Security Applicable P0 Supply chain, signatures, SBOM, no secrets
6 Observability Applicable P1 stderr logs (text/json), Prometheus metrics, per-scanner status
7 Auditability Applicable P1 Report as evidence, fingerprints
8 LGPD Applicable (by exclusion) P1 No PII processed
9 PCI DSS N/A as a product P2 Can support compliance
10 ISO 27001 Partial mapping P2 Controls applicable to the binary/pipeline
11 OWASP ASVS CLI subset P1 Controls relevant to a CLI
12 OWASP Top 10 Applicable to the artifact itself P0 Risk of the binary/image, not of the target
13 Resilience Applicable P0 Graceful degradation
14 Backup/DR/RTO/RPO N/A (stateless) P2 Reproducible artifacts
15 SLA/SLO/SLI Defined P1 Use in CI and release pipeline

2. Performance

Quorum is dominated by I/O and by subprocesses (each scanner is an external binary invoked via exec). The wall-clock time of a scan is, in practice, max(time of each scanner) + correlation/consensus overhead, because the adapters run in parallel. The opt-in advisory layer (--advice) runs after the deterministic pipeline and is off by default, so it never enters this critical path unless explicitly requested.

2.1 Parallelism (fan-out)

The orchestrator fires one goroutine per adapter and aggregates the results under a mutex (internal/orchestrator/orchestrator.go, Run). There are 12 adapters registered in internal/adapter (trivy, grype, checkov, kics, dockle, kubescape, polaris, kube-score, terrascan, tfsec, regula, conftest); only those that support the target type enter the fan-out:

flowchart LR
  T[target] --> O[Orchestrator.Run]
  O -->|goroutine| A1[trivy.Run]
  O -->|goroutine| A2[grype.Run]
  O -->|goroutine| A3[checkov.Run]
  O -->|goroutine| A4[kics.Run]
  O -->|goroutine| A5[dockle.Run]
  O -->|goroutine| A6[kubescape.Run]
  O -->|goroutine| A7[polaris.Run]
  O -->|goroutine| A8[kube-score.Run]
  O -->|goroutine| A9[terrascan.Run]
  O -->|goroutine| A10[tfsec.Run]
  O -->|goroutine| A11[regula.Run]
  O -->|goroutine| A12[conftest.Run]
  A1 & A2 & A3 & A4 & A5 & A6 & A7 & A8 & A9 & A10 & A11 & A12 --> W[wg.Wait + mutex aggregates]
  W --> C[Correlator.Enrich]
  C --> M[consensus.Merge]
  M --> R[Reporter SARIF/JSON/XML]

Performance implications:

  • Scaling is bounded by the slowest scanner, not by the sum. Adding a fast scanner to a set that already contains a slow one does not change the total time much. With 12 scanners, the typical bottleneck is still the heavy Python/heavy processes (Checkov, KICS) and not the number of adapters itself.
  • The correlation/consensus phase is in-process and CPU-light (grouping by correlationKey in maps, sha256 per key) — negligible compared to the scanners.
  • Alias resolution can add network latency (OSV.dev). It is mitigated by a local cache (~/.cache/quorum/aliases.json) and can be disabled with --offline.

2.2 Per-scanner timeout

Two distinct time budgets protect the pipeline against hangs:

Parameter Default Where Effect on expiry
--timeout (PerScannerTime) 5m cmd/quorum/scan.go, Options.PerScannerTime Scanner marked timeout; the others continue
ProbeTime (version probe) 60s orchestrator.defaultProbeTime Scanner marked unavailable with diagnostics

The 60s version probe is deliberately generous: heavy tools (e.g. Checkov, a Python process) are slow to start cold, especially when all 12 scanners start at the same time on a memory-starved runner. The orchestrator distinguishes three probe failures and emits an actionable message:

  • probe timeout → "tool too slow to start or resource-starved (give the container more memory, or scope --scanners)";
  • killed (OOM)signal: killed → "likely out of memory; raise the container's memory limit";
  • not installed → "not installed/available".

An execution timeout never brings down the whole scan: the scanner's status becomes timeout/error and the report records that explicitly — "0 findings is not proof of safety".

2.3 Pre-cached Grype DB

The :full image (Dockerfile.full) freezes Grype's vulnerability base at build time:

ENV GRYPE_DB_CACHE_DIR=/opt/grype/db \
    GRYPE_DB_AUTO_UPDATE=false \
    GRYPE_DB_VALIDATE_AGE=false
RUN grype db update && grype db status

Direct performance and resilience benefits:

  • The first scan works offline and does not fail with "failed to load vulnerability db: database does not exist".
  • It removes the DB download from the critical path of every CI job.
  • GRYPE_DB_VALIDATE_AGE=false is mandatory here: without it Grype would refuse a DB older than 5 days (db.max-allowed-built-age) and fail every scan a few days after the build. The baked DB is accepted by design (it never expires); updating it means a rebuild or GRYPE_DB_AUTO_UPDATE=true at runtime (requires network).
  • Documented trade-off: the DB is frozen at build date — freshness is a supply-chain choice, not a crash.

2.4 DoS caps (resource limits)

Two limits protect the Quorum process against pathological targets/outputs (R2/R5), independently of scanner reliability:

Cap Default Override env Behavior on expiry Where
Stdout per scanner 512 MiB QUORUM_MAX_OUTPUT_BYTES (bytes) aborts the adapter with an error (avoids OOM while buffering) internal/adapter/adapter.go, runCmd/capWriter
Target size (repo/k8s) 20 GiB QUORUM_MAX_TARGET_BYTES (bytes; 0 disables) refuses the scan early with an actionable message cmd/quorum/scan.go, checkTargetSize
  • The output cap uses a capWriter that cuts the read as soon as the ceiling is crossed — a scanner that dumps gigabytes of JSON does not bring the process down.
  • The target cap does a filepath.WalkDir with early-abort: as soon as the sum of regular files exceeds the ceiling, the walk stops (normal repos pay only a light stat pass). Image-type targets (no local tree) are skipped.

2.5 Performance goals (product SLO)

Target goals; not yet validated by a formal benchmark (see Gaps).

Metric Goal Condition Priority
Orchestrator overhead (correlation+consensus+report) ≤ 2s ≤ 50k raw findings P1
Probe time for all scanners ≤ 60s :full image warm P0
Alias latency (cache hit) ~0 (no network) idempotent re-scan P1
Total SCA scan time (:full, average image) ≤ slowest scanner time + 2s 2 vCPU / 4 GB runner P1
Orchestrator peak memory (excluding scanners) ≤ 256 MB ≤ 50k findings + DoS caps active P2

Performance checklist:

  • [ ] Run with --offline in CI when the OSV network is not desired on the critical path.
  • [ ] Restrict --scanners to what is needed on small runners (12 scanners on :full).
  • [ ] Persist ~/.cache/quorum/aliases.json across CI jobs.
  • [ ] Give the :full container enough memory (Checkov/KICS are heavy).
  • [ ] Tune QUORUM_MAX_OUTPUT_BYTES/QUORUM_MAX_TARGET_BYTES only if a legitimate target exceeds the defaults.
  • [ ] Keep --advice off (default) on the critical path; the advisory layer is post-processing and optional.

3. Scalability

3.1 Stateless CLI

Each Quorum execution is self-contained and without shared state:

  • No state is persisted between runs beyond optional caches (on-disk aliases, and — only under --advice — the advice cache keyed by fingerprint+provider+model) that are idempotent and rebuildable.
  • There is no server, session, queue or cross-process coordination.
  • The output (SARIF/JSON/XML) is a deterministic function of the inputs (target + scanners + crosswalk + baseline), with pure correlationKey/Fingerprint. The advisory layer does not change this: with --advice off the output is byte-identical, and even with it on the correlation and consensus fields are untouched.

3.2 Horizontal scale on CI runners

flowchart TB
  subgraph CI[Pipeline / CI fleet]
    R1[Runner 1\nquorum scan repoA]
    R2[Runner 2\nquorum scan repoB]
    R3[Runner N\nquorum scan imageX]
  end
  R1 --> S1[(SARIF/JSON\nartifact)]
  R2 --> S2[(SARIF/JSON\nartifact)]
  R3 --> S3[(SARIF/JSON\nartifact)]
  • Scaling is embarrassingly parallel: N independent executions on N runners, with no central contention point (except the registry/Releases when obtaining the artifact).
  • The scaling bottleneck is the runner's resources (CPU/memory for the 12 scanners), not Quorum itself.
  • Single external dependency at execution time: OSV.dev for aliases — mitigable with --offline or a shared cache.
Scale vector Mechanism Limit
More repositories/images more parallel jobs/runners CI fleet capacity
More scanners per scan goroutine fan-out (up to 12) runner memory
Frequent re-scans alias cache cache size (negligible)

N/A: vertical scalability of a service (pod auto-scaling, database sharding, load balancing) — there is no long-running service. See §4.


4. Availability

N/A as a service. Quorum exposes no endpoint, daemon or long-running process; therefore there is no service uptime to measure. The concept is reinterpreted as distribution availability — the ability to obtain and run the artifact.

flowchart LR
  Dev[Developer / CI] -->|docker pull| GHCR[(GHCR\nghcr.io)]
  Dev -->|download| REL[(GitHub Releases\nGoReleaser)]
  Dev -->|api.osv.dev| OSV[(OSV.dev\noptional)]
  GHCR --> Run[quorum scan ...]
  REL --> Run
  OSV -. graceful degradation .-> Run
Dependency Role Availability Mitigation
GHCR (ghcr.io) distribution of the :full/:slim images GitHub SLA pin by @sha256, internal mirror, runner cache
GitHub Releases native binaries + checksums + signatures GitHub SLA internal mirror, versioned copy
OSV.dev alias resolution (VULN only) best-effort --offline, local cache, graceful degradation
Scanner binaries actual execution local (:full image, 12 scanners) or PATH (:slim) :full already bundles everything

Distribution availability goals:

  • [ ] Images published reproducibly on every semver tag (release.yml).
  • [ ] Able to operate 100% offline with :full (pre-cached Grype DB + bundled crosswalk + --offline).
  • [ ] Enterprise recommendation: mirror the images in an internal registry and pin by digest.

Future proposal (clearly separated): there is no plan to turn Quorum into a hosted service; should that arise, this section would define real service uptime/SLA.


5. Security

Quorum's security surface has two fronts: (a) security of the artifact itself and of its supply chain, and (b) execution hygiene (not handling secrets). The scanned target is input data — Quorum only reads it.

5.1 Supply chain

flowchart LR
  src[Source code\nGo 1.26] --> rel[release.yml]
  rel --> img[Images :full / :slim]
  rel --> bin[GoReleaser binaries]
  rel --> kn[Knowledge pack + crosswalk]
  img --> cosign[cosign sign --yes\nkeyless OIDC + retry]
  bin --> cblob[cosign verify-blob\nchecksums.txt.sig]
  img --> slsa1[attest-build-provenance\nSLSA v1]
  img --> sbom1[attest-sbom\nattested SPDX]
  bin --> slsa2[attest-build-provenance\nsubject-checksums]
  bin --> sbom2[GoReleaser + syft\nSBOM per binary]
  kn --> slsa3[attest-build-provenance\nknowledge.sha256]
  cosign --> ver[Verify on release + retry]
  slsa1 --> ver
  sbom1 --> ver
  slsa2 --> ver
  slsa3 --> ver
Control Implementation Evidence
Keyless image signing cosign sign via GitHub OIDC (no keys), with retry (4 attempts) release.yml ("Sign image" step)
SLSA build-provenance attestation actions/attest-build-provenance@v2 (image by digest and binaries by subject-checksums) release.yml
Attested SPDX SBOM actions/attest-sbom@v2 for the image (syft SPDX-JSON) in addition to BuildKit sbom: true; GoReleaser+syft per binary release.yml, .goreleaser.yaml
Knowledge-pack + crosswalk attestation actions/attest-build-provenance@v2 over knowledge.sha256 (every advisory pack + crosswalk file); verified in the same release release.yml (knowledge job)
Verification on the release itself gh attestation verify oci://…@DIGEST (with retry) fails the build if invalid release.yml ("Verify provenance" step)
Binary signing checksums.txt + cosign verify-blob (.sig/.pem) README §Native binary
Scanner pinning by digest FROM …@sha256: (Trivy, KICS, golang, alpine); Kubescape/tfsec/Terrascan/Regula/Conftest verified by SHA256 checksum Dockerfile.full
Bases pinned by @sha256 build/runtime images (golang:1.26-alpine, alpine:3.20) pinned by digest Dockerfile.full
GitHub Action verifies before running action.yml (composite) cosign-verifies :full by default (verify: true) action.yml
Restricted release trigger semver tags v[0-9]+.[0-9]+.[0-9]+ only release.yml
Moving v0/v0.x tag for action pin advanced automatically on each semver release; does not trigger a release tag-major.yml, README §CI/CD
THIRD_PARTY_NOTICES.md license inventory of dependencies/scanners repository

Expected consumer verification (recommended in production):

cosign verify ghcr.io/martinez1991/quorum-sec-scan:full \
  --certificate-identity-regexp \
    "https://github.com/Martinez1991/quorum-sec-scan/.github/workflows/release.yml@.*" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

gh attestation verify oci://ghcr.io/martinez1991/quorum-sec-scan:full \
  --repo Martinez1991/quorum-sec-scan

# Advisory knowledge pack (digest-pinned OWASP corpus + remediation templates):
gh attestation verify knowledge/owasp/corpus.yaml \
  --repo Martinez1991/quorum-sec-scan

Honest risk note: Dockerfile.full pins Trivy, KICS and the bases (golang/alpine) by @sha256, and verifies the remaining scanners (Grype/Syft/Dockle/Kubescape/Polaris/kube-score/tfsec/Terrascan/Regula/Conftest) by SHA256 checksum of the official release artifacts. Converting all of them to immutable image digests is an incremental improvement (DESIGN §12). See Gaps.

5.2 Absence of secrets

Aspect State Justification
User credentials none no accounts/authentication
Own API tokens none OSV.dev is a public keyless endpoint
Scanner credential passthrough opt-in via QUORUM_<SCANNER>_ARGS (e.g. QUORUM_CHECKOV_ARGS --bc-api-key …) supplied by the operator, never echoed in logs (action.yml)
Advisory provider key only with --advice-provider=remote, via QUORUM_ADVICE_API_KEY (opt-in) forwarded as env by action.yml; remote is gated on --advice-allow-egress, blocked by --offline, and refuses --fix (would upload source)
Secrets in build/release only the runner's ephemeral GITHUB_TOKEN minimal scope (contents, packages, id-token, attestations)
Secret persistence none nothing is written beyond the alias cache (public) and the opt-in advice cache (normalized findings + advice text, no source code)
Secret detection in the target via the trivy adapter (TypeSecret) findings reported, with Trivy's Match redacted (redactSecretText), never persisted outside the report

Secure-execution principles:

  • [ ] Quorum does not request or store credentials.
  • [ ] Logs go to stderr and must not contain secrets (detected secret content is redacted before it leaves in the report).
  • [ ] Release workflow permissions follow least privilege.
  • [ ] Images must be verified before use (cosign + provenance + SBOM).
  • [ ] The report is written with restricted 0600 permission and the path is normalized with filepath.Clean (emit, scan.go).
  • [ ] The remote advisory provider sends only the normalized finding, never source code; it is off by default and requires explicit egress consent.

5.3 Threat model summary

Threat Vector Mitigation
Tampered image compromised registry / mutable tag cosign signature + SLSA + attested SBOM + digest pinning
Trojanized scanner binary compromised upstream digest/checksum pinning at build
Silent false negative scanner does not run but reports 0 explicit per-scanner status (§6)
Malformed bind mount wrong -v scans empty /work; local image invisible documented warning; action.yml auto-mounts docker.sock for type: image
Argument injection target starting with - interpreted as a scanner flag validateTargetRef refuses targets starting with -
DoS by giant output/target scanner dumps GBs / pathological tree caps QUORUM_MAX_OUTPUT_BYTES (512 MiB) and QUORUM_MAX_TARGET_BYTES (20 GiB) (§2.4)
Target-data exfiltration runtime network --offline; the only external call is OSV (vuln IDs only). The remote advisory provider is off by default, requires --advice-allow-egress, and is blocked by --offline

6. Observability

Being an ephemeral CLI, observability is local to the process: logs for humans or machines on stderr, machine status in the report itself, and exportable metrics for a Prometheus collector.

6.1 Logs (stderr) — text or JSON

  • All progress telemetry goes to stderr (scan.go, logf), keeping stdout clean for the report when there is no --output.
  • The --log-format text|json flag (default text) chooses the format:
  • text: readable lines prefixed with [quorum];
  • json: one object per line with {ts, level, msg} (RFC3339 UTC), ready for ingestion by a structured log collector.
  • --quiet/-q suppresses the progress logs and the summary.
  • Logged events: target/type/crosswalk rule count, per-scanner start/end with duration, baseline/min-severity suppressions, metrics writes and the gate firing.
[quorum] target=. type=repo crosswalk=142 rules (./crosswalk) offline=false
[quorum] run  trivy (0.71.2) ...
[quorum] done trivy: 17 findings in 4.2s
[quorum] skip grype: does not support target repo
[quorum] filtered: 3 suppressed by baseline (5 entries), 2 below min-severity medium
[quorum] metrics written to quorum.prom
[quorum] gate: found HIGH finding >= --fail-on high → exit 1

The same event in --log-format json:

{"ts":"2026-07-04T12:00:00Z","level":"info","msg":"done trivy: 17 findings in 4.2s"}

6.2 Prometheus metrics (--metrics)

The --metrics <file> flag writes the scan's metrics in Prometheus text format (internal/report/metrics.go, WriteMetrics), suitable for a node_exporter textfile collector or a Pushgateway — the canonical way to expose telemetry from a CLI without a long-running process. The file is written with 0644 permission (non-sensitive counts, meant for scraping).

Emitted metrics:

Metric Type Labels Meaning
quorum_scan_duration_seconds gauge total wall-clock scan time
quorum_scanner_up gauge scanner, status 1 if it ran, 0 otherwise
quorum_scanner_findings gauge scanner raw findings per scanner (pre-consensus)
quorum_scanner_duration_seconds gauge scanner duration per scanner
quorum_findings_after_consensus gauge findings after consensus merge
quorum_findings_total gauge severity consensus findings per severity
quorum_multi_detected gauge findings corroborated by more than one scanner

Advisory metrics (emitted only under --advice, and only describing the advisory layer — they never alter the consensus metrics above):

Metric Type Labels Meaning
quorum_advice_enriched gauge kind (remediation|references|recommendation) findings enriched by each advisory phase
quorum_advice_provider gauge provider advisory provider in use (none|local|remote)
quorum_advice_fix gauge stage (proposed|verified) verify-the-fix rate (verified/proposed)

6.3 Per-scanner status

Each run produces a ScannerRun (orchestrator.go) with machine status, exposed in the summary (stderr), in the report and in the metrics (quorum_scanner_up):

Status Meaning
ran ran and produced results (even if 0)
skipped scanner does not support the target type
unavailable binary missing / probe failed (timeout/OOM/not installed)
error scanner execution error
timeout execution exceeded --timeout

The final summary prints status, count per severity, multi-detected findings and the anchor phrase "0 findings is not proof of safety — see scanner statuses above." This turns status into an observable signal, not noise.

6.4 State and proposals

Capability State Note
stderr logs ✅ Implemented prefixed, with --quiet
Structured JSON log on stderr ✅ Implemented --log-format json ({ts,level,msg})
Per-scanner status ✅ Implemented in the summary, report and metrics
Per-scanner duration ✅ Implemented ScannerRun.Duration
Prometheus metrics (textfile/Pushgateway) ✅ Implemented --metrics <file>
Advisory metrics ✅ Implemented only under --advice (quorum_advice_*)
OpenTelemetry traces ⚠️ Future proposal no long-running process; not prioritized

7. Auditability

The report is the audit evidence. It is reproducible, stamps who detected what and provides a stable identity per finding.

Audit artifact Content Where
Per-scanner status ran/skipped/unavailable/error/timeout + version + duration Result.Runs, report, metrics
Detection provenance detectedBy[], detectionCount, confidence MergedFinding, SARIF properties
Stable identity Fingerprint = sha256(correlationKey) SARIF partialFingerprints["quorum/v1"], JSON fingerprint
Suppressions always logged and counted, never silently dropped baseline .quorumignore, filter.Apply
Scanner versions ScannerVersion per finding / ScannerRun.Version report
Scan metrics counts/durations in Prometheus format --metrics file

Audit properties:

  • Reproducibility: same input → same correlationKey/Fingerprint (pure function). This enables temporal diffs of reports. The opt-in advisory attachments (Remediation/References/Advice) are additive and clearly labeled "AI-generated, advisory only"; they do not alter identity.
  • Suppression traceability: a finding suppressed by the baseline is reported as suppressed (with the entry that suppressed it), it does not vanish.
  • Artifact chain of custody: SARIF/JSON can be archived as CI-gate evidence; signatures/provenance/SBOM (§5) attest the origin of the binary that generated it.

CI audit checklist:

  • [ ] Archive the report (SARIF/JSON) as a job artifact.
  • [ ] Archive the --metrics file alongside the report (historical telemetry).
  • [ ] Version-control the .quorumignore (each suppression reviewed and dated).
  • [ ] Retain the summary output (per-scanner status) alongside the artifact.
  • [ ] Record the version/digest of the Quorum image used.

8. LGPD (Brazilian General Data Protection Law)

Applicable by exclusion — Quorum does not process personal data. The justification is technical: the data processed is software and infrastructure metadata, not data about natural persons.

Data category Processed? Nature
Vulnerability identifiers (CVE/GHSA/AVD) Yes public technical data
PURLs / package versions Yes technical data
File paths, IaC/K8s resources Yes technical data of the target
Detected secret content (Trivy) Possible not PII by definition; redacted and kept only in the user's report
Personal data (name, CPF, e-mail, etc.) No out of functional scope

Consequences:

  • No PII ⇒ no need for a legal basis, consent, DPO or DPIA attributable to Quorum as controller/processor.
  • The only external call (OSV.dev) sends only vulnerability IDs (e.g. CVE-2021-…) — there is no personal data in the traffic. Disableable with --offline.
  • User responsibility: if the scanned target contains PII (e.g. a secret that is an e-mail), the generated report may contain it even with secret redaction. The handling and retention of that report is the responsibility of whoever operates Quorum, not of the product.
  • The remote advisory provider (opt-in) sends only the normalized finding, not source code; it is off by default, requires explicit egress consent, and is blocked by --offline.

N/A: data-subject rights, portability, internal anonymization — there is no personal-data store under Quorum's control.


9. PCI DSS

N/A as a product. Quorum does not store, process or transmit cardholder data (CHD/SAD) and is not part of a Cardholder Data Environment (CDE) by construction. Therefore it is not in scope of PCI DSS as a system.

However, Quorum can support the compliance of whoever operates a CDE, as a supporting tool for the requirements below:

PCI DSS v4.0 requirement How Quorum helps
6.2 / 6.3 — develop secure software and manage vulnerabilities SCA consensus (Trivy+Grype) with confidence for prioritization
6.3.1 — identify vulnerabilities from recognized sources CVE IDs via OSV; authoritative confirmation weighs into the score
6.3.2 — inventory of components (bespoke/3rd-party) PURLs per finding; attested SPDX SBOM of the :full image
6.4 / 6.5 — protect applications and manage changes CI gate via --fail-on; auditable baseline; policy-as-code (Conftest)
11.3 — vulnerability scans pipeline-automatable scanning, SARIF evidence

Quorum does not replace an external ASV scan or a pentest. It is a compensating/preventive control in the SDLC, not a compliance attestation.


10. ISO/IEC 27001 (Annex A)

Mapping of the controls applicable to Quorum (as a tool and as a build chain). Broad organizational controls are the responsibility of the adopter.

Control (ISO 27001:2022) Applicability How Quorum addresses it
A.8.8 — Technical vulnerability management ✅ Direct product core: detection + consensus + gate
A.8.25 — Secure development lifecycle ✅ Supports integrates into the SDLC via CI
A.8.28 — Secure coding ✅ Supports actionable IaC/MISCONFIG/SECRET/policy-as-code findings
A.8.29 — Security testing in dev/acceptance ✅ Supports gate via exit code
A.8.30 — Outsourced development ⚙️ Partial scan of 3rd-party components (SCA)
A.8.31 — Separation of environments ➖ Adopter's isolated execution per ephemeral job
A.8.15 — Logging ⚙️ Partial stderr logs (text/json) + per-scanner status + metrics
A.8.16 — Monitoring ⚙️ Partial Prometheus metrics (--metrics)
A.5.23 — Security in cloud services ➖ Adopter's N/A (no own cloud service)
A.5.7 — Threat intelligence ⚙️ Partial OSV/CVE as vulnerability source
Supply chain (A.5.19–A.5.23) ✅ Own cosign + SLSA provenance + attested SBOM + knowledge-pack attestation + sha256/checksum pinning (§5)

Legend: ✅ meets/supports directly · ⚙️ partial · ➖ adopter's responsibility.


11. OWASP ASVS (controls relevant to a CLI)

Most ASVS chapters assume a web application with session and authentication — N/A here. What remains are the controls applicable to a binary/CLI:

ASVS domain Applicability State in Quorum
V1 Architecture & threat modeling DESIGN.md + §5.3 of this doc
V5 Input validation flag parsing (cobra); validateTargetRef refuses -…; adapter parsers with contract tests
V7 Error handling & logging actionable errors, stderr logs (text/json), secret redaction
V10 Malicious code / integrity signing, provenance, SBOM, dependency pinning
V12 Files & resources ⚙️ reads target/crosswalk/baseline; report written with 0600 and filepath.Clean; output/target DoS caps
V14 Configuration secure defaults (--offline optional, crosswalk with fallback, advisory off by default)
V2 Authentication ➖ N/A no authentication
V3 Session ➖ N/A no session
V4 Access control ➖ N/A no multi-user
V6 Stored cryptography ➖ N/A stores no sensitive data
V8/V9 Data/Communication ⚙️ OSV via HTTPS; remote advisory (opt-in) via HTTPS with explicit egress consent; no sensitive data in transit
V13 APIs ➖ N/A exposes no API

Practical ASVS checklist:

  • [ ] Validate flag format/severity and fail with exit code 2 on invalid usage.
  • [ ] Ensure adapter parsers have a versioned fixture/contract test (coverage in CI).
  • [ ] Do not emit secret content in progress logs (redaction active).
  • [ ] Verify binary/image integrity before executing.

12. OWASP Top 10 (of the binary/image itself)

Scope: risks of the Quorum artifact itself, not of the target it scans (that is the scanners' job). Mapping to OWASP Top 10:2021.

Risk Relevance to Quorum Control
A01 Broken Access Control Low no access control (local CLI)
A02 Cryptographic Failures Low keeps no secrets; signatures via Sigstore; report 0600
A03 Injection Medium invokes scanners via exec (args, not shell); validateTargetRef blocks argument injection via -
A04 Insecure Design Medium "false split > false merge" principle; threat model in DESIGN
A05 Security Misconfiguration Medium secure defaults; docker.sock auto-mount for type: image; crosswalk with fallback
A06 Vulnerable & Outdated Components High Go deps + 12 pinned scanner binaries; SPDX SBOM; Quorum itself should be scanned
A07 Identification & Auth Failures N/A no authentication
A08 Software & Data Integrity Failures High cosign (with retry) + SLSA provenance + attested SBOM + knowledge-pack attestation + digest/checksum pinning
A09 Logging & Monitoring Failures Medium per-scanner status + metrics avoid silent false negatives
A10 SSRF Low the only network egress is OSV.dev (fixed host); --offline disables it. The opt-in remote advisory endpoint is user-configured and gated on explicit egress consent

Prioritized actions:

  • [ ] A06/A08: periodic rebuild to refresh the Grype DB and scanner version bumps.
  • [ ] A08: convert the remaining checksums to image @sha256 in Dockerfile.full.
  • [ ] A03: keep scanner invocation via args (no shell), reviewed in code review.

Note on the AI advisory layer: the OWASP LLM Top 10 only applies when the AI layer is enabled (--advice-provider=local|remote). The deterministic core has no AI; see 13-ia.md.


13. Resilience (graceful degradation)

Resilience is a central design principle: a partial failure degrades, it does not crash, and the degradation is always visible.

flowchart TD
  A[Scanner missing] --> A1[status unavailable\nscan continues]
  B[Slow probe / OOM] --> B1[status unavailable\nactionable message]
  C[Scanner exceeds --timeout] --> C1[status timeout\nothers continue]
  D[OSV.dev unavailable] --> D1[uses original id\nlocal cache; no failure]
  E[Crosswalk missing] --> E1[fallback /opt/quorum/crosswalk\nor unmapped finding]
  F[Unmapped control] --> F1[isolated finding\nunmapped flag]
  G[Giant output/target] --> G1[DoS cap aborts\nwith actionable message]
  H[cosign/OIDC unstable on release] --> H1[retry 4x\nwith backoff]
  I[Advice model unreachable] --> I1[report ships without AI advice\nscan never fails]
Failure Behavior Source
Scanner not installed unavailable, scan proceeds orchestrator.runOne
Slow probe / OOM unavailable with diagnostics (memory/scope) orchestrator.runOne
Execution timeout timeout, other scanners continue orchestrator + --timeout
OSV network unavailable aliases degrade to original id; never fails the scan alias/DESIGN §7
:full without network pre-cached Grype DB + bundled crosswalk Dockerfile.full
Aged Grype DB GRYPE_DB_VALIDATE_AGE=false — DB never expires, scan does not hang Dockerfile.full
Giant scanner output aborted at the 512 MiB cap (QUORUM_MAX_OUTPUT_BYTES) adapter.runCmd
Giant repo/k8s target refused at the 20 GiB cap (QUORUM_MAX_TARGET_BYTES) scan.checkTargetSize
Missing default crosswalk automatic fallback to /opt/quorum/crosswalk resolveCrosswalkDir
Unmappable control finding stays isolated, unmapped flag (does not guess a merge) DESIGN §6
Unstable signing/verification on release retry (4 attempts, growing backoff) on cosign and gh attestation verify release.yml
Advice model/endpoint unreachable graceful degradation: report ships without AI advice, scan never fails internal/advisor

Resilience invariant: "0 findings is not proof of safety" — every degradation shows up as status, preventing a failure from turning into a false "all clear".


14. Backup, DR, RTO and RPO

N/A — Quorum is stateless. There is no production data under its custody that could be lost. Continuity depends on reproducible artifacts, not on backup/restore.

Concept Applicability Justification
Data backup N/A no persistent storage of business state
Disaster Recovery (DR) N/A (service) there is no service to recover
RTO ~minutes, reinterpreted "recover" = re-run the scan on another runner
RPO 0, reinterpreted nothing to lose; each scan is recomputable from source

What replaces backup/DR:

  • Reproducibility: digest-pinned images + signed binaries + provenance + attested SBOM allow rebuilding/obtaining exactly the same artifact.
  • Distribution mirror: mirroring images/binaries in an internal registry protects against GHCR/Releases unavailability.
  • Alias cache: disposable; rebuilt from OSV/scanner. Written with 0600 permission and a schemaVersion for safe invalidation. (The opt-in advice cache is equally disposable and rebuildable, keyed by fingerprint+provider+model.)

The only truly recoverable item: the archived report as evidence (§7) — its retention/backup is the responsibility of the pipeline that generates it, not of Quorum.

Future proposal (separated): if a mode with a shared persistent cache is adopted at scale, define a backup/retention policy for that cache.


15. SLA / SLO / SLI

There is no contractual service SLA (it is not a service). We define SLO/SLI for two real operational contexts: use in CI and release pipeline.

15.1 Use in CI (the binary/image as a pipeline dependency)

SLI Definition Target SLO How to measure
Scan reliability % of jobs that finish with exit 0/1 (no 2) ≥ 99% job exit codes
Determinism % of re-scans of the same input with the same fingerprint set 100% report diff
Overhead latency orchestrator overhead (excluding scanners) ≤ 2s p95 summary/Duration/metrics
Network-failure resilience scans completed with OSV unavailable 100% (degrades) --offline execution
Execution coverage % of supported scanners with ran status ≥ 95% on :full per-scanner status / quorum_scanner_up
Advisory neutrality % of scans where --advice leaves the core output (fingerprints/consensus) unchanged 100% byte-diff with/without --advice

15.2 Release pipeline (artifact publishing)

SLI Definition Target SLO How to measure
Publishing success % of semver tags that publish images+binaries 100% release.yml
Verified integrity % of releases with cosign+provenance+SBOM+knowledge-pack verified in the build itself 100% "Verify provenance" / attest-sbom / knowledge job steps
Build reproducibility deterministic build (-trimpath, pinned deps, bases by sha256) 100% Dockerfile.full/GoReleaser
Test coverage tests with -race -covermode=atomic on every push/PR blocking green ci.yml ("Test (race + coverage)" step)
Advisory eval coverage remediation coverage, OWASP reference relevance and verify-the-fix rate measured in CI tracked (no heavy model) internal/evals harness
Release time tag → images+binaries published ≤ 30 min workflow duration

Important distinction between product SLA vs. integrity: publishing availability depends on GitHub/GHCR (not under our control), but integrity (signature + provenance + SBOM + knowledge-pack verified) is blocking in the release itself — an artifact with broken attestation fails the build. Transient Sigstore/OIDC instability is absorbed by retry (§13), not by relaxing the verification.


Assumptions

  • The numbers/goals in §2.5 and §15 are engineering targets, not yet validated by a formal benchmark in this repository.
  • The production consumer is assumed to verify the image/binary (cosign + gh attestation verify) and, ideally, pin by digest — the product provides the means (signature, provenance, attested SBOM, knowledge-pack attestation), the application is the adopter's.
  • --offline is the recommended mode when CI policy forbids network egress; with it, the only external runtime dependency (OSV.dev) is eliminated, and the remote advisory provider is blocked entirely.
  • "No PII" assumes the product is not configured to process personal data; PII that happens to be present in the target is the responsibility of the report's operator (even with secret redaction applied).
  • The advisory layer (--advice) is opt-in and off by default; when off, the output is byte-identical and determinism is preserved. Only the remote provider causes data to leave the host, and only under explicit consent (--advice-allow-egress), sending the normalized finding, never source code.
  • The PCI DSS, ISO 27001, ASVS and OWASP Top 10 mappings are interpretive, based on the as-is behavior of the code (v0.8.3), and do not constitute a compliance attestation.
  • Distribution availability depends on third-party services (GitHub GHCR/Releases, OSV.dev), whose SLAs are not controlled by this project.
  • The :full image is linux/amd64 (the 12 bundled scanners are amd64); arm64 scenarios use :slim (amd64+arm64) with scanners on the PATH, which changes the local performance/availability profile.
  • The DoS cap defaults (512 MiB of output per scanner, 20 GiB target) are sized far above any real case; legitimate targets that exceed them require an explicit env override.

Known gaps

  • There is no versioned performance benchmark to back the target SLOs.
  • Not all scanners in Dockerfile.full are pinned by an immutable image digest (Trivy/KICS/bases are; the rest by version+SHA256 checksum); hardening all of them to @sha256 is a recommended incremental improvement.
  • Observability covers logs (text/json), per-scanner status and Prometheus metrics (--metrics, plus the opt-in quorum_advice_*); OpenTelemetry traces remain a future proposal.
  • There is no native mirror/retention mechanism for artifacts; it is the adopter's responsibility.