11 - DevOps¶
This section describes, faithfully to the code, how Quorum (quorum-sec-scan, v0.8.3 — revision 2026-07-04) is built, tested, versioned and distributed. Quorum is a CLI/Docker consensus security scanning tool written in Go 1.26; there is no long-running service, web frontend, database or REST API. Because of that, "DevOps" here essentially means release engineering: a Continuous Integration (CI) pipeline that proves consensus between scanners actually happens and measures test coverage, and a Continuous Delivery (CD) pipeline that publishes signed Docker images and signed native binaries, both with SLSA provenance attestation and attested SPDX SBOM, verified in the release itself. The artifact is immutable and versioned; "deploy" is publish; "rollback" is re-pointing a moving tag to an earlier digest.
Every fact below derives directly from:
.github/workflows/ci.yml— jobbuild-test(with coverage)..github/workflows/e2e.yml— jobconsensus..github/workflows/release.yml— jobsimages,binariesandknowledge..github/workflows/tag-major.yml— jobmove(advances the moving tagsv0/v0.2on every semver release)..github/workflows/docs.yml— jobsbuild/deploy(bilingual MkDocs Material site on GitHub Pages)..goreleaser.yaml— native binary build.action.yml— composite GitHub Action wrapping the:fullimage.
1. Overview¶
flowchart LR
dev[Developer] -->|feature branch| pr[Pull Request]
pr -->|on: pull_request| ci[ci.yml<br/>build-test + coverage]
pr -->|on: pull_request| e2e[e2e.yml<br/>consensus]
ci -->|green| merge{merge into main}
e2e -->|green| merge
merge -->|push: main| ci2[ci.yml + e2e.yml<br/>re-run on main]
merge -->|push: main + docs/**| docs[docs.yml<br/>GitHub Pages]
merge -->|semver tag vX.Y.Z| rel[release.yml]
merge -->|semver tag vX.Y.Z| tm[tag-major.yml<br/>advance v0 / v0.2]
rel --> ghcr[(GHCR<br/>:full/:slim images)]
rel --> ghrel[(GitHub Release<br/>binaries + checksums)]
rel --> know[(knowledge pack<br/>SLSA attestation)]
rel --> sig[cosign keyless + SLSA provenance + SPDX SBOM]
| Stage | Workflow | Trigger | What it proves / does |
|---|---|---|---|
| Build, test & coverage | ci.yml |
push to main, any pull_request |
Compiles, go vet, go test -race with atomic coverage, publishes the coverage summary, uploads coverage.out, builds the binary, smoke (list-scanners) |
| Consensus proof | e2e.yml |
push to main, any pull_request, workflow_dispatch |
Runs real scanners over known targets and fails if no finding is corroborated by two engines |
| Documentation | docs.yml |
push to main touching docs/**, mkdocs.yml or the workflow itself; workflow_dispatch |
Builds the bilingual MkDocs Material site (mkdocs-static-i18n) and publishes it to GitHub Pages |
| Release | release.yml |
push of tag v[0-9]+.[0-9]+.[0-9]+, workflow_dispatch |
Publishes images and binaries, attests the knowledge pack, signs (cosign keyless with retry), attests and verifies SLSA provenance + SPDX SBOM |
| Moving-tag advance | tag-major.yml |
push of tag v[0-9]+.[0-9]+.[0-9]+ |
Repositions v0 and v0.2 to the release commit (Action pin) |
2. Continuous Integration (CI)¶
2.1 ci.yml — job build-test¶
Trigger: push to main and any pull_request. Runner: ubuntu-latest. Go 1.26 with cache enabled.
Exact steps:
| # | Step | Command | Fails if |
|---|---|---|---|
| 1 | Checkout | actions/checkout@v4 |
— |
| 2 | Setup Go | actions/setup-go@v5 (go-version: "1.26", cache: true) |
— |
| 3 | Vet | go vet ./... |
Static/suspicious error reported by vet |
| 4 | Test (race + coverage) | go test -race -covermode=atomic -coverprofile=coverage.out ./... |
Any test fails or a data race is detected |
| 5 | Coverage summary | go tool cover -func=coverage.out \| tail -1 (also written to GITHUB_STEP_SUMMARY) |
— (informational) |
| 6 | Upload coverage | actions/upload-artifact@v4 (name: coverage, path: coverage.out, if-no-files-found: warn) |
— |
| 7 | Build | go build -trimpath -o dist/quorum ./cmd/quorum |
Compilation failure |
| 8 | Smoke | ./dist/quorum list-scanners |
Binary does not run the basic command |
Relevant points:
-raceis mandatory. The orchestrator does parallel fan-out with goroutines (one scanner per goroutine), so the race detector is the first line of defense against concurrency regressions.- Test coverage in CI.
go testruns with-covermode=atomic -coverprofile=coverage.out; theCoverage summarystep prints the total line (go tool cover -func) into the log and the step summary, andcoverage.outis published as an artifact for later inspection. Coverage is observed on every push/PR (there is no numeric gate that fails the build). - Each adapter's contract tests run against fixtures in
internal/adapter/testdatainsidego test ./...— they do not require the scanners installed, being deterministic and fast. Current coverage spans the 12 adapters (trivy,grype,checkov,kics,dockle,kubescape,polaris,kube-score,terrascan,tfsec,regula,conftest) plus the real-data tests (realdata_test.go). - The Smoke step guarantees the binary boots and the scanner registry (
list-scanners) responds, without depending on the network or external scanners.
Advisory layer under test. The advisory layer added in v0.8.3 (opt-in via
--advice, packagesinternal/enrich,internal/rag,internal/advisor) is covered by the same deterministicgo test ./...— including theinternal/evalsharness that measures deterministic remediation coverage, OWASP reference relevance and the verify-the-fix rate. These evals run in CI with no heavy model, so they stay fast and deterministic. Without--advice, output is byte-identical to the deterministic core.
2.2 e2e.yml — job consensus¶
This workflow is the living proof of the product's principle: "0 findings is not proof of safety", and consensus only counts with real scanners, not fixtures. Trigger: push to main, any pull_request and workflow_dispatch (manual run).
Sequence:
- Checkout + Setup Go 1.26 (with cache).
- Build quorum to
dist/quorumand adddist/and~/.local/bintoGITHUB_PATH. - Install scanners by direct download of releases (pinned versions):
- Trivy
0.71.2, Grype0.114.0(a comment in the workflow warns that old vulnerability-DB schemas of Grype are retired by Anchore), Checkov viapipx install checkov. - Verify scanners and pre-fetch DB:
trivy --version,grype version,checkov --version,grype db update(fails early if the DB cannot be downloaded),docker pull alpine:3.10(ensures both engines resolve the same local image) andquorum list-scanners. - IaC consensus (Trivy + Checkov over
examples/terraform,--type repo,--offline): requiressummary.multiDetected >= 1, otherwise it emits::error::no cross-engine IaC consensus reachedand fails. - SCA consensus (Trivy + Grype over
alpine:3.10,--type image,--offline): samemultiDetected >= 1rule. - Upload reports (
iac.json,sca.json) as an artifact, withif: always().
--offlinedisables alias lookups on OSV.dev, making the e2e deterministic and independent of network variation with respect to alias correlation.Note on e2e scope.
consensusproves cross-engine for SCA (Trivy + Grype) and IaC (Trivy + Checkov). Consensus for MISCONFIG/IaC and K8S_POSTURE across the other engines (kics, dockle, kubescape, polaris, kube-score, terrascan, tfsec, regula, conftest) is derived at runtime via the crosswalk (crosswalk/*.yaml); the e2e keeps the minimal deterministic pair that runs fast in CI. The advisory layer is not exercised here — it is presentation-only and never touches consensus.
flowchart TD
a[Build quorum] --> b[Install trivy/grype/checkov<br/>pinned versions]
b --> c[grype db update + docker pull alpine:3.10]
c --> d[scan IaC: trivy+checkov]
c --> e[scan SCA: trivy+grype]
d --> f{multiDetected >= 1?}
e --> g{multiDetected >= 1?}
f -->|no| x1[::error:: no IaC consensus -> FAIL]
g -->|no| x2[::error:: no SCA consensus -> FAIL]
f -->|yes| h[upload iac.json/sca.json]
g -->|yes| h
2.3 docs.yml — bilingual MkDocs Material site on GitHub Pages¶
The documentation (this and the other sections in docs/) is published as a static site on GitHub Pages via MkDocs Material. Since v0.8.3 the site is bilingual — English (default) and Brazilian Portuguese — built with the mkdocs-static-i18n plugin.
- Trigger:
pushtomaintouchingdocs/**,mkdocs.ymlor.github/workflows/docs.yml; orworkflow_dispatch. Changes that do not touch the documentation do not rebuild the site. - Permissions:
contents: read,pages: write,id-token: write(Pages OIDC deploy). - Concurrency: group
pageswithcancel-in-progress: false— allows one publish at a time, without cancelling an in-progress deploy. - Job
build:actions/checkout@v4→actions/setup-python@v5(python-version: "3.x") →pip install "mkdocs-material==9.*" "mkdocs-static-i18n==1.*"→mkdocs build --site-dir site→actions/configure-pages@v5→actions/upload-pages-artifact@v3(path: site). - Job
deploy(needs: build):actions/deploy-pages@v4, on thegithub-pagesenvironment, exposes the published URL (page_url).
The site is a read-only projection of
docs/. There is no application build or runtime state — just static HTML generated from Markdown. Each page has an English (<file>.md) and a Portuguese (<file>.pt.md) source; the i18n plugin builds the language switcher.
3. Git flow and branching strategy¶
The flow is classic GitHub Flow, PR-based to main, with semver tag versioning and automatically advanced moving tags.
gitGraph
commit id: "main"
branch feature/xyz
commit id: "work"
commit id: "more work"
checkout main
merge feature/xyz tag: "PR + CI green"
commit id: "v0.8.3" tag: "v0.8.3"
Practical rules:
mainis the release line. All work happens on feature/fix branches (e.g.ci/auto-advance-major-tag,docs/fix-site-links, as seen in recent history).- Every change enters via Pull Request. The PR triggers
ci.yml(build-test + coverage) ande2e.yml(consensus). Both must be green before merge. - Merge into
mainre-runsci.ymlande2e.ymlon thepushtomain(defense in depth against merge races) and, ifdocs/**changed, triggersdocs.ymlto republish the site. - Release by semver tag. Only tags of the form
v[0-9]+.[0-9]+.[0-9]+(e.g.v0.8.3) triggerrelease.yml. There is no automatic release on merge — the tag is the explicit gesture of publication. - Moving tags
v0(major) andv0.2(minor) exist to pin the GitHub Action: consumers useuses: Martinez1991/quorum-sec-scan@v0. These tags are advanced automatically bytag-major.ymlon every semver release — no longer a manual step. Bothrelease.ymlandtag-major.ymlare restricted to full semver tags (X.Y.Z), so moving the Action pointer does not re-trigger a build or republish images.
3.1 tag-major.yml — automatic moving-tag advance¶
flowchart LR
t[push tag vX.Y.Z] --> j[job move]
j --> chk[checkout fetch-depth: 0]
chk --> calc["derive major=vX and minor=vX.Y<br/>(strip the suffix)"]
calc --> mv["git tag -f -a vX / vX.Y -> same commit"]
mv --> push["git push -f origin vX and vX.Y"]
push --> stop["does not re-trigger release.yml/tag-major.yml<br/>(semver scope + push via GITHUB_TOKEN)"]
- Trigger:
pushof tagv[0-9]+.[0-9]+.[0-9]+. Permission:contents: write. - Logic: from
github.ref_name(e.g.v0.8.3) it derivesmajor(v0) andminor(v0.8) and force-updates both annotated tags togithub.sha(git tag -f -a+git push -f), asgithub-actions[bot]. - No loop: the trigger only accepts full semver tags, and pushes made with
GITHUB_TOKENdo not trigger new workflows — that is why movingv0/v0.2neither rebuilds images nor re-invokes itself.
PR checklist (recommended)¶
- [ ] Branch created from an up-to-date
main. - [ ]
go vet ./...andgo test -race ./...pass locally. - [ ] New adapter parsers have a contract test against a fixture in
internal/adapter/testdata. - [ ] New crosswalk correlations (AWS/Azure/GCP/K8s) are backed by real scanner output (false split > false merge).
- [ ] CI (
build-test) green; coverage observed in the step summary. - [ ] E2E (
consensus) green — real consensus still happens. - [ ] If the advisory layer changed,
internal/evalsstill passes (remediation coverage, OWASP reference relevance, verify-the-fix rate) and non---adviceoutput stays byte-identical. - [ ] If you touched
docs/**/mkdocs.yml, confirm the site build (docs.yml) is green in both languages. - [ ] Commit messages follow conventional prefixes (
fix:,feat:,ci:, etc.);docs:/test:/chore:are filtered from the changelog (see.goreleaser.yaml).
4. Continuous Delivery (CD) — release.yml¶
Release trigger: push of tag v[0-9]+.[0-9]+.[0-9]+ or workflow_dispatch (with input version, default dev).
Workflow permissions (least privilege needed):
| Permission | Why |
|---|---|
contents: read (job images/knowledge) / contents: write (job binaries) |
Read the repo; create the release and upload binary assets |
packages: write |
Push images to GHCR |
id-token: write |
OIDC for keyless signing with cosign (Sigstore) and keyless attestation |
attestations: write |
SLSA build-provenance attestation and SPDX SBOM attestation |
4.1 Job images¶
Variant matrix (fail-fast: false):
| Variant | Dockerfile | Platforms | Contents |
|---|---|---|---|
full |
Dockerfile.full |
linux/amd64 |
All scanners bundled (scanner binaries are amd64; grype DB pre-cached with GRYPE_DB_VALIDATE_AGE=false) |
slim |
Dockerfile |
linux/amd64, linux/arm64 |
Orchestrator only |
Tag resolution (step meta), with image=ghcr.io/<owner>/<repo> lowercased and version derived from GITHUB_REF_NAME without the v prefix:
| Variant | Published tags |
|---|---|
full |
:full, :<version>, :<version>-full, :latest |
slim |
:slim, :<version>-slim |
Job pipeline:
- Checkout.
- Resolve version and image name (step
meta, writesimage,version,tagstoGITHUB_OUTPUT). - QEMU + Buildx (needed for the multi-arch
slimbuild). - Log in to GHCR with
GITHUB_TOKEN. - Install cosign (
sigstore/cosign-installer@v3). - Build & push via
docker/build-push-action@v6withprovenance: true,sbom: true, scoped GHA cache (quorum-<variant>,mode=max) andbuild-args: VERSION=<version>. - Sign image (keyless) with retry:
cosign sign --yes "${IMAGE}@${DIGEST}"wrapped in aretry()helper (4 attempts, backoff15s·i). Keyless signing depends on the GitHub OIDC endpoint + Sigstore (Fulcio/Rekor), which occasionally time out on long builds and can expire the OIDC token mid-sign; each attempt re-fetches a fresh token. It signs the (multi-arch) manifest digest once, covering every tag pointing at it. - Attest SLSA build provenance:
actions/attest-build-provenance@v2generates an SLSA v1 attestation (subject-digest= image digest,push-to-registry: true), recording how the image was built (workflow, commit, runner). - Verify provenance attestation (with retry):
gh attestation verify "oci://${IMAGE}@${DIGEST}" --repo ...under the sameretry()— re-verifies end-to-end (Sigstore transparency log + OIDC identity). A broken attestation fails the release. - Generate image SBOM (syft):
anchore/sbom-action@v0generatessbom.spdx.json(formatspdx-json) for the image by digest. - Attest image SBOM:
actions/attest-sbom@v2issues a first-class SPDX attestation (verifiable bygh attestation verify/cosign), withpush-to-registry: true, in addition to the BuildKitsbom: trueattestation from the build step. - Summary: writes variant, platforms, digest and tags to
GITHUB_STEP_SUMMARY.
4.2 Job binaries¶
Condition: if: github.ref_type == 'tag' (GoReleaser needs the tag). Additional permissions: contents: write, id-token: write, attestations: write.
Pipeline:
- Checkout with
fetch-depth: 0(GoReleaser needs full history for the changelog). - Setup Go 1.26.
- Install cosign.
- Install syft (
anchore/sbom-action/download-syft@v0) — used by GoReleaser to generate the file SBOMs. - GoReleaser (
goreleaser/goreleaser-action@v6,version: "~> v2",args: release --clean) — per.goreleaser.yaml: - Builds
CGO_ENABLED=0,-trimpath,ldflags -s -w -X main.version={{.Version}}. - Matrix
goos: [linux, darwin, windows]xgoarch: [amd64, arm64]. - Archives
quorum_<version>_<os>_<arch>(zip on Windows) includingREADME.md,README.pt-BR.md,LICENSEand thecrosswalk/**directory (bundles the default crosswalks — AWS/Azure/GCP/K8s). - SPDX SBOM per archive (
sboms,artifacts: archive, via syft) — each release ships a machine-readable bill of materials for the binaries. checksums.txtcovering all artifacts.- Keyless signature of the checksum file via
cosign sign-blob(produces${artifact}.sigand${artifact}.pem). Since the checksum pins the hashes of every artifact, the signature over it covers the entire release. - Creates the GitHub Release (
draft: false,prerelease: auto) with GitHub changelog (excludesdocs:/test:/chore:). - Attest SLSA build provenance for binaries:
subject-checksums: dist/checksums.txt— one attestation covering every listed artifact. - Verify binary provenance attestation (with retry):
gh attestation verify dist/quorum_*_linux_amd64.tar.gz --repo ...under theretry()helper (spot-check of one artifact).
4.3 Job knowledge — attesting the advisory knowledge pack¶
New in v0.8.3. The advisory layer's knowledge pack — the curated remediation templates plus the digest-pinned OWASP corpus (knowledge/*.yaml, knowledge/owasp/corpus.yaml) and the crosswalk/** mappings — ships inside the images, but is also a data artifact a consumer may want to verify independently. So it gets first-class SLSA build provenance, like the images and binaries.
- Permissions:
contents: read,id-token: write(keyless attestation via Sigstore OIDC),attestations: write. - Pipeline:
- Checkout.
- Compute knowledge checksums: a deterministic, sorted list of every pack file —
find knowledge crosswalk -type f \( -name '*.yaml' -o -name '*.yml' \) -print0 | sort -z | xargs -0 sha256sum > knowledge.sha256. - Attest knowledge provenance:
actions/attest-build-provenance@v2withsubject-checksums: knowledge.sha256— one attestation covering every pack file. - Verify knowledge attestation (with retry):
gh attestation verify knowledge/owasp/corpus.yaml --repo ...— spot-checks the OWASP corpus against its just-created attestation.
Because the corpus is digest-pinned and now carries a provenance attestation, the
quorum advise-indexstep (which embeds the corpus for semantic retrieval) preserves the same pin, and a consumer can verify the pack withgh attestation verify knowledge/owasp/corpus.yamlbefore trusting the advice it feeds.
flowchart TD
tag[push tag vX.Y.Z] --> imgs[job: images]
tag --> bins[job: binaries]
tag --> know[job: knowledge]
subgraph images[job images — full/slim matrix]
m1[meta: resolve tags] --> m2[buildx + qemu]
m2 --> m3[build-push provenance+sbom]
m3 --> m4[cosign sign digest + retry]
m4 --> m5[attest SLSA provenance]
m5 --> m6[gh attestation verify + retry]
m6 --> m7[syft SBOM spdx-json]
m7 --> m8[attest SBOM SPDX]
end
subgraph binaries[job binaries — GoReleaser]
b1[goreleaser release --clean] --> b2[archives + SBOM + checksums.txt]
b2 --> b3[cosign sign-blob checksums]
b3 --> b4[GitHub Release]
b4 --> b5[attest SLSA over checksums]
b5 --> b6[gh attestation verify spot-check + retry]
end
subgraph knowledge[job knowledge — advisory pack]
k1[sha256sum knowledge/ + crosswalk/] --> k2[attest SLSA over knowledge.sha256]
k2 --> k3[gh attestation verify corpus.yaml + retry]
end
m8 --> ok1[(GHCR: signed + attested images + SBOM)]
b6 --> ok2[(GitHub Release: signed + attested binaries + SBOM)]
k3 --> ok3[(knowledge pack: SLSA-attested)]
5. "Deploy" for a CLI/Docker¶
There is no runtime environment managed by this project. Deploy = publish immutable, verifiable artifacts. The unit of deployment is the consuming user or pipeline that pulls the image or downloads the binary.
| Traditional concept | Quorum equivalent |
|---|---|
| Server/runtime environment | N/A — no persistent service |
| Deploy | Publish images on GHCR + binaries Release on GitHub |
| Immutable version | Image digest (@sha256:...) and semver tag |
| Integrity/origin | cosign keyless (OIDC) + SLSA provenance attestation + attested SPDX SBOM (+ SLSA-attested knowledge pack) |
| Runtime configuration | CLI flags (--type, --scanners, --fail-on, --metrics, --log-format, the advisory flags --advice, --advice-provider, --fix, ...), per-scanner passthrough (QUORUM_<SCANNER>_ARGS) and Docker mounts |
The advisory layer is opt-in configuration, not a runtime toggle. All AI behavior is off by default and selected per invocation:
--adviceturns on the deterministic Phase 0/2 enrichment (remediation templates + digest-pinned OWASP RAG);--advice-provider=local|remoteand--fix=suggestadd the opt-in LLM phases. Without--advicethe output is byte-identical to the deterministic core, which still has no AI.
How the consumer verifies before using¶
Image:
cosign verify ghcr.io/martinez1991/quorum-sec-scan:slim \
--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:slim \
--repo Martinez1991/quorum-sec-scan
action.yml itself does this automatically: by default (verify: true) it cosign-verifies ghcr.io/martinez1991/quorum-sec-scan:full (installing cosign if needed) before running the image via docker run. On local image scans (type: image) the Action auto-mounts /var/run/docker.sock (input docker-socket), avoiding a false-zero from an unreachable image. Pinning the image by @sha256:... in production is recommended (input image). The Action also exposes all advisory inputs (advice, advice-provider, advice-endpoint, advice-model, advice-embed-model, advice-max, advice-cache, advice-allow-egress, advice-api-key, fix): for local it auto-adds host-gateway so the container can reach an on-host endpoint, and for remote it forwards the API key via env.
Knowledge pack (the advisory data artifact):
Binary (from a release):
cosign verify-blob checksums.txt \
--signature checksums.txt.sig \
--certificate checksums.txt.pem \
--certificate-identity-regexp \
"https://github.com/Martinez1991/quorum-sec-scan/.github/workflows/release.yml@.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
sha256sum -c checksums.txt
6. Rollback¶
Since artifacts are immutable and versioned, rollback is a re-pointing/pin operation, not "undoing a deploy".
For the consumer (recommended)¶
- Pin to an exact version: swap
:latest/:fullfor:<previous-version>-full(e.g.:0.8.2-full) or, ideally, for the digest@sha256:...of a known-good release. - Action: swap
@v0for an earlier fixed tag (@v0.8.2), or pinimage:to the desired digest.
| Scenario | Rollback action |
|---|---|
| New image with a regression | Re-pin to :<previous-version>-full or the previous digest |
| Buggy binary | Download the asset from the previous (signed) release |
Unstable Action @v0 |
Pin a specific @vX.Y.Z or pin image: by digest |
For the maintainer¶
- Re-tag the moving tag: move
:full/:latest/:v0/:v0.2back to the previous release digest. cosign signs the digest, so the previous release's signature/attestation stays valid when re-pointing the tag. Note thattag-major.ymlonly advancesv0/v0.2forward on the next semver release — a rollback of those tags is a manual maintainer intervention. - Forward-fix preferred: cutting a new semver tag (
vX.Y.Z+1) with the fix is the cleanest path, since each release is fully rebuilt, signed and attested — andtag-major.ymlre-aligns the moving tags automatically.
By design, an existing semver tag is not overwritten with different content. The semver tag is immutable; the moving tags (
full,latest,v0,v0.2) are what reposition.
Rollback checklist¶
- [ ] Identify the last known-good version (semver tag + digest).
- [ ] Consumers: re-pin to the previous version/digest (or
@vX.Y.Zof the Action). - [ ] Maintainer: if needed, re-point moving tags (
v0/v0.2/full/latest) to the previous digest. - [ ] Confirm
cosign verify+gh attestation verifyon the target artifact. - [ ] Open a forward-fix and cut a new semver tag as soon as possible (
tag-major.ymlre-alignsv0/v0.2).
7. Feature flags, Blue-Green and Canary¶
N/A for the product. These techniques assume a long-running service with live traffic that can be routed, switched or gradually migrated. Quorum is an ephemeral CLI/Docker process: it runs, produces a report (SARIF/JSON/XML) and exits. There is no traffic, no parallel replicas, no runtime state to switch.
| Technique | Status | Rationale |
|---|---|---|
| Feature flags | N/A | Behavior is controlled by CLI flags at invocation time (--scanners, --format, --fail-on, --offline, --metrics, --log-format, --advice, --fix, ...) and passthrough env (QUORUM_<SCANNER>_ARGS); there are no dynamic runtime toggles |
| Blue-Green | N/A | There is no "live" environment with a traffic pool to switch |
| Canary | N/A (in the product) | There is no fleet serving requests to roll out gradually |
How the CONSUMER can canary versions¶
Although the product does not support canary internally, the consuming pipeline can canary Quorum versions with standard CI strategies:
- Staggered pin: most repositories pin a stable version (digest/
@vX.Y.Z); a pilot repository adopts the new version before broad adoption. - Non-blocking parallel run: run the new version in a job with
continue-on-error: truealongside the stable version (gate), comparing thesummaryand theexit-codebefore promoting. workflow_dispatch/ version matrix: compare reports of two tags (stable vs. candidate) over the same target and only then update the pin.- Deterministic report comparison: use
--offlineto reduce network variance when comparingmultiDetected/detectionCountbetween versions.
flowchart LR
repoA[Production repos<br/>stable pin @vX.Y.Z] --> use[Stable Quorum]
repoP[Pilot repo / canary job] --> cand[Candidate Quorum @vX.Y.Z+1]
cand -->|compare summary/exit-code| dec{ok?}
dec -->|yes| promote[promote pin to new version]
dec -->|no| keep[keep previous pin]
A scan-behavior feature flag = simply choosing CLI flags per invocation. There is no — nor is there any desire for — a dynamic flag system for a single-run binary.
8. Summary of triggers and responsibilities¶
| Workflow | pull_request |
push: main |
tag vX.Y.Z |
workflow_dispatch |
|---|---|---|---|---|
ci.yml (build-test + coverage) |
yes | yes | — | — |
e2e.yml (consensus) |
yes | yes | — | yes |
docs.yml (GitHub Pages, i18n) |
— | yes (paths docs/**, mkdocs.yml, the workflow) |
— | yes |
release.yml (images+binaries+knowledge) |
— | — | yes | yes |
tag-major.yml (advance v0/v0.2) |
— | — | yes | — |
| Artifact | Where | Signature | Provenance | SBOM | Multi-arch |
|---|---|---|---|---|---|
:full image |
GHCR | cosign keyless (retry) | SLSA (verified) | SPDX attested (syft) + BuildKit | linux/amd64 |
:slim image |
GHCR | cosign keyless (retry) | SLSA (verified) | SPDX attested (syft) + BuildKit | linux/amd64, linux/arm64 |
| Binaries | GitHub Release | cosign sign-blob (over checksums) | SLSA (verified) | SPDX per archive (syft) | linux/darwin/windows x amd64/arm64 |
| Knowledge pack (templates + OWASP corpus + crosswalk) | in-image / repo data | — (checksum-listed) | SLSA (verified) | — | N/A |
Assumptions¶
- Dockerfile contents were not detailed line by line. The
:full/:slimvariants and their contents (bundled scanners, grype DB pre-cached withGRYPE_DB_VALIDATE_AGE=false) are based on therelease.ymlcomments, the.goreleaser.yamland the product description; the build step itself (docker/build-push-action@v6) was documented from the workflow, not from an internal analysis ofDockerfile/Dockerfile.full. secrets.GITHUB_TOKENis the default token provided by GitHub Actions; it is assumed that thepackages: write,id-token: write,attestations: writeandpages: writescopes are available in the repository (they are declared in the workflows).- Branch protection / required checks: the document describes the PR-to-
mainflow as practice; there is no branch-protection configuration file verifiable in the repository here, so the "required checks" rules are recommendations aligned with the workflows' behavior. Likewise, coverage is observed (summary + artifact) but there is no numeric gate that fails the build. - Consumer verification commands (cosign/
gh attestation verify) were extrapolated from the comments and verification steps present inrelease.ymlandaction.yml; the identity regexp uses the ownerMartinez1991, peraction.yml. v0/v0.2as moving tags of the Action: the repositioning is automated by the workflow.github/workflows/tag-major.yml, which runs on every semver release and force-updatesv0/v0.2to the same commit (git tag -f -a+git push -fasgithub-actions[bot]). These tags do not re-triggerrelease.yml/tag-major.ymlbecause both are restricted to full semver tags and pushes viaGITHUB_TOKENdo not chain new workflows.- GitHub Pages:
docs.ymlpublishes the MkDocs Material site; enabling Pages with "Build and deployment → Source: GitHub Actions" is a repository configuration prerequisite (documented in the workflow comment) that is not verifiable from the versioned files alone.
See also: README.md · README.pt-BR.md · DESIGN.md (supply chain §12, scanner status §14).