Trivy, SBOMs, cosign, and branch protection — turning the green check into a contract.
Cloud & DevOps Module 2
Things you depend on
python:3.11-slim)Things that touch your image
Each touch point is a possible compromise vector — even if your code is clean.
- name: Trivy scan
uses: aquasecurity/trivy-action@0.24.0
with:
image-ref: api:ci-${{ github.sha }}
severity: HIGH,CRITICAL
exit-code: 1
ignore-unfixed: true
exit-code: 1 makes the job fail when findings are foundignore-unfixed: true skips CVEs with no upstream fix — avoids alert fatigueHIGH,CRITICAL cuts noise from LOW/MEDIUM| Tool | Vendor | Advisory DB | Notes |
|---|---|---|---|
| Trivy | Aqua Security (OSS) | Broader multi-source DB | Faster, GitHub Action available, scans config/secrets too |
| Grype | Anchore (OSS) | Grype DB + GHSA | Pairs naturally with Syft for SBOM-first workflows |
Either works. Pick one and stick to it — mixing tools leads to inconsistent severity noise. We use Trivy in this sample.
A Software Bill of Materials (SBOM) lists every package that ended up in your image — the full dependency tree, not just your direct deps.
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: api:ci-${{ github.sha }}
format: spdx-json
output-file: sbom-api.spdx.json
- uses: actions/upload-artifact@v4
with: { name: sbom-api, path: sbom-api.spdx.json }
spdx-json is machine-readable and widely supportedcosign signs an image (or any OCI artifact) with a key. The signature lives in the registry next to the image. Verifiers check the signature before deploying.
Two signing modes:
The signature attests: “this image was produced by the expected pipeline at the expected commit.”
- uses: sigstore/cosign-installer@v3
- name: Sign image (keyless)
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}/api@${{ steps.build.outputs.digest }}
permissions: id-token: write on the workflow--yes skips the interactive prompt — required in CIcosign verify \
--certificate-identity-regexp "https://github.com/$ORG/$REPO/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/$ORG/$REPO/api@$DIGEST
Deploy-side verification checks that the image came from the expected pipeline — not just that it has a valid signature, but that the cert identity pins it to your workflow.
Run this in a Kubernetes admission webhook or a deploy job to prevent unsigned images from reaching production.
GitHub UI: Settings → Branches → Add branch protection rule → pattern main
lint, test (3.11), test (3.12), build (api), build (worker), trivy-api, trivy-workerThe green check is now a contract: merging without it is impossible.
mainBy the end: CVE gates on PRs, signed images on main, verified trust chain.
ignore-unfixed: true is your friend — without it, you fail on CVEs that have no upstream patch and the team goes numb to the noise--yes skips the interactive confirmation; in a non-TTY runner, omitting it hangs the job foreverid-token: write permission; gate signing on github.event_name != 'pull_request' (or use the separate security.yml pattern).trivyignore in the working directory where Trivy runs, not next to the Dockerfile