Security¶
This section is the complete security analysis of Quorum (quorum-sec-scan, v0.8.3),
a CLI/Docker consensus security scanning tool. Quorum does not scan artifacts
directly: it orchestrates a pool of 12 OSS scanners (trivy, grype, checkov, kics,
dockle, kubescape, polaris, kube-score, terrascan, tfsec, regula, conftest), parses the
untrusted output of each one, reads arbitrary targets (images, repositories,
manifests) and, optionally, queries OSV.dev over the network. This profile — a
security tool that runs other tools and ingests untrusted data — concentrates the risk on
four boundaries: shell-out, parsing, file I/O and network/supply chain.
As of v0.8.3 there is one more, strictly opt-in surface: the advisory layer
(--advice). It is presentation-only — it never touches correlationKey, fingerprint,
the confidence score, aggregated severity, or the fail-on gate; without --advice the
output is byte-identical. The deterministic core still has no AI. Its optional remote
provider is the only path that can send data off-host, and it is consent-gated
(--advice-allow-egress) and blocked by --offline — so it gets its own boundary below.
The document models the threats over these boundaries, maps the product against the main frameworks (OWASP, MITRE, STRIDE, DREAD, LINDDUN, NIST, CIS, ISO 27001, PCI DSS, LGPD) and, for each risk, presents Description, Impact, Likelihood, Severity, Mitigation, Recommendation and suggested Tools. Where a framework does not apply to the CLI/Docker scope (MASVS, MITRE ATLAS — and OWASP LLM Top 10, which applies only when the opt-in AI layer is enabled), this is declared N/A with technical justification.
Cross-links: Architecture · Operations/CI ·
Supply Chain & Releases · AI · DESIGN.md
(§12 CLI & Docker, §14 Risks).
Version note. This document was originally written for v0.2.3 and revised for v0.8.3 (revision 2026-07-04). Between those versions, the pool grew from 6 to 12 scanners, consensus was extended to MISCONFIG/IaC and K8S_POSTURE (via a crosswalk derived from real output), most of the hardening gaps this doc used to flag were closed (target
-refused,--outputnormalized +0o600, OSVidvalidated +PathEscape, cache0o600+ schemaVersion, secret redaction, DoS caps, supply chain with attested SLSA + SBOM), and an opt-in advisory layer (--advice, presentation-only, off by default) was added. The history is preserved in the text of each risk.
1. Trust-boundary model¶
Quorum is a Go process that, starting from user/CI-controlled input, spawns subprocesses and consumes bytes it did not produce. All the analysis below is anchored to this model.
flowchart LR
subgraph Untrusted["UNTRUSTED input"]
T[Target: image / repo / k8s]
OSVresp[OSV.dev response]
SOUT[scanner stdout/stderr]
CACHE[(aliases.json on disk)]
BL[.quorumignore / crosswalk / Rego policy]
end
subgraph Quorum["quorum process (Go 1.26)"]
SCAN[scan.go runScan + validateTargetRef + checkTargetSize]
ORCH[orchestrator fan-out]
ADP[adapter.runCmd exec.CommandContext + capWriter 512MiB]
PARSE[JSON/SARIF adapter parsers + redaction]
ALIAS[alias resolver + OSVClient]
ADV[advisory layer: enrich / rag / advisor -- opt-in, presentation-only]
EMIT[emit -> WriteFile 0o600 / stdout]
end
subgraph External["Subprocesses / network"]
SCANNERS[12 scanners: trivy grype checkov kics dockle kubescape polaris kube-score terrascan tfsec regula conftest]
OSV[(api.osv.dev)]
AIEP[(AI endpoint: local Ollama / remote API -- only under --advice-provider)]
end
T --> SCAN --> ORCH --> ADP --> SCANNERS
SCANNERS -- untrusted stdout --> PARSE --> ALIAS
ALIAS <--> CACHE
ALIAS --> OSV
OSV -- OSVresp --> ALIAS
BL --> SCAN
PARSE --> ADV
ADV -. opt-in .-> AIEP
ADV --> EMIT
PARSE --> EMIT
EMIT --> OUT[report.sarif/json/xml + output file + Prometheus metrics]
Trust boundaries:
| # | Boundary | Untrusted input | Where in the code |
|---|---|---|---|
| B1 | Shell-out | target.Ref, scanner names, flags, QUORUM_<NAME>_ARGS |
internal/adapter/adapter.go:runCmd (exec.CommandContext) |
| B2 | Parsing | JSON/SARIF stdout of the 12 scanners | internal/adapter/*.go (json.Unmarshal) |
| B3 | File I/O | --output, --cache, --baseline, --crosswalk, --metrics, --advice-cache |
cmd/quorum/scan.go:emit, internal/cache/store.go |
| B4 | Network | OSV.dev response | internal/alias/osv.go |
| B5 | Supply chain | scanner binaries, base image, knowledge pack + crosswalk | Dockerfile.full, action.yml, release.yml, GHCR |
| B6 | Resources | target size (repo/image), stdout size | cmd/quorum/scan.go:checkTargetSize, adapter.capWriter |
| B7 | Advisory / AI (opt-in) | LLM output; egress of the normalized finding to a remote endpoint | internal/advisor, internal/rag, internal/enrich (only under --advice) |
2. STRIDE per boundary¶
| Boundary | Spoofing | Tampering | Repudiation | Info Disclosure | DoS | Elevation |
|---|---|---|---|---|---|---|
| B1 Shell-out | — | — | — | — | ▲ | ▲ (command injection) |
| B2 Parsing | — | ▲ (forged output inflates/hides findings) | — | — | ▲ (giant JSON — capped at 512 MiB) | — |
| B3 File I/O | — | ▲ (path traversal/overwrite) | — | ▲ (findings in wrong location) | — | — |
| B4 OSV network | ▲ (DNS/MITM) | ▲ (forged response) | — | ▲ (leaks queried IDs) | ▲ (latency — 8s timeout) | — |
| B5 Supply chain | ▲ (fake image/binary) | ▲ (trojanized binary/pack) | — | — | — | ▲ |
| B6 Resources | — | — | — | — | ▲ (zip/img bomb — 20 GiB target cap) | — |
| B7 Advisory/AI (opt-in) | ▲ (rogue AI endpoint) | ▲ (forged recommendation) | — | ▲ (remote provider egress of the finding) | — | — |
▲ = vector relevant to Quorum. Blanks indicate a vector not applicable to the CLI scope.
Row B7 is inert unless --advice is set; the presentation-only design keeps it off the
correlation/confidence path.
3. Risk matrix (DREAD)¶
DREAD scale per criterion 1–10; Score = mean; Severity derived: Critical ≥ 8, High 6–7.9, Medium 4–5.9, Low < 4.
| ID | Risk | Damage | Reprod. | Exploit. | Affected | Discover. | Score | Sev | State |
|---|---|---|---|---|---|---|---|---|---|
| R1 | Command injection via target/flags |
9 | 3 | 3 | 6 | 4 | 5.0 | Medium | ✅ Mitigated (argv, no shell + target - refused) |
| R2 | Parsing malicious scanner output | 6 | 7 | 5 | 7 | 5 | 6.0 | High | Partial (byte cap + redaction; SARIF sanitization pending) |
| R3 | Path traversal / overwrite via --output |
7 | 8 | 7 | 5 | 6 | 6.6 | High | ✅ Mitigated (Clean + 0o600) |
| R4 | Supply chain (scanner binaries, mutable tag) | 9 | 5 | 5 | 8 | 4 | 6.2 | High | ✅ Mostly mitigated (attested SLSA + SBOM + pin/checksum) |
| R5 | DoS via giant target / zip bomb / img bomb | 6 | 6 | 5 | 6 | 6 | 5.8 | Medium | ✅ Mitigated (20 GiB target cap + 512 MiB stdout cap + timeout) |
| R6 | SSRF / ID exfiltration via OSV | 5 | 4 | 3 | 5 | 5 | 4.4 | Medium | ✅ Mitigated (id validation + PathEscape; metadata leak remains) |
| R7 | Cache poisoning (aliases.json) |
5 | 6 | 5 | 5 | 4 | 5.0 | Medium | ✅ Mitigated (0o600 + schemaVersion; signing still absent) |
| R8 | Findings exposure (sensitive report) | 6 | 5 | 4 | 6 | 5 | 5.2 | Medium | ✅ Mitigated (0o600 + secret redaction; artifact classification is operational) |
| R9 | Tampered crosswalk/baseline (false merge/suppress) | 7 | 5 | 4 | 5 | 4 | 5.0 | Medium | Partial |
| R10 | RCE in a trojanized scanner parser/dep | 9 | 3 | 3 | 7 | 3 | 5.0 | Medium | Partial (mitigated by pin/checksum + SLSA) |
| R11 | Advisory-layer egress / AI output handling (opt-in) | 5 | 4 | 3 | 4 | 3 | 3.8 | Low | ✅ Mitigated by design (off by default; egress consent-gated + --offline-blocked; presentation-only) |
Product principle that reduces class risk: "false split > false merge" — when in doubt, Quorum does not merge findings. That limits the damage of R2/R9 (a forged output tends to produce extra findings, not hide real ones). Consensus for MISCONFIG/IaC and K8S_POSTURE via crosswalk was derived from real output precisely to keep
false split > false merge(the crosswalk covers AVD for AWS/Azure/GCP and theC-####kubescape controls for Kubernetes;tfsecauto-correlates withtrivyby emitting native AVD; RBAC stays single-engine because it needs cluster context).
4. Detailed risks¶
R1 — Command injection via target / flags (B1)¶
- Description.
runCmdexecutes each scanner withexec.CommandContext(ctx, bin, args...)ininternal/adapter/adapter.go. The arguments (includingtarget.Ref) are passed as an argv slice, with no intermediate shell (/bin/sh -c). There is no string interpolation in the command, so shell metacharacters (;,|,$(), backticks) in a target such asrepo; rm -rf /are treated as a literal path, not as a command. - Impact. Arbitrary command execution in the Quorum process context (and, under Docker,
inside the
:fullcontainer). - Likelihood. Low — requires a regression that reintroduces
sh -c, or a scanner that itself interprets the target as shell. - Severity. Medium (high impact, low likelihood in the current state).
- Mitigation (present).
exec.CommandContextwith argv; nosh -canywhere in the code; the target type is resolved byresolveTargetTypeand the ref is always positional. - Mitigation (present) — argument injection. ✅ Resolved. A target starting with
-(e.g.--config=/etc/...) could be interpreted as a flag by the scanner.runScanvalidatestargetat the CLI boundary viavalidateTargetRef(cmd/quorum/scan.go) and refuses any ref starting with-(the message suggests./-namefor a literal path), so no adapter ever receives a leading-hyphen ref. This universal validation was chosen over injecting a per-scanner--(which would break scanners without--support). - Note — args passthrough.
QUORUM_<NAME>_ARGS(viaextraArgs/splitArgs) injects extra args into a scanner (e.g.QUORUM_CHECKOV_ARGS="--bc-api-key <key>"to unlock Prisma/Bridgecrew policies). This is operator-controlled (whoever runs the container/CLI), at the same trust level as the flags themselves — not untrusted third-party input.conftestevaluates your Rego from./policy(or viaQUORUM_CONFTEST_ARGS="--policy <dir>"). - Recommendation.
- [x] ~~Validate/normalize
target.Ref(reject refs starting with-)~~ — done. - [ ] (Optional) Validate image reference format and path existence for repos.
- [ ] Regression test ensuring no adapter uses
sh -c. - Tools.
gosec(G204),semgreprulego.lang.security.audit.dangerous-exec,golangci-lint.
R2 — Parsing malicious scanner output (B2)¶
- Description. Each of the 12 adapters runs
json.Unmarshalon the scanner's stdout (e.g.trivy.parse,grype.parse,conftest.parse). A compromised scanner, or a target that induces the scanner to emit adversarial output, can (a) inflate findings, (b) inject content into fields such asTitle/Descriptionthat later flow into SARIF, or (c) emit huge JSON. - Impact. Polluted report; potential XSS/HTML injection if the SARIF is rendered in a third-party UI (GitHub code scanning, etc.); memory consumption.
- Likelihood. Medium — depends on compromising a scanner or a hostile target.
- Severity. High.
- Mitigation (present). Structured parsing into Go structs (unknown fields ignored), not
eval;runCmdonly treats exit≠0 as an error when stdout is empty (the "findings-found" convention), avoiding an exit code being mistaken for success; the false split > false merge principle limits hiding. - Mitigation (present) — memory DoS. ✅
runCmdcaps stdout viacapWriterat 512 MiB (defaultMaxOutputBytes, tunable viaQUORUM_MAX_OUTPUT_BYTES). On exceeding the cap the process aborts with a clear error instead of OOMing —capWriterkeeps draining the pipe (returnslen(p)) so the child does not stall, and truncation is detected after the run. - Mitigation (present) — secrets. ✅ Fields carrying text near secrets are redacted:
trivy.parseonly stores a secret finding'sMatchafter passing it throughredactSecretText(keeps locating context, but only the first 4 chars of each long token survive — see R8). - Remaining gaps. No explicit sanitization/escaping of generic textual fields
(
Title/Description/Location) before emitting SARIF for third-party UIs; no schema validation of the output. - Recommendation.
- [x] ~~
io.LimitReader/byte cap on scanner stdout (memory DoS)~~ — done (capWriter, 512 MiB). - [ ] Sanitize/escape
Title/Description/Locationin thereportlayer. - [ ] Keep the per-adapter contract tests (
internal/adapter/testdata,realdata_test.go) and add adversarial fixtures (giant fields, control unicode, malformed JSON). - Tools.
go-fuzz/native fuzzing on the parsers,semgrep, SARIF validators.
R3 — Path traversal / overwrite via --output (B3)¶
- ✅ Mitigated.
- Description. Until v0.2.3, in
cmd/quorum/scan.go:emit, the--outputpath was used directly (os.WriteFile(output, ..., 0o644)) without normalization, and readable by other host users. - Impact. In CI, an input-controlled
--output(e.g. derived from a branch/PR name) could overwrite files writable by the runner and expose findings (perm0644). - Likelihood. Medium (in pipelines that build
--outputfrom variables). - Severity. High.
- Mitigation (present).
emitappliesfilepath.Clean(output)(collapses./and../), creates the parent directory (0o755) and writes the report with perm0o600(no longer world-readable). The Prometheus metrics file (--metrics) follows the sameCleanbut writes0o644because it holds only non-sensitive counts meant for scraping. - Recommendation.
- [x] ~~Normalize
--outputwithfilepath.Clean~~ — done. - [x] ~~Reduce the report's permission~~ — done:
0o600. - [ ] (Optional) Confine output to an
--output-dir/CWD when it comes from untrusted input. - [ ] (Optional)
O_EXCL/atomic write when not an intentional overwrite. - Tools.
gosec(G304 file path provided as taint),semgrep.
R4 — Scanner and image supply chain (B5)¶
- ✅ Mostly mitigated (hardened in
release.yml,Dockerfile.fulland.goreleaser.yaml). - Description.
Dockerfile.fullcombines three trust levels for the 12 scanners: - Digest-pinned (base image):
aquasec/trivy@sha256:…,checkmarx/kics@sha256:…; thegolang:1.26-alpine@sha256:…andalpine:3.20@sha256:…bases are pinned too. - Checksum-verified (SHA256): Dockle, Kubescape (
KUBESCAPE_SHA256pinned, nocurl | bashfallback), Polaris, kube-score, tfsec, Terrascan, Regula and Conftest — each downloads the artifact +checksums.txtand runssha256sum -c. - Anchore
install.sh(residual): Grype and Syft viaraw.githubusercontent.com/.../main/install.sh. The installer validates the binary's internal checksum, but the script itself comes from a moving branch (main). Checkov viapip install "checkov==<version>"without--require-hashes. - Impact. A trojanized binary running inside the trust boundary → RCE, exfiltration.
- Likelihood. Low–Medium (reduced by the majority pin/checksum).
- Severity. High (impact), but likelihood contained.
- Mitigation (present).
:fullimage (linux/amd64) and:slim(amd64+arm64) published on GHCR, keyless-signed with cosign (OIDC) with retry against Fulcio/Rekor flakiness.- SLSA build-provenance attestation (
actions/attest-build-provenance) of the image and, for the native binaries, over the GoReleaserdist/checksums.txt— re-verified in the release itself (gh attestation verify, with retry). - Attested SPDX SBOM (
actions/attest-sbom, via syft) for the image, on top of BuildKit'ssbom: true; per-binary SBOMs via GoReleaser/syft. - Attested advisory knowledge pack. The advisory data — remediation templates and the
digest-pinned OWASP corpus (
knowledge/*.yaml,knowledge/owasp/corpus.yaml) plus the crosswalk — now gets its own SLSA build-provenance attestation each release (release.ymlknowledgejob, over a sortedknowledge.sha256manifest). It ships inside the images but is also a standalone data artifact a consumer can verify withgh attestation verify knowledge/owasp/corpus.yaml. - Grype DB pre-cached in the image with
GRYPE_DB_VALIDATE_AGE=false(the 1st scan runs offline; the bundled DB does not expire). - The composite GitHub Action (
action.yml) runscosign verifyon the:fullimage before executing it (verify: trueby default, via therelease.ymlidentity-regexp); auto-mounts/var/run/docker.sockfortype: image(avoids a false-zero when scanning a local image); exposes*-argsanddocker-socketinputs; the movingv0tag (for action pinning) is advanced automatically bytag-major.ymlon every semver release. THIRD_PARTY_NOTICES.mddocuments third-party dependencies.- Remaining gaps. Grype/Syft still via a moving-branch
install.sh; Checkov viapip installwithout--require-hashes; thecosigninstalled byaction.ymlusesreleases/latest/download(mutable). - Recommendation.
- [x] ~~Pin downloaded scanners by digest/checksum~~ — done for 10 of 12 (grype/syft via
install.shand checkov via pip remain). - [x] ~~Publish and verify the
:fullimage SBOM in the release~~ — done (attested SPDX). - [x] ~~Attest the advisory knowledge pack~~ — done (
knowledgejob). - [x] ~~Verify the SLSA attestation~~ — done at release; replicate on the consumer
(
gh attestation verify/cosign verify-attestation). - [ ] Pin the Anchore
install.shby commit SHA;pip install --require-hashesfor Checkov. - [ ] Pin the
cosignversion installed inaction.yml. - Tools. cosign,
slsa-verifier/gh attestation verify, syft (SBOM), trivy/grype on the image itself,pip-audit, Dependabot/Renovate with digest pinning.
R5 — DoS via giant target, zip bomb, image bomb (B6)¶
- ✅ Mitigated.
- Description. Quorum reads arbitrary targets and delegates reading to the scanners. A giant repo, an image with inflated layers, or a malicious compressed file (zip bomb) can exhaust the runner's CPU/memory/disk.
- Impact. Pipeline hang; runner cost; OOM.
- Likelihood. Medium.
- Severity. Medium.
- Mitigation (present).
- Target size cap:
checkTargetSize(cmd/quorum/scan.go) aborts early if a filesystem target (repo/k8s) exceeds 20 GiB (defaultMaxTargetBytes, tunable/disable-able viaQUORUM_MAX_TARGET_BYTES); theWalkDirstops as soon as the cap is crossed, so normal repos only pay a lightstatpass. Image targets (no local tree) are skipped. - Stdout cap:
capWriterlimits each scanner's output to 512 MiB (see R2). - Per-scanner timeout (
--timeout, default 5m;PerScannerTimein the orchestrator) with parallel fan-out isolating each scanner; a version probe that distinguishes timeout / killed(OOM) / not-installed and produces an actionable message ("likely out of memory; raise the container's memory limit"); per-scanner status (ran|skipped|unavailable|error|timeout) makes the DoS visible instead of masking it as "0 findings". Principle "0 findings is not proof of safety". - Remaining gaps. The target cap is by total on-disk size, not by decompression
depth (a small on-disk zip bomb can inflate inside the scanner); Quorum imposes no
ulimit/cgroup of its own (it delegates to the container runtime). - Recommendation.
- [x] ~~Byte cap on stdout and target size limit~~ — done (512 MiB / 20 GiB).
- [ ] Document/recommend memory limits and
--timeoutin CI. - [ ] Run the image with
--memory/--pids-limit/--read-onlyandtmpfsfor/tmp. - Tools. Docker/K8s cgroup limits,
timeout(1), runner monitor.
R6 — SSRF / exfiltration via OSV (B4)¶
- ✅ URL-manipulation risk mitigated; passive metadata exfiltration to the provider
remains (use
--offline). - Description. The alias resolver queries
https://api.osv.dev/v1/vulns/<id>(internal/alias/osv.go). The<id>comes from the scanner output. Until v0.2.3 it was concatenated into the URL path without sanitization.BaseURLis fixed (not flag-controllable), which limits classic SSRF, but an adversarialidcould contain path/query characters. - Impact. (a) Passive exfiltration: the list of queried IDs reveals which vulnerabilities
exist in the target to a third party (OSV.dev). (b) Residual URL-manipulation risk if
idis not validated. - Likelihood. Low–Medium.
- Severity. Medium.
- Mitigation (present).
--offlineturns OSV off entirely (inrunScan,osvis only instantiated when!f.offline; it then uses only scanner-local aliases + cache); fixedBaseURL; client with an 8s timeout,MaxRetries=2and exponential backoff (200ms base) sensitive toctx— graceful degradation: a network failure never breaks the scan (DESIGN §7); CVE is preferred locally before touching the network, reducing the query count. - Mitigation (present) — id validation. ✅ The
idis validated against^[A-Za-z][A-Za-z0-9._-]{0,127}$(vulnIDPattern) and passes throughurl.PathEscape(defense in depth) before composing the URL; malformed ids are refused without touching the network ("osv: refusing to query malformed vuln id"). This prevents a forged id from injecting path/query segments (../,?,/). - Remaining gaps. No explicit host allowlist beyond the constant; the query still leaks metadata (the list of IDs) to the provider.
- Recommendation.
- [x] ~~
url.PathEscape(id)+ validate the format before the request~~ — done. - [ ] Document
--offlinefor air-gapped / sensitive-data environments. - [ ] Allow an internal OSV mirror via env, with a host allowlist.
- Tools.
gosec(G107 url taint), egress proxy allowlist,semgrep.
R7 — Cache poisoning of aliases.json (B3)¶
- ✅ Mitigated as to permission and resilient parsing; integrity/signing still absent.
- Description.
internal/cache/store.gois a JSON KV at~/.cache/quorum/aliases.json(atomic write via.tmp+rename). A corrupted/tampered file is silently degraded to an empty cache; an attacker with FS access could map a GHSA to the wrong CVE, affecting correlation. - Impact. Incorrect correlation (targeted false merge/split); localized obfuscation.
- Likelihood. Low (requires access to the user/runner FS).
- Severity. Medium.
- Mitigation (present).
- Perm
0o600on write (Putwrites the.tmpas0o600beforerename) — no longer world-readable. - Schema versioning:
diskFormat.SchemaVersion(schemaVersion = 1).Openonly adopts the file when it parses AND matches the schema ANDData != nil; a legacy/incompatible/corrupt file degrades to an empty cache instead of a mis-parsed read (single rebuild, no migration). - Atomic write; the cache is only an optimization (it never breaks the scan); OSV is the arbiter when the cache lacks the key.
- Remaining gaps. No per-entry integrity/signing; values trusted without cryptographic re-validation; no TTL.
- Recommendation.
- [x] ~~Perm
0600on the cache file~~ — done. - [x] ~~Schema version~~ — done (
schemaVersion). - [ ] In ephemeral CI, do not persist the cache between untrusted jobs, or use
--cache "". - [ ] Per-entry TTL and checksum/signature.
- Tools. FS permission control,
gosec(G306 file perms).
R8 — Findings exposure (B3 / output)¶
- ✅ Mitigated as to permission and secret values; artifact classification is an operational responsibility.
- Description. The report (SARIF/JSON/XML) contains vulnerabilities, misconfigs and detected secrets (trivy/dockle emit secret findings). This output is sensitive data: it reveals the target's attack surface and may contain text near secrets.
- Impact. Disclosure of security posture / exploitation clues to whoever reads the artifact.
- Likelihood. Medium (CI artifacts are often public or broadly accessible).
- Severity. Medium.
- Mitigation (present).
emitwrites with perm0o600(was0644).- Secret-value redaction: a trivy secret finding's
Matchis only persisted afterredactSecretText(internal/adapter/adapter.go) — long tokens ([A-Za-z0-9+/=_-]{12,}) become<4 chars>…REDACTED…, preserving locating context without leaking the value. The struct comment marksMatchas "sensitive: stored only after redaction". - Default output is stdout (does not persist by default);
--min-severityand--quietreduce noise; the.quorumignorebaseline can suppress known findings. - Remaining gaps. Redaction covers the long-token pattern; short/atypical secrets may escape; no automatic artifact-classification warning.
- Recommendation.
- [x] ~~Perm
0600for--output~~ — done. - [x] ~~Redact/mask secret values~~ — done (
redactSecretText). - [ ] Treat SARIF reports as restricted artifacts (not public by default in CI).
- [ ] Document artifact retention/purging.
- Tools. CI artifact-storage controls, DLP, ACL.
R9 — Tampered crosswalk / baseline (B3)¶
- Description. The crosswalk (YAML rule→control) and
.quorumignoreinfluence correlation and suppression.resolveCrosswalkDirfalls back automatically to/opt/quorum/crosswalkwhen./crosswalkdoes not exist (and the flag was not passed). A tampered baseline can suppress real findings; a tampered crosswalk can force incorrect merges. - Impact. Hiding findings (improper suppression) or loss of granularity.
- Likelihood. Low–Medium.
- Severity. Medium.
- Mitigation (present). Suppressions are always logged
(
filtered: N suppressed by baseline …); the false split > false merge principle + a crosswalk derived from real output (favoringfalse split) limit silent merges; the bundled crosswalk (aws.yaml,azure.yaml,gcp.yaml,k8s.yaml) is part of the signed — and now attested (R4) — image, and the log showscrosswalk=N rules. - Gaps. User baseline/crosswalk are neither signed nor reviewed; the directory fallback can mask an empty crosswalk.
- Recommendation.
- [ ] Version
.quorumignore/crosswalk in the repo with mandatory review (CODEOWNERS). - [ ] Alert when 0 rules are loaded (there is already a
crosswalk=Nlog). - [ ] Optional: sign/validate a custom crosswalk.
- Tools. code review/CODEOWNERS, git history, PR policy.
R10 — RCE via a trojanized scanner parser/dependency (B5 ⟶ B2)¶
- Description. Combination of R4+R2: if a scanner binary is trojanized, it runs inside the trust boundary and its output feeds the parsers. It is the worst chained case.
- Impact. Full compromise of the container/runner.
- Likelihood. Low.
- Severity. Medium (high impact, low likelihood given the majority pin/checksum).
- Mitigation (present). Digest pin (trivy/kics + bases) and SHA256 checksum of the other
downloaded binaries (kubescape/polaris/kube-score/tfsec/terrascan/regula/conftest/dockle);
cosign + SLSA + SBOM attested on the Quorum image;
action.ymlrunscosign verifybefore running; the stdout cap (512 MiB) limits the damage of a binary's adversarial output. - Recommendation. See R4 (close grype/syft/checkov) and run the image with least privilege
(rootless,
--read-only,--cap-drop=ALL, no--privileged). - Tools. cosign, slsa-verifier, container runtime hardening.
R11 — Advisory-layer egress / AI output handling (B7, opt-in)¶
- ✅ Mitigated by design (off by default; the surface only exists under
--advice). - Description. The advisory layer (
--advice) is presentation-only: it attaches remediation templates, OWASP references and — when a provider is selected — a natural-language recommendation. It never touchescorrelationKey,fingerprint, the confidence score, aggregated severity, or thefail-ongate. Without--advicethe output is byte-identical. Three surfaces carry security weight: - Egress (remote provider).
--advice-provider=remotecalls an external API (QUORUM_ADVICE_API_KEY). Because data leaves the host it is gated on explicit consent (--advice-allow-egress), blocked by--offline("--advice-provider=remote is disabled by --offline") and it refuses--fix(that would upload source). Only the normalized finding (title, path, control) is sent — never source code. Phases 0 (deterministic templates/internal/enrich) and 2 (lexical/semantic RAG over the digest-pinned OWASP corpus,internal/rag) run fully local, no egress. - AI output handling. LLM output is untrusted text. It is treated as advisory only
and each AI attachment is labeled "AI-generated, advisory only"; it never changes the
gate.
--advice-provider=local(e.g. Ollama) and--fix=suggestare reproducible (temperature=0+ an on-disk cache keyed byfingerprint+provider+model) and degrade gracefully: if the model is unreachable the report ships without AI advice and the scan never fails. - Verify-the-fix.
--fix=suggestproposes a patch that must pass a re-scan: apply to a temp copy, re-scan with the same scanner, keep the suggestion only if the finding is gone and the file still parses. It never auto-applies. - Impact. (a) With the remote provider, the normalized finding is disclosed to an external API (metadata about the target's posture). (b) Forged/hallucinated AI text if the endpoint is rogue — but it is advisory-only and cannot flip the gate or the confidence.
- Likelihood. Low (the whole layer is opt-in and off by default; remote additionally needs consent + is offline-blocked).
- Severity. Low.
- Mitigation (present). Off by default (byte-identical output without
--advice); presentation-only (isolated from correlation/confidence/gate); remote egress consent-gated (--advice-allow-egress) and--offline-blocked; remote refuses--fix; only the normalized finding is sent, never source; AI attachments explicitly labeled; graceful degradation on an unreachable model; verify-the-fix never auto-applies; the knowledge pack is digest-pinned and attested (R4). New metricsquorum_advice_enriched,quorum_advice_provider,quorum_advice_fix{stage=proposed|verified}make the layer's behavior observable only under--advice. - Remaining gaps. The remote provider still discloses finding metadata to a third party by
design (mitigate with
--advice-provider=localor--offline); trust in the AI text is the operator's (it is advisory, never gating). - Recommendation.
- [x] ~~Gate remote egress on explicit consent and block it under
--offline~~ — done. - [x] ~~Keep the layer presentation-only and off by default~~ — done.
- [ ] Prefer
--advice-provider=localfor sensitive targets; keep--offlinefor air-gap. - [ ] Rotate/scope
QUORUM_ADVICE_API_KEY; never echo it in logs (action.ymlforwards it via env, does not print it). - Tools. egress proxy allowlist, secret management for the API key,
internal/evalsharness (measures deterministic remediation coverage, OWASP reference relevance, and the verify-the-fix rate in CI). See AI and the design proposal 21-proposta-ia.md.
5. OWASP Top 10 (2021) — mapping to the CLI/Docker context¶
| Category | Applicability to Quorum | Risk(s) | State |
|---|---|---|---|
| A01 Broken Access Control | Partial — no authz of its own; access = FS/CI runner | R3, R7, R8 | Depends on environment (0o600 perms) |
| A02 Cryptographic Failures | OSV over HTTPS; 0o600 artifacts; secrets redacted |
R6, R8 | OK (TLS + redaction) |
| A03 Injection | Central — command/argument injection | R1 | ✅ Mitigated (argv, no shell; target - refused) |
| A04 Insecure Design | false split > false merge, transparent status | R2, R9 | Strong by design |
| A05 Security Misconfiguration | 0o600 perms (report+cache), crosswalk fallback, Docker run |
R3, R7, R8 | ✅ Improved (permissions closed) |
| A06 Vulnerable & Outdated Components | 12 scanners/base image bundled | R4, R10 | ✅ Mitigated (pin/checksum + SLSA/SBOM; grype/syft/checkov remain) |
| A07 Identification & Auth Failures | N/A — no accounts/auth | — | N/A |
| A08 Software & Data Integrity Failures | cosign + SLSA + SBOM attested (image, binaries, knowledge pack); versioned cache; unsigned baseline | R4, R7, R9, R11 | ✅ Mostly mitigated |
| A09 Logging & Monitoring Failures | Status/suppression logs (text/json); Prometheus metrics |
R9 | OK (CLI scope) |
| A10 SSRF | OSV.dev (fixed BaseURL; validated id + PathEscape); opt-in remote advice endpoint is consent-gated + --offline-blocked |
R6, R11 | ✅ Mitigated (metadata leak remains) |
6. OWASP API Security Top 10 (2023) — mapped to the CLI¶
Quorum exposes no REST/HTTP API (no server, no endpoints) — N/A by architecture. It is a
client of the OSV.dev API (and, only under --advice-provider, of an AI endpoint). The
API Top 10 applies by analogy to that consumption:
| Item | Applicability | Note |
|---|---|---|
| API1 BOLA | N/A | No exposed objects/users |
| API2 Broken Authentication | N/A | OSV is a public endpoint without auth (the opt-in remote advice API uses QUORUM_ADVICE_API_KEY) |
| API3 BOPLA | Partial | Quorum trusts fields of the OSV response (R6) — treated as untrusted; AI output is advisory-only (R11) |
| API4 Unrestricted Resource Consumption | ✅ Mitigated | 8s timeout + MaxRetries=2 + backoff on the OSV client |
| API5 BFLA | N/A | No functions/levels |
| API6 Unrestricted Business Flows | N/A | — |
| API7 SSRF | ✅ Mitigated | Fixed BaseURL; validated id + PathEscape (R6); metadata leak remains |
| API8 Security Misconfiguration | Partial | --offline for air-gap; remote advice is off + consent-gated (R11) |
| API9 Improper Inventory | N/A | A single known endpoint |
| API10 Unsafe Consumption of APIs | ✅ Mitigated | id validated/encoded, response treated as untrusted (R6); AI text labeled advisory-only (R11) |
Future proposal (clearly separated): if Quorum ever exposes an API/daemon, this Top 10 becomes primary (authn/z, rate limit, BOLA). Today it is N/A by design.
7. OWASP ASVS 5.0 — controls applicable to the scope¶
Recommended target level: ASVS L1 (with L2 where applicable to a command-line tool).
| ASVS chapter | Relevant requirement | State in Quorum |
|---|---|---|
| V1 Encoding/Sanitization | Treat scanner output, OSV response and AI output as untrusted | Partial (R2: cap + redaction done; SARIF sanitization pending; R6 id encoded; R11 AI advisory-only) |
| V2 Validation | Validate target, --output, --log-format, OSV id |
✅ Covered (R1/R3/R6); semantic image/path validation remains |
| V5 File Handling | Path traversal in --output/cache |
✅ --output (R3, Clean+0o600) and aliases.json cache (R7, 0o600+schema) mitigated |
| V8 Data Protection | Findings/secrets at rest | ✅ Mitigated (0o600 + redaction, R8) |
| V10 Communication | TLS for OSV | OK |
| V12 Secure Comms / Egress | --offline, host allowlist, advisory egress consent |
Partial (R6: --offline ok; allowlist pending; R11: remote egress consent-gated + offline-blocked) |
| V14 Configuration | Dependency pinning, build hardening | ✅ Mostly mitigated (R4: pin/checksum + SLSA/SBOM + attested knowledge pack) |
| V50 (Authn) / V51 (Session) | Authentication/session | N/A — no users |
8. OWASP SAMM — process maturity¶
| SAMM function | Practice | Evidence in the project | Target maturity |
|---|---|---|---|
| Governance | Education & Guidance | DESIGN.md §12/§14, this doc, GitHub Pages (MkDocs Material) | 2 |
| Design | Threat Assessment | STRIDE/DREAD in this doc | 2 |
| Design | Security Requirements | false split > false merge; 0 findings ≠ safe | 2 |
| Implementation | Secure Build | Dockerfile pin/checksum, GoReleaser, cosign + SLSA + SBOM + attested knowledge pack | 3 |
| Implementation | Software Dependencies | Pin/checksum of 10/12 scanners; grype/syft/checkov remain | 2 |
| Verification | Security Testing | Per-adapter contract tests; CI coverage; E2E; internal/evals harness |
2→3 |
| Verification | Architecture Assessment | Trust boundaries (§1) | 2 |
| Operations | Incident Mgmt | Transparent per-scanner status; Prometheus metrics | 1→2 (define runbook) |
9. OWASP MASVS — N/A¶
Quorum is not a mobile application: there is no iOS/Android app, no on-device storage, no mobile IPC, no WebView. All MASVS controls (STORAGE, CRYPTO, AUTH, NETWORK, PLATFORM, CODE, RESILIENCE) are N/A. The equivalent themes of protecting data at rest and in transit are covered by ASVS (§7) and by risks R6/R8.
10. OWASP LLM Top 10 — applies only when the opt-in AI layer is enabled¶
The Quorum core uses no AI/LLM: correlation and consensus are deterministic
(correlationKey, Fingerprint = sha256(correlationKey), a confidence formula based on
engine diversity/severity/authoritative confirmation). The advisory layer (--advice) is
opt-in and off by default, and even then Phase 0 (curated templates, internal/enrich)
and Phase 2 (RAG over the digest-pinned OWASP corpus, internal/rag) are deterministic and
model-free. An actual LLM is queried only under --advice-provider=local|remote
(internal/advisor). Therefore the LLM Top 10 does not apply to the deterministic core;
when the AI layer is enabled, the relevant items are already addressed by the R11 design:
| LLM risk | Where it applies | State when AI enabled |
|---|---|---|
| LLM01 Prompt Injection | A hostile finding's text becomes part of the prompt | Advisory-only output; never gates; temperature=0 |
| LLM02 Insecure Output Handling | LLM text flows into the report | Labeled "AI-generated, advisory only"; not sanitized into the gate/confidence |
| LLM05 Supply Chain | OWASP corpus / model | Corpus is digest-pinned + attested (R4); local model runs on-host |
| LLM06 Sensitive Information Disclosure | Remote provider egress | Consent-gated (--advice-allow-egress) + --offline-blocked; only the normalized finding is sent, never source; --fix refused remotely (R11) |
| LLM10 Model Theft | N/A | Quorum ships no model of its own |
Honest framing. Without
--advicethere is no AI at all and LLM01–LLM10 are inert. With--advice-provider=local|remotethe layer stays presentation-only, so a compromised model can at most produce misleading advisory text — it cannot changecorrelationKey,fingerprint, confidence, aggregated severity, or thefail-ongate. See AI and 21-proposta-ia.md.
11. MITRE ATT&CK — relevant TTPs (attacker targets the pipeline)¶
| Tactic | Technique | Relation to Quorum | Mitigation |
|---|---|---|---|
| Initial Access | T1195 Supply Chain Compromise | Trojanized scanner binary / image (R4/R10) | Pin/checksum, cosign + SLSA + SBOM + attested knowledge pack |
| Execution | T1059 Command/Scripting Interpreter | Shell-out (R1) | argv, no shell; target - refused |
| Execution | T1203 Exploitation for Client Execution | Parser exploited by forged output (R2) | structured parsing, 512 MiB cap, fuzzing |
| Persistence | T1554 Compromise Host Software Binaries | Replace scanner in PATH (:slim mode) |
signed :full image; controlled PATH |
| Defense Evasion | T1027/T1070 Obfuscation/Indicator Removal | Tampered baseline/crosswalk (R9) | suppressions always logged |
| Discovery | T1082/T1083 System/File Discovery | Findings reveal surface (R8) | 0o600 perms, redaction, restrict artifacts |
| Exfiltration | T1041 Exfil over C2 / web | IDs leaked via OSV (R6); finding metadata via remote advice (R11) | --offline, validated id, egress allowlist; remote advice consent-gated + offline-blocked |
| Impact | T1499 Endpoint DoS | Giant target / zip bomb (R5) | 20 GiB target cap, 512 MiB stdout cap, timeout |
12. MITRE ATLAS — N/A (core); opt-in AI layer noted¶
ATLAS covers threats to machine-learning systems (model evasion, training-data poisoning,
model extraction). The Quorum core has no ML/AI component (see §10), so no ATLAS
technique applies to it. The poisoning relevant here is of the alias cache (R7), handled
as classic data tampering, not ML data poisoning. When the opt-in AI layer is enabled
(--advice-provider=local|remote), Quorum acts as a client of an external model rather than
training/serving one, so ATLAS still barely applies — the residual concern (untrusted model
output, provider egress) is covered by R11, and the RAG corpus is digest-pinned + attested
(R4).
13. LINDDUN — privacy¶
Personal data processed by Quorum is minimal and incidental (no accounts; there may be metadata in paths/detected secrets). Mapping:
| LINDDUN threat | Applicability | Risk | Mitigation |
|---|---|---|---|
| Linkability | Low | Vuln IDs queried on OSV correlatable | --offline |
| Identifiability | Low | Paths/secrets may contain personal data | min-severity, redaction (redactSecretText) |
| Non-repudiation | N/A | No user actions to repudiate | — |
| Detectability | Medium | An OSV query reveals a vuln's existence (R6) | --offline, internal mirror |
| Disclosure of info | Medium | Report/secrets exposed (R8); finding metadata to a remote advice provider (R11) | 0o600 perms, redaction, artifact restriction; --advice-provider=local/--offline |
| Unawareness | Low | — | documentation (GitHub Pages) |
| Non-compliance | Medium | LGPD if findings contain personal data | see §19 |
14. NIST Cybersecurity Framework (CSF 2.0)¶
| Function | State | Evidence / gap |
|---|---|---|
| GOVERN | Partial | Principles and risks documented; incident runbook missing |
| IDENTIFY | Good | Trust boundaries, dependency inventory (Dockerfile), attested SBOM |
| PROTECT | Good | cosign + SLSA + SBOM + attested knowledge pack, --offline, 0o600 perms, DoS caps; grype/syft/checkov pin remains |
| DETECT | Good (scope) | Per-scanner status, 0 findings ≠ safe, suppressions logged, Prometheus metrics |
| RESPOND | Initial | Actionable messages (OOM/probe); no formal process |
| RECOVER | N/A→Initial | Stateless tool; image rebuild restores state |
15. NIST SP 800-53 (selected controls)¶
| Family/Control | Application | State |
|---|---|---|
| AC-3/AC-6 Least Privilege | Run container rootless, --cap-drop=ALL |
Recommended (operational gap) |
| AU-2/AU-12 Audit Logging | Execution/suppression logs (text/json); metrics |
OK (scope) |
| CM-7 Least Functionality | --scanners limits the surface; advisory off by default |
OK |
| SA-12 Supply Chain Protection | Pin/checksum + cosign + SLSA + SBOM + attested knowledge pack | ✅ Mostly mitigated (R4) |
| SC-7 Boundary Protection | Egress only to OSV; --offline; remote advice consent-gated + offline-blocked |
Partial (R6/R11) |
| SC-8 Transmission Confidentiality | TLS OSV | OK |
| SI-7 Software/Info Integrity | Artifact signing/attestation (image, binaries, knowledge pack); versioned cache (0o600) |
✅ Mitigated (R4/R7) |
| SI-10 Input Validation | Validation of target/output/id/log-format | ✅ Covered (R1/R3/R6) |
16. CIS Controls v8 (relevant)¶
| Control | Application | State |
|---|---|---|
| 2 Inventory of Software | Dockerfile lists scanners/versions; attested SBOM | OK |
| 4 Secure Configuration | Container hardening, 0o600 perms |
✅ Improved |
| 7 Continuous Vuln Mgmt | Quorum's purpose (consensus, 12 scanners) | Core |
| 8 Audit Log Mgmt | Logs (text/json) + Prometheus metrics |
OK |
| 16 Application Software Security | This doc's SDL, contract tests, CI coverage, internal/evals |
Partial |
| 18 Penetration Testing | Parser fuzzing recommended | Gap |
17. ISO/IEC 27001:2022 (Annex A — relevant controls)¶
| Control | Theme | State |
|---|---|---|
| A.5.23 Security in cloud services | Use of OSV.dev; opt-in remote advice API | Partial (--offline, consent-gated egress) |
| A.8.8 Technical vulnerability management | End product | Core |
| A.8.28 Secure coding | argv, structured parsing, caps, redaction | ✅ Improved |
| A.8.30 Outsourced/3rd-party | 12 third-party scanners bundled | ✅ Mostly mitigated (pin/checksum) |
| A.5.31 Legal requirements (LGPD) | Data in findings | See §19 |
| A.8.16 Monitoring | Per-scanner status + metrics | OK |
18. PCI DSS v4.0¶
Quorum stores/processes no cardholder data (no CHD/SAD). It is a compliance-support tool, not a system in CDE scope.
| Requirement | Relation | State |
|---|---|---|
| Req 6.2/6.3 Secure software and patching | Quorum helps evidence SCA/IaC/misconfig/K8s | Support |
| Req 6.4.3 Software components | Attested SBOM/pin | ✅ Reinforced support |
| Req 11.3 Vulnerability detection | Consensus scans in CI (12 scanners) | Support |
| Req 12 Policies | Integrate Quorum into the process | Organizational |
| Others (1–4, 7–10) | N/A — no CHD, no CDE network | N/A |
19. LGPD (Law 13.709/2018)¶
Quorum collects no personal data from users (no accounts, no user telemetry; --metrics
writes only aggregated counts). Residual risk: personal data may appear incidentally in
findings (paths, detected secrets/credentials, emails in code).
| Aspect | Situation | Action |
|---|---|---|
| Direct collection of personal data | None | — |
| Incidental personal data in reports | Possible (R8) | Treat the report as restricted (0o600); secret redaction |
| International transfer | An OSV.dev query (USA) sends only validated vuln IDs, not personal data; the opt-in remote advice provider sends only the normalized finding, never source (R11) | --offline for air-gap; --advice-provider=local |
| Legal basis / minimization | Default output to stdout; --min-severity |
Retain the minimum |
| Data-subject rights | N/A (no registration) | — |
20. Present mitigations vs. gaps (executive summary)¶
quadrantChart
title Mitigations: coverage vs. effort to close gaps
x-axis "Low current coverage" --> "High current coverage"
y-axis "Low effort" --> "High effort"
quadrant-1 "Maintain"
quadrant-2 "Invest"
quadrant-3 "Quick wins"
quadrant-4 "Plan"
"argv no shell + target - (R1)": [0.88, 0.2]
"cosign + SLSA + SBOM (R4)": [0.82, 0.55]
"--offline + validated id (R6)": [0.85, 0.2]
"transparent status (R5)": [0.85, 0.25]
"--output Clean+0600 (R3)": [0.85, 0.2]
"cache 0600 + schema (R7)": [0.8, 0.2]
"secret redaction (R8)": [0.8, 0.25]
"DoS caps 512MiB/20GiB (R5)": [0.82, 0.3]
"advisory opt-in + egress gate (R11)": [0.86, 0.22]
"pin grype/syft/checkov (R4)": [0.4, 0.45]
"SARIF sanitization (R2)": [0.3, 0.4]
Already present (strengths):
- Shell-out without sh -c — arguments via argv (exec.CommandContext); a target starting
with - is refused (validateTargetRef).
- Structured struct-based parsing + per-adapter contract tests (12 adapters) with versioned
fixtures; 512 MiB stdout cap (capWriter); secret redaction.
- --offline turns OSV off; OSV client with 8s timeout / retry / backoff and graceful
degradation; id validated (vulnIDPattern) + url.PathEscape.
- --output normalized (filepath.Clean) and written 0o600; aliases.json cache 0o600 +
schemaVersion; 20 GiB target size cap (checkTargetSize).
- Hardened distribution: keyless cosign (OIDC) with retry + SLSA build-provenance attestation
+ attested SPDX SBOM (image and per-binary) + an attested advisory knowledge pack
(knowledge job), all re-verified in the release; action.yml runs cosign verify before
running and auto-mounts the Docker socket for type: image; the moving v0 tag is advanced
automatically by tag-major.yml.
- Digest pin of trivy/kics + bases; SHA256 checksum of
kubescape/polaris/kube-score/tfsec/terrascan/regula/conftest/dockle; pre-cached,
non-expiring Grype DB.
- Per-scanner timeout + version probe distinguishing timeout/OOM/absent; per-scanner status,
"0 findings is not proof of safety", logged suppressions; text/json logs + Prometheus
metrics (--metrics).
- false split > false merge design + a crosswalk derived from real output limit hiding.
- Advisory layer opt-in and presentation-only (byte-identical without --advice); remote
egress consent-gated (--advice-allow-egress) + --offline-blocked; only the normalized
finding is sent, never source; verify-the-fix never auto-applies; internal/evals measures
coverage/relevance/fix-rate in CI.
Priority gaps (actionable backlog):
- [ ] R4 Pin the Anchore install.sh (grype/syft) by commit SHA; pip install
--require-hashes for Checkov; pin the cosign version in action.yml.
- [ ] R2 Sanitize/escape textual fields (Title/Description/Location) before SARIF
consumed by third-party UIs; add adversarial fixtures.
- [ ] R5 Decompression-depth limit (beyond the on-disk size cap).
- [ ] R6 Host allowlist / internal OSV mirror via env; document --offline.
- [ ] R7 Per-entry cache TTL and checksum/signature; --no-cache in ephemeral CI.
- [ ] R9 Sign/validate custom crosswalk and baseline; alert on 0 rules.
21. Operational hardening checklist (CI/Docker)¶
- [ ] Run
quorum:fullrootless,--read-only,--cap-drop=ALL, no--privileged. - [ ] Impose
--memory,--pids-limitandtmpfson/tmp(complements the target/stdout cap, R5). - [ ] Restrict the runner's egress to
api.osv.dev(or use--offline). - [ ] Verify the image with
cosign verify(theaction.ymldefault) and the SLSA/SBOM attestation on the consumer (gh attestation verify); verify the knowledge pack too (gh attestation verify knowledge/owasp/corpus.yaml). - [ ] Pin the image by
@sha256in the pipeline, not by the:fulltag (the action'sv0is moving by design and advances on every release). - [ ] Treat SARIF reports (
0o600) as restricted artifacts; define retention/purging. - [ ] Do not persist
aliases.jsonbetween untrusted jobs (or--cache ""). - [ ] Limit scanners with
--scannersto what is needed (least functionality / CM-7). - [ ] Tune
QUORUM_MAX_OUTPUT_BYTES/QUORUM_MAX_TARGET_BYTESonly when a legitimate target requires it; keep the defaults (512 MiB / 20 GiB) otherwise. - [ ] Pass platform keys (e.g. Prisma/Bridgecrew) via
QUORUM_CHECKOV_ARGS/thecheckov-argsinput — never in logs (action.ymldoes not echo those envs). - [ ] Keep the advisory layer off unless needed; for sensitive targets prefer
--advice-provider=localand never combine remote advice with--fix; passQUORUM_ADVICE_API_KEYvia theadvice-api-keyinput, never in logs.
Assumptions¶
- Analyzed version: v0.8.3 (revision 2026-07-04), from the code on
main. Claims were verified ininternal/adapter/adapter.go(runCmd,capWriter,redactSecretText,extraArgs),internal/adapter/{trivy,grype,conftest}.goand the remaining adapters (12 in total),cmd/quorum/scan.go(emit/runScan/validateTargetRef/checkTargetSize+ the advisory flag wiring),internal/alias/osv.go(vulnIDPattern/PathEscape),internal/cache/store.go(schemaVersion/0o600), the advisory packagesinternal/{enrich,rag,advisor,evals},knowledge/*.yaml+knowledge/owasp/corpus.yaml,Dockerfile.full,action.yml,.github/workflows/release.ymlandDESIGN.md(§12, §14). DESIGN.mdcarries the supply-chain discussion in §12 (CLI & Docker) and the risks in §14 (Risks and mitigations); both were addressed.- Confirmed that there is no intermediate shell in the shell-out (no
sh -c); classic command injection (R1) is mitigated, and the argument injection residue (a target starting with-) was also closed byvalidateTargetRef. - The DREAD scores are qualitative estimates for prioritization, not empirical measurements; the "State" column reflects the code read in this revision (many items formerly "Gap/Partial" were promoted to "Mitigated").
- Claims about cosign, SLSA, SBOM, the attested knowledge pack, GHCR
:full/:slim, GoReleaser and thev0tag are based onrelease.yml,action.ymlandDockerfile.fullread directly (see also Supply Chain). - The advisory layer (
--advice) is assumed opt-in and off by default and presentation-only: without it the output is byte-identical and no AI runs; it never touchescorrelationKey/fingerprint/confidence/aggregated severity/thefail-ongate. The remote provider is the only egress path and is consent-gated (--advice-allow-egress) and--offline-blocked; it sends only the normalized finding, never source. This framing is why the OWASP LLM Top 10 is treated as conditional (§10) rather than flatly N/A. - The SARIF report is assumed to be consumable by third-party UIs (GitHub code scanning), which motivates the remaining recommendation to sanitize textual fields (R2).
- MISCONFIG/IaC and K8S_POSTURE consensus rests on a crosswalk derived from real output
(favoring
false split); RBAC stays single-engine (kubescape RBAC needs cluster context) — documented as a decision, not a gap. - Frameworks marked N/A (MASVS, its own REST API, and most of PCI DSS) are so for lack of, respectively, a mobile app, an HTTP server and cardholder data. MITRE ATLAS and the OWASP LLM Top 10 are N/A for the deterministic core and become partially relevant only when the opt-in AI layer is enabled — reassess if the product scope changes (see "Future proposals").