
"We scan our images for vulnerabilities" is a sentence that's true in almost every organization and enforced in almost none of them. A scan that produces a report nobody reads before deploying isn't a gate — it's a compliance artifact. The actual question that matters is a narrower one: can an image that failed the scan, or that nobody signed, physically run in production at all? Most pipelines can't answer yes to that with certainty, because the enforcement point and the deploy point are different systems that trust each other by convention rather than by cryptographic proof.
gcp-cloudrun-secure-delivery closes that gap: nothing reaches production unless it was built by this specific pipeline and passed a vulnerability gate — two independently signed attestations, verified by Binary Authorization on every single revision, not just at deploy time for show. Releases travel through Cloud Deploy by immutable digest, staging deploys automatically, production requires a human approval, and a bad release is caught by a 25% canary and rolled back with one command. It deployed live in europe-west1, shipped a real release end to end through staging → approval → canary → 100% production, and its bad-release drill ran for real: a faulty release was caught at the canary-25 stage and rolled back.
Two attestors, not one
The design deliberately splits trust into two independent claims rather than one combined check: built-by-pipeline proves provenance (this exact digest came from this exact build), and scan-passed proves the vulnerability gate ran and the image cleared it. Both are signed with Cloud KMS keys that only the build service account may use. Binary Authorization requires both attestations to admit an image — which means a rogue image signed with a stolen provenance key but missing the scan attestation still gets denied, and vice versa. The live test suite proves this concretely by attempting three distinct kinds of rogue image and confirming each is denied, by name, with a stated reason rather than a generic rejection.

The gate runs before the image is ever pushed
The vulnerability scan (Trivy) runs against the local image before it's pushed to any registry — a CRITICAL-severity vulnerability with an available fix fails the build outright. That ordering is the point of ADR 0002: a vulnerable image that never reaches a registry can't accidentally get deployed by someone who skips the pipeline, whereas a gate that runs after push is only as strong as everyone's discipline about not pulling from the registry directly.

Tamper-evident by construction
Every image gets a CycloneDX SBOM and SLSA build provenance, stored per exact digest — so "what's actually running" and "what do we claim is running" are answerable from the same artifact instead of from separate, driftable documentation. Deploys reference the digest, never a mutable tag, which the ADRs call out plainly: immutable tags plus digest-pinned deploys mean a rollback is just re-pointing traffic at a previous digest through the normal, approved release path — not a special-cased emergency procedure with its own risk profile.
The canary drill, run for real
Cloud Deploy owns the progression: staging automatically, production only after a human approves, then a canary that steps 25% → 50% → 100% with a soak period between stages. I ran the failure case deliberately: pushed a faulty release into the canary, watched it serve a portion of traffic with faulty responses at the 25% stage, and rolled it back with a single command.

Rollback in production requires the same approval gate as a forward release, which is worth stating explicitly because it's tempting to make rollback frictionless in the name of incident response — and doing that quietly builds an unaudited bypass around the approval process you built everything else to enforce.
A real permission gap the first live run found
The build's first live run surfaced something no design review caught: the canary's own auto-advance automation didn't have permission to advance itself. It's the kind of gap that only shows up when a canary actually tries to progress on a live stack — fixed and documented in ADR 0003 and runbook 07.
Four decisions
| Decision |
|---|
| Two independent attestors, not one combined check |
| The vulnerability gate runs before the image is ever pushed |
| Digest-pinned releases; Cloud Deploy owns the canary progression |
| Immutable tags; rollback is a normal, approved rollout, not a special case |
What's honestly out of scope
There's no source-code provenance layer here — branch protection, signed commits — this repo starts trust at the build, not at the commit. No automated smoke tests gate promotion between stages; the approval step is a human judgement call, not an automated check. The vulnerability gate specifically blocks CRITICAL-with-an-available-fix, which means a CRITICAL finding with no fix yet, or a HIGH-severity finding, doesn't block the build by design. It's single-region, and the demo quote service behind all of this is intentionally a toy — the interesting engineering is entirely in the pipeline around it.
One detail worth flagging for anyone adapting this: Binary Authorization here is a project-wide, single policy, not scoped to just this repo's rules. down.sh reverts the project to allow-all rather than leaving a half-configured policy behind — which matters if you're running this alongside other Binary Authorization-dependent work in the same project.
Try it yourself
git clone https://github.com/soodrajesh/gcp-cloudrun-secure-delivery
cd gcp-cloudrun-secure-delivery
gcloud config set project <your-project>
./scripts/up.sh # infra → secure build → staging → approve → prod canary → live tests
./scripts/down.sh # delete everything, revert Binary Authorization to allow-all
Well under €1 for a full build-test-drill-teardown cycle. Cloud Run scales to zero between requests, and a Terraform-managed budget with alerts is created alongside everything else.
📢 Have questions or feedback? Drop a comment below or connect with me on Twitter/X@spysood!