Everything FosterStack does for security
DRAFT for v0.3, private preview, not published and never merged before v0.3.0 is live.
This is the list of security practices that run on the FosterStack Cache repository today, one line each. Every line links to the place that goes deep: a page that explains it, or the file that does it. SECURITY.md is the short policy (what we promise, including the patch target and how to report a vulnerability); this page is what we do.
Source
- Signed commits. The
mainbranch accepts only signed commits, under a repository ruleset that also requires the checks on every pull request (the repository's rules, required checks). - Signed release tags, checked first. The release chain starts by verifying the tag against a list of allowed signers kept on
main, before any repository code runs (admission stage, allowed signers). - Release tags are protected. Creating a release tag is restricted by a repository rule, and a published release tag cannot be moved, updated or deleted (the repository's rules).
- The merge gate runs from
main. The check that judges a pull request is the copy onmain, not the copy in the pull request, so a change cannot edit the rule that judges it (review gate). - CI runs a fork's code without secrets. The CI workflow runs on
pull_request, neverpull_request_target, so a fork's code runs with GitHub's no-secrets treatment (CI). - A reserved branch for the automated auditor. Only the auditor's delivery identity may push to its branch prefix, and any other pusher fails the check (guard).
Dependencies
- Every action in every workflow is pinned to a full commit digest. A tag or a branch reference fails the check on every pull request, and the check also reads the tag each digest claims to be (pin checker).
- Container images are named by digest. An image a workflow runs, pulls or passes to an action must be a digest, never a tag, and the same checker fails the pull request otherwise (pin checker).
- A new version waits seven days. A pull request that moves a pinned version is held until that version has been public for seven days, by a server-side time (supply-chain workflow).
- Everything we pin is checked daily. The inventory of pinned actions and packages is checked once a day against published advisories and malicious-package reports (supply-chain workflow).
- Automated dependency review on every pull request. Newly added dependencies and their advisories are listed and checked before merge (CI).
- Updates are automated and judged. Patch and minor updates merge after the full gate; a major update is read against the upstream release notes and our usage by an automated reviewer before anyone merges it (auto-merge, reviewer).
- The Go toolchain stays on the newest patch of a supported minor. A daily job proposes the bump when a newer supported release exists (freshness job).
- Go vulnerability analysis on every change.
govulncheckruns on every pull request and push;gosecand CodeQL read the code (CI, CodeQL). - Posture is measured in public. OpenSSF Scorecard runs weekly and its badge is on the README (Scorecard).
Build
- One build, promoted by digest. The release is built once; the same bytes are scanned, tested and promoted, and the promoted digest is asserted equal to the scanned digest after every copy (release chain).
- Every stage signs what it did. Admission, build, image assembly, reproducibility, each scan and acceptance each sign a statement naming the exact digests, and the next stage verifies it on entry (RELEASING.md).
- Pinned toolchain. The build uses the Go version from
go.modwith downloads of a newer toolchain disabled, and module manifests are verified, never rewritten (build stage). - Distroless image, assembled by copy. Each image variant is a base image digest plus the binary bytes, with no build step inside the image; the production and FIPS images have no shell and no package manager (image stage).
- Assembled twice and compared. The image assembly is repeated on a fresh runner and the digests must be equal (reproducibility stage).
- A FIPS variant. A
-fipsbuild uses Go's FIPS 140-3 validated cryptographic module, announces itself at startup, and the whole test suite runs infips140=onlymode on every commit (SECURITY.md). - Approved-only cryptography is checked by lint too. A lint rule denies the third-party cryptographic packages we name, such as
golang.org/x/crypto, so code reaches for the validated module instead (.golangci.yml).
Scanning
- Scanners with different databases block every release. The set is one list in the repository, each scanner blocks at every severity including unknown, and nothing hard-codes a name or a count (scanner list, SECURITY.md).
- Scanned on every pull request. The pull request gate builds the same image the release would ship and scans it (scan workflow).
- Four scanners, every day. The daily rescan builds main's candidate and scans it with four independent scanners, then tallies how many saw each finding (daily rescan).
- Published images are rescanned daily. A CVE disclosed against bytes we already shipped raises a tracked issue (scanning.md).
- An automated audit reads every finding. Each day an automated auditor reads every scanner finding against the code and the published VEX, and writes up what each one needs (daily auditor).
- Exceptions are published statements. The only way to except a finding is an OpenVEX statement with a justification, kept in the repository and published with each release (
.vex/, scanning.md). - Scanner verdicts are signed. Each scanner's verdict is a signed statement bound to the digests it scanned (verify-images.md).
Release
- Signed images. Every image is signed keylessly by the promotion workflow, in both registries (verify-images.md).
- Signed provenance. Archives, SBOMs and the checksums file carry build provenance naming the CI run that made them (verify-images.md).
- An SBOM for every archive, published with the release (RELEASING.md).
- The VEX document ships with every release as
fosterstack-cache.openvex.json(RELEASING.md). - One authorization before anything goes public. A single stage verifies the whole graph, fails closed on anything missing, and signs the approval that promotion requires (authorization stage).
- The release is checked as a customer would. After promotion the public digests are verified and pulled anonymously, and only then is the release published (promotion stage).
- Acceptance runs against the exact artifacts. The archives and images that ship run the customer scenarios first: Gradle, Maven, Kubernetes and the artifact checks (acceptance, artifact acceptance).
Runtime
- No telemetry and no outbound connections. The server sends nothing anywhere, and a release test traces the candidate from its first instruction with egress denied to prove it (egress acceptance, SECURITY.md).
- No request access log. The only per-request logging is on failure, and it never carries a body, a credential or a client address (SECURITY.md).
Watch
- A daily watch of what we depend on. Newly published advisories and malicious-package reports about anything we pin are read every day (supply-chain workflow).
Related: how releases are built and signed, verify a release yourself and the security policy.
Moving off the free Build Cache Node? See what the end of life means and the migration guide.