0011 — Supply-chain / release hardening
Status: Accepted
Context
The initial CI gate ran only gofmt, go vet, race tests, and a build — no vulnerability
scanning, no SAST, no linter. All GitHub Actions across ci.yml and release.yml were
referenced by mutable tags (@v4, @v3, …). A re-pointed tag or repo compromise would
run unreviewed code with the workflow token and secrets. Known-CVE dependencies or insecure
code patterns could merge unflagged. The release pipeline lacked a CycloneDX SBOM,
checksums, and reproducible-build flags. The container image ran as root.
Decision
The following hardening is applied in the v0.1 release:
- CGO off: all builds set
CGO_ENABLED=0so the binary is statically linked and the minimal Docker image requires no C runtime. - Non-root container image: the Dockerfile uses a multi-stage build; the final stage
copies only the binary and CA certificates from the builder, and runs as
USER 10001(never root). CA certs are copied fromca-certificatesin the build stage — not installed at container runtime withapk add(which would add download-time supply-chain risk and require network access). - SHA-pinned Actions: every GitHub Action is pinned to a full commit SHA with a
trailing
# vX.Y.Zcomment. A.github/dependabot.ymlcovers ecosystemsgithub-actions,gomod, anddocker, withpersist-credentials: falsein all workflows so the workflow token is not exposed to checked-out code. - GoReleaser + CycloneDX SBOM:
.goreleaser.yaml(schema v2) cross-compileslinux,darwin × amd64,arm64, producestar.gzarchives with bundledLICENSE/README.md/config.yaml, achecksums.txt, a CycloneDX SBOM viacyclonedx-gomod(consistent withmake sbom), and reproducible-build flags (-trimpath). The multi-arch GHCR image usesdocker/metadata-actionfor tags and builds with max-mode provenance attestation. make cigate:gofmtcheck,go vet,golangci-lint,go test -race,govulncheck— all must pass before merge.make surereproduces this locally.
Consequences
- Workflows execute only immutable, reviewed Action code; tag-repoint attacks are neutralised. Dependabot keeps pins fresh via reviewable PRs.
- CVE-bearing dependencies and insecure code patterns fail CI before merge.
- The container is non-root by default, reducing the blast radius of any container escape.
- CA cert copying (no
apk add) means the final image has no package manager and no extra network calls at container start. make release-snapshotreproduces the GoReleaser pipeline locally without a tag push.- The injected
main.versionuses GoReleaser's{{ .Version }}(novprefix).