PodSalsa is a worked example of releasing a Go application securely on GitHub.
The application itself is deliberately trivial — a small web service that displays its own version, Git commit and build date. The interesting part is everything around it: how the binaries and container images are built, what is signed, what provenance is produced, and how anyone can verify all of it independently.
Releases carry SLSA Build Level 3 provenance. If you are here for one thing, it is probably how that is wired or how to verify it.
Every release travels the same path, and every step leaves something a consumer can check:
flowchart LR
src["<b>Source</b><br/>tagged commit"]
build["<b>Build</b><br/>trusted builders<br/>goreleaser · ko"]
sign["<b>Sign</b><br/>keyless, via Sigstore<br/>provenance · signature · SBOM"]
pub["<b>Publish</b><br/>GitHub Release<br/>ghcr.io"]
ver["<b>Verify</b><br/>gh · cosign · cue"]
enf["<b>Enforce</b><br/>Kyverno admission"]
src --> build --> sign --> pub --> ver --> enf
The build and the signing happen inside trusted builders — reusable workflows that the calling workflow cannot influence. That isolation is what raises the provenance from Build Level 2 to Level 3, and it is the property verification actually pins. Read how the pipeline is put together →
Alongside the release pipeline, the repository runs code scanning, dependency review and vulnerability scanning on every change — see the workflow overview.
Everything published is verifiable with the GitHub CLI and cosign. To check a released binary:
export VERSION=$(curl -s "https://api.github.com/repos/janfuhrer/podsalsa/releases/latest" | jq -r '.tag_name')
export ARTIFACT=podsalsa_${VERSION}_darwin_amd64.tar.gz
curl -L -O https://github.com/janfuhrer/podsalsa/releases/download/$VERSION/$ARTIFACT
gh attestation verify $ARTIFACT \
--repo janfuhrer/podsalsa \
--cert-identity-regex '^https://github\.com/janfuhrer/podsalsa/\.github/workflows/build-binaries\.yml@refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$' \
--source-ref refs/tags/$VERSION \
--deny-self-hosted-runnersFull verification guide → covering container images, signatures, SBOMs and policy validation.
| Artifact | Description | Verification |
|---|---|---|
| Go-binary archives | Multi-architecture and platform archives | SLSA provenance |
| Container images | Multi-architecture images on ghcr.io |
SLSA provenance & cosign signature |
| SBOMs | CycloneDX, for both archives and images | SLSA provenance & cosign attestation |
| Checksums file | SHA-256 of the archives | cosign signature |
# container
docker run --rm -p 8080:8080 ghcr.io/janfuhrer/podsalsa:latest
# or with compose
docker compose upThe service listens on :8080 and serves / and /health. Configuration is read from flags
or PODSALSA_-prefixed environment variables (for example PODSALSA_LEVEL=debug).
A Kubernetes manifest is in kubernetes/, and building from source is
make go-build.
Start at the documentation index, which is organised around how it is built, how that is proven, and how you check it. The most-used entry points:
| Release verification | Verify binaries, images, signatures and SBOMs |
| GitHub workflows | Every workflow, and how the release pipeline fits together |
| Trusted builders & SLSA L3 | Why the release is split into a caller and two builders |
| GitHub Actions best practices | Secrets, permissions, script injection, pinning |
| Sigstore & keyless signing | What happens when something is signed |
| Kubernetes enforcement | Blocking unverified images with Kyverno |
| Additional information | Component analysis with Dependency-Track and GUAC |
| Archive | Superseded approaches, including verification of releases ≤ v0.9.x |
Use this repository as a reference for securely building and releasing Go applications on GitHub with SLSA Build Level 3 provenance. Fork it, adapt the workflows, and reuse the security practices in your own projects.
If you are adapting the release pipeline, note that the trusted-builder split and the certificate identities in policy.cue, SECURITY.md and the verification workflows all reference each other — CLAUDE.md lists what has to change together.
