Supply chain
trstctl ships with a signed, attested, scanned supply chain. This page is the single source of truth for what is signed, what is scanned, and how you verify it. Nothing here is aspirational — each gate runs in CI, and the scan results are recorded below.
Signed, reproducible releases
A version tag (vX.Y.Z) drives .github/workflows/release.yml, which:
- builds a reproducible distroless image (
CGO_ENABLED=0,-trimpath, layer timestamps pinned to the commit) under an 80 MB size budget; - pushes it to GHCR (with an optional Docker Hub mirror);
- generates build provenance (
provenance: true); - attaches a CycloneDX SBOM of the image; and
- cosign-signs the image and attests the SBOM keylessly via GitHub OIDC — no long-lived signing key to leak.
Publishing happens on a real tag push. The pipeline itself is exercised in CI on every change (build, size gate, SBOM generation), and the signing/attestation steps run on the tag. The signature is verifiable by anyone (below).
Verify a published image (signature-on-install)
scripts/verify-image.sh ghcr.io/imfeelingtheagi/trstctl:<tag>
This confirms the image was signed by this repo's release workflow (the cosign
certificate identity is the workflow, asserted by GitHub's OIDC issuer) and that it
carries the CycloneDX SBOM attestation. Only an image built by
release.yml verifies.
Software-composition analysis (every dependency surface)
Dependencies live on three surfaces; two of them are outside go.sum, so they
get their own scans. All three run in CI and via make sca.
Go modules — govulncheck (pinned, reachability-aware)
govulncheck is pinned to @v1.1.4 (in ci.yml and the Makefile) so the
gate is deterministic, not a moving @latest. It is reachability-aware: it fails
only on advisories the code can actually call.
$ govulncheck ./...
=== Symbol Results ===
No vulnerabilities found.
Your code is affected by 0 vulnerabilities.
(2 advisories exist in imported packages but are not reachable from trstctl's code.)
npm (web UI) — npm audit
The web dependency tree is pinned by web/package-lock.json and scanned with
npm audit --omit=dev --audit-level=high in the CI web job.
$ npm audit --omit=dev
found 0 vulnerabilities (300 production dependencies)
embedded-postgres binary — committed checksum pin (CI and runtime) + Trivy
The embedded-postgres dependency downloads a real PostgreSQL 16.4.0 binary
from Maven Central at runtime — outside go.sum. It is used both by the
integration tests and by the served single-node/eval path
(internal/server/startBundledPostgres), so its provenance is committed and
enforced at runtime, not merely scanned in CI (SUPPLY-003):
deploy/supply-chain/embedded-postgres.jsonrecords the exact version, Maven coordinates, source URLs, and a committed per-arch SHA-256 pin for both the Mavenjarand the inner.txzarchive the library caches and extracts. The pin is populated (the trust-on-first-use bootstrap is complete), so the gate is a hard fail, not a no-op.internal/server/bundled_pg_pins.gocarries the same per-arch.txzpins that the served binary enforces at runtime: before starting bundled PostgreSQL,startBundledPostgresverifies the cached.txzagainst the committed pin and refuses to start a tampered or MITM'd binary, fail-closed. This is independent of the library's same-origin.sha256sidecar, so a Maven/MITM compromise serving a matching jar+sidecar is still caught.TestRuntimePinsMatchManifestasserts the Go pins and the JSON manifest never drift.scripts/supply-chain/verify-embedded-postgres.sh(CIsupply-chainjob) verifies the downloaded jar and its inner.txzagainst the committed pins (failing the build on any change) and Trivy-scans the extracted binaries for HIGH/CRITICAL issues.
This binary is not bundled in the shipped distroless image (which carries only the Go binaries); it is fetched on first run of the bundled single-node/eval path.
SBOMs
Two CycloneDX SBOMs are produced:
- the image SBOM the release attaches and cosign attests; and
- a module SBOM of the Go dependency graph (
make sbom, uploaded by the CIsupply-chainjob).
CI security & quality gates
Beyond SCA, CI enforces a security and quality bar on every pull request, repo-wide, so a regression cannot merge. Each gate fails the build, not merely reports:
- SAST — CodeQL (
.github/workflows/codeql.yml): static analysis of the Go and web-UI code with thesecurity-extendedquery suite, on every PR, on pushes tomain, and weekly. - Secret scanning — gitleaks (
.github/workflows/security.yml,.gitleaks.toml): scans the full history against gitleaks' default ruleset. The only allowlisted matches are deterministic PEM test vectors under_test.go/testdata; production source is scanned by every rule, so a hardcoded secret there fails CI. - Dependency vulnerabilities: the pinned
govulncheckjob (above) plus Dependabot (.github/dependabot.yml) raising update PRs for Go modules, npm, GitHub Actions, and the Docker base. - Container image scanning — Trivy (
.github/workflows/security.yml): builds the runtime image from a digest-pinned base (never a floating tag) and fails on any fixable HIGH/CRITICAL vulnerability — this is what catches a vulnerable base image.scripts/ci/check-base-pinned.shguards that the release path stays digest-pinned. - Critical-package coverage gate (
make test→scripts/ci/coverage-critical.sh): in addition to the repo-wide coverage floor, each security-critical package (crypto boundary, issuance, outbox, RLS store, signing, revocation) must independently meetCRITICAL_COVERAGE_MIN, computed from the merged-coverpkgprofile — so a critical package cannot hide behind the aggregate average.
The architecture linter (trstctllint) and the workflow linter (actionlint) remain
required. The full set of required status checks, plus enforce-admins, linear
history, and code-owner review, is now codified in the repository — see
Branch protection & required checks for the exact list and
.github/branch-protection.json
for the machine-applicable form (a repo admin applies it once; a reality-test keeps
the required-check list in sync with the CI job names). Code ownership of the
root-of-trust paths is codified in
.github/CODEOWNERS.
Run it yourself
make supply-chain # module SBOM + Go/npm/embedded-postgres SCA (network needed for the PG leg)
make vuln # just the pinned govulncheck gate
make sbom # just the module SBOM
make coverage-critical # per-package coverage gate on the critical set (needs cover.out from `make test`)
See deploy/supply-chain/README.md for the per-surface summary table.