- ποΈ March 29, 2024 β Andres Freund (Microsoft engineer) investigates a 500ms ssh slowdown on his Debian sid box. He digs into compressed test files. He finds CVE-2024-3094: a backdoor in xz-utils that hooks
RSA_public_decryptin OpenSSH and accepts a hardcoded key - πͺ The backdoor was planted by a maintainer (alias "Jia Tan") who spent two years building reputation, then merged it into versions 5.6.0 and 5.6.1 in February 2024
- π Distros affected: Debian sid, Fedora 40 beta, openSUSE Tumbleweed, Kali rolling, Homebrew
- π― Caught by accident, 28 days before stable releases shipped. Had Andres not noticed the 500ms blip, this would have hit Ubuntu 24.04 LTS and millions of production sshd daemons
- π§ What would have stopped it in CI:
- π Signed releases verified at install (Sigstore Cosign) β would have required the attacker to also compromise the maintainer's signing key
- π SBOM + reproducible build verification β the malicious binary embedded in
tests/differed from a from-source rebuild - π Anomaly detection on test-data file sizes β the suspicious blob was multiple MB in a "compression test"
π€ Think: xz didn't show up in OWASP Top 10:2021 SSRF rules, in your SAST scanner, or in your SCA database. The whole supply chain layer is what catches this class of attack.
| # | π Outcome |
|---|---|
| 1 | β Explain why supply chain attacks are a distinct class β and which prior labs already protect against parts of it |
| 2 | β Sign a container image with Cosign and verify the signature with the public key |
| 3 | β Attach an SBOM attestation + a provenance attestation to an image |
| 4 | β Recognize the Sigstore architecture: Cosign, Fulcio, Rekor β and how keyless signing works |
| 5 | β Pick an appropriate SLSA Build Level target for a project and identify what's missing |
graph LR
L3["π L3 Signed<br/>commits"] -.feeds.-> L8
L4["π L4 SBOM<br/>+ SLSA intro"] --> L8
L7["π¦ L7 Hardened<br/>image"] --> L8["π L8 Sign<br/>+ attest (here)"]
L8 --> L9["π L9 Verify<br/>at runtime"]
style L8 fill:#FF9800,color:#fff
- πͺ Building on:
- L3 β you can already sign commits; signing artifacts is the same idea applied to the output
- L4 β you have a CycloneDX SBOM from Syft; this lecture wraps it in an attestation
- L7 β the image you hardened in L7 becomes the subject of Cosign's signature
- π― Lab 8 alignment: Task 1 (Cosign sign + tamper demo) + Task 2 (SBOM + provenance attestations) + bonus (blob/script signing β the Codecov 2021 fix)
π¬ "You can't trust anything you didn't build yourself. And you didn't build everything yourself." β Ken Thompson's classic "Reflections on Trusting Trust" (1984, Turing Award lecture) β the original supply-chain paper
flowchart LR
Code[π¨βπ» Your code] --> Deps[π¦ Dependencies<br/>~80-90% of bytes]
Deps --> Build[ποΈ Build]
Build --> Image[π¦ Image]
Image --> Registry[ποΈ Registry]
Registry --> Deploy[π Deploy]
Compromise1[πͺ€ Maintainer takeover<br/>xz 2024] -.-> Deps
Compromise2[π Build server compromise<br/>SolarWinds 2020] -.-> Build
Compromise3[π¦ Registry breach<br/>Docker Hub 2019] -.-> Registry
style Compromise1 fill:#F44336,color:#fff
style Compromise2 fill:#F44336,color:#fff
style Compromise3 fill:#F44336,color:#fff
- πͺ Three classes of supply chain attack:
- Dependency compromise (xz 2024, event-stream 2018, ua-parser-js 2021)
- Build server compromise (SolarWinds 2020, Codecov 2021)
- Registry/distribution compromise (Docker Hub 2019)
- π§ SAST/DAST/IaC scans don't catch any of these. The whole signing + attestation layer exists for this exact failure class
- π’ Sigstore project β Linux Foundation, founded 2021 by Red Hat, Google, Purdue, Chainguard
- π Sigstore project promoted to OpenSSF Graduated in 2024
- πΉ Cosign: Go binary, single static install
- π’ Latest: Cosign v2.x (April 2026 stable)
- π― What Cosign does:
- Generate a keypair β sign artifacts β verify signatures
- Or use keyless signing (Sigstore identity β ephemeral cert)
- Attach attestations (SBOMs, provenance, vuln scans) as signed claims
- Push everything to the registry alongside the image (OCI "referrers" pattern)
# Lab 8 Task 1 starts here
cosign generate-key-pair # creates cosign.key + cosign.pub
cosign sign --key cosign.key ghcr.io/me/juice-shop@sha256:abc...
cosign verify --key cosign.pub ghcr.io/me/juice-shop@sha256:abc...sequenceDiagram
participant Dev as π€ Developer
participant Cosign as π Cosign
participant Reg as ποΈ Registry
participant Verifier as β
Verifier (CI/deploy)
Dev->>Cosign: cosign sign image@digest --key
Cosign->>Cosign: Hash image, sign with private key
Cosign->>Reg: Push signature as OCI artifact<br/>(sha256-xxx.sig)
Verifier->>Reg: Pull image + signature
Verifier->>Cosign: cosign verify image@digest --key public
Cosign-->>Verifier: β
Verified / β Invalid
- πͺ Critical detail: Cosign signs the digest of the image (
@sha256:...), not the tag. Tags are mutable; digests aren't (recall L4) - π§ The signature is itself an OCI artifact stored at
sha256-<digest>.sig. Compatible with any OCI-spec registry (GHCR, ECR, Docker Hub, Harbor, Quay, distribution v3)
- π¨ The traditional signing problem: if your
cosign.keyleaks, your signatures are worthless (attackers can sign their malware as you) - π‘ Keyless signing (Sigstore's defining innovation):
- No long-lived private key
- Cosign requests a short-lived certificate from Fulcio (Sigstore's CA), authenticated via OIDC (GitHub Actions, your Google account, etc.)
- Certificate is valid for 10 minutes
- Cosign signs with the cert's ephemeral key
- Signature + cert are uploaded to Rekor (transparency log) and to the registry
# Keyless sign β no key file, requires browser OIDC or GHA OIDC
cosign sign ghcr.io/me/juice-shop@sha256:abc...
# (opens browser for one-time GitHub/Google OAuth)
# Keyless verify β proves "signed by this GitHub identity from this workflow"
cosign verify ghcr.io/me/juice-shop@sha256:abc... \
--certificate-identity-regexp "https://github.com/me/.+" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com- πͺ Why this is huge: in CI (GitHub Actions OIDC, L4), every signing operation is auditable in Rekor by anyone. You can't quietly sign a malicious artifact β it gets logged publicly
graph TB
C[π Cosign<br/>The client] --> F[πͺͺ Fulcio<br/>Cert authority<br/>OIDC β ephemeral cert]
C --> R[π Rekor<br/>Transparency log<br/>tamper-evident, immutable]
C --> Reg[π¦ Registry<br/>Stores signature]
style C fill:#2196F3,color:#fff
style F fill:#9C27B0,color:#fff
style R fill:#FF9800,color:#fff
| π§± Component | π― Purpose |
|---|---|
| Cosign | Client tool β signs, verifies, attaches attestations |
| Fulcio | Free CA that issues 10-minute certs based on OIDC identity |
| Rekor | Immutable transparency log of every signature ever issued |
| TUF | The root-of-trust system distributing Fulcio's root cert |
- πͺ The transparency log is the breakthrough. Anyone (including security teams at orgs that don't use Sigstore) can query Rekor and see "who signed what when". Backdating signatures becomes impossible
- π§ If you're curious:
rekor-cli search --artifact <hash>shows every signature ever made of an artifact
A signature proves who. An attestation proves what.
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{ "name": "ghcr.io/me/juice-shop",
"digest": { "sha256": "abc..." } }
],
"predicateType": "https://cyclonedx.org/bom/v1.5",
"predicate": {
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"components": [ ... ]
}
}-
πͺ in-toto attestation format = the standard envelope. Subject identifies what's being attested; predicate is the claim's content
-
πͺ Common predicate types:
https://cyclonedx.org/bom/v1.5β CycloneDX SBOMhttps://spdx.dev/Document/v2.3β SPDX SBOMhttps://slsa.dev/provenance/v1β SLSA build provenancehttps://cosign.sigstore.dev/attestation/vuln/v1β vuln scan results
-
πͺ Lab 8 Task 2 attaches a CycloneDX SBOM attestation (from Lab 4) + a SLSA provenance attestation to the image you hardened in Lab 7
# Lab 4 produced this file
ls labs/lab4/sbom.cdx.json
# Sign and attach it to the image as a CycloneDX-formatted in-toto attestation
cosign attest \
--type cyclonedx \
--predicate labs/lab4/sbom.cdx.json \
--key cosign.key \
ghcr.io/me/juice-shop@sha256:abc...
# Anyone can now pull + verify
cosign verify-attestation \
--key cosign.pub \
--type cyclonedx \
ghcr.io/me/juice-shop@sha256:abc... | jq '.payload | @base64d | fromjson'- πͺ Operational value: when the next Log4Shell arrives, you query the attestation, not the source repo β "do any of my images attest to depending on Log4j 2?"
- π§ The chain effect: L4 made the SBOM β L8 signs it β L9 verifies it before deploy β L10 imports it into DefectDojo. One file, four labs of leverage.
Recall from Lecture 4 β SLSA Build Levels. Now you have the tools to actually meet them:
| πͺ Level | π― Requirement | π οΈ Lab tooling |
|---|---|---|
| Build L1 | Provenance exists | A signed in-toto attestation produced by your CI |
| Build L2 | Provenance is signed by the build system | Cosign keyless signing (Lab 8) |
| Build L3 | Build platform itself is hardened, non-falsifiable | GitHub Actions OIDC reusable workflow + Cosign |
- πͺ GitHub Actions can produce SLSA L3 for free. The
slsa-github-generator(OpenSSF) or the newergh attestationcommand both work - πͺ Lab 8 Bonus task (signing a tarball / blob) is the same Cosign API applied to non-OCI artifacts β
cosign sign-blob. The Codecov 2021 incident is the canonical case for this β signing thebashuploader they distributed
A signature you don't verify is decoration.
# K8s admission policy (Kyverno example)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: enforce
rules:
- name: check-cosign-signature
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences: ["ghcr.io/me/*"]
attestors:
- entries:
- keys:
publicKeys: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY------ πͺ Same pattern in: Sigstore's
policy-controller, Connaisseur, Notary v2 β all do K8s-admission-time signature verification - πͺ In Lab 9 you'll verify signatures at deploy time as part of the runtime story. Lab 8 establishes them; Lab 9 enforces them
- ποΈ October 22, 2021 β npm package
ua-parser-js(8M weekly downloads) is hijacked. Maintainer's account compromised; three malicious versions published over 4 hours: 0.7.29, 0.8.0, 1.0.0 - π¦ The packages include cryptominers (XMRig) and password stealers
- π Affected: any project running
npm installduring that 4-hour window - π‘οΈ What would have helped:
- npm Provenance (released 2023) β signed attestation proving the artifact came from a specific GitHub Actions workflow on a specific commit. Verifies via Sigstore
- Strict lockfile pinning + integrity hashes (
package-lock.jsonalready does this for transitive deps) - Cosign verification of npm packages β the toolchain exists; few teams enable it
- π§ The bigger pattern: in 2021 there was no way to verify who published an npm package. By 2024 there is. Adoption is still uneven
Back to xz-utils 2024 with the lecture's tools in hand:
| πͺ Control | π‘οΈ Would it have stopped xz? |
|---|---|
| Signed releases (Cosign keyless) | Partial β attacker also controlled the maintainer's signing identity; would need community-of-maintainers signature |
| SBOM attestation | The SBOM would list xz-utils 5.6.0 β but the bug is in what xz does at runtime, not what version says it is |
| Reproducible builds | β Strong β the released binary differed from a from-source rebuild; reproducible build verification would have flagged this |
| In-toto provenance β every link signed | β Strong β would have required the attacker to compromise the entire chain (commit β build β test β release), not just the package |
- π§ No silver bullet. xz showed that even with Cosign, SBOM, and signed packages, a patient attacker with maintainer privileges can ship a backdoor. Reproducible builds + community signing are the deeper defenses
π¬ "xz-utils proved that supply chain security is not a tool you buy. It's a discipline you maintain." β Filippo Valsorda, Cryptographers' Mailing List, April 2024
-
π in-toto is a CMU project (since 2017); the underlying spec for everything Sigstore does
-
πͺ The mental model: every step in your pipeline produces a link (a signed claim about what happened), and a layout describes the required steps + their valid producers
-
πͺ In practice today (2026), most orgs use Cosign attestations (which use in-toto envelope) rather than the full in-toto layout system, because Cosign is enough
-
π― What you should know for interviews:
- "in-toto" = the standard for software supply chain attestations
- "Sigstore" = the implementation infrastructure (Cosign + Fulcio + Rekor)
- "SLSA" = the maturity ladder
-
π§ You don't need to learn the full in-toto spec for this course. The Cosign + attestation flow is what 99% of teams ship in 2026
graph LR
I[π¦ Image<br/>cosign sign image@digest] --> R[ποΈ Registry]
S[π SBOM<br/>cosign attest --type cyclonedx] --> R
P[ποΈ Provenance<br/>cosign attest --type slsaprovenance] --> R
B[π¦ Blob/tarball<br/>cosign sign-blob] -.bonus.-> Out[π Out-of-band]
style I fill:#4CAF50,color:#fff
style S fill:#2196F3,color:#fff
style P fill:#FF9800,color:#fff
style B fill:#9C27B0,color:#fff
| π·οΈ What | πͺ Why |
|---|---|
| The image itself | Primary artifact; proves who built it |
| The SBOM (from L4) | Proves what's inside; queryable for future CVEs (Log4Shell pattern) |
| Provenance | Proves how it was built β which commit, which workflow, which build server |
| Blob/tarball (bonus) | The same cosign sign-blob pattern that would have stopped Codecov 2021 |
- πͺ All four signatures go to the same registry alongside the image. One pull = everything you need to verify trust
| π¨ Mistake | π οΈ Fix |
|---|---|
| Signing the tag, not the digest | cosign sign image@sha256:... always (tags are mutable) |
| Private key checked into repo | Use keyless signing OR store key in cloud KMS (cosign supports GCP/AWS/Azure KMS) |
| Signing but never verifying | Wire cosign verify into deploy admission controller (Kyverno / policy-controller) |
| Attestation predicate types invented | Use standard URIs (cyclonedx.org/bom/v1.5, slsa.dev/provenance/v1) β non-standard ones break interop |
| Forgetting to push the public key (or trust policy) to verifiers | Document the verification command alongside the build pipeline |
# .github/workflows/release.yml β runs after L4/L5/L6/L7 all pass
jobs:
sign-and-attest:
needs: [test, sast, dast, image-scan]
permissions:
id-token: write # OIDC for Cosign keyless
contents: read
packages: write # push to GHCR
attestations: write
steps:
- uses: sigstore/cosign-installer@v3.7.0
- name: Cosign keyless sign
run: cosign sign --yes ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
- name: Attach SBOM attestation
run: cosign attest --yes --type cyclonedx --predicate sbom.cdx.json ...
- name: Attach SLSA provenance
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with: { image: ghcr.io/${{ github.repository }}, digest: ${{ steps.build.outputs.digest }} }- πͺ What this gives you: SLSA Build Level 3 + signed SBOM + provenance, all from a free GitHub Actions runner with OIDC
- π§ What it doesn't give you: verification. That's the next layer (L9) β admission-time enforcement
- π§ͺ Lab 8 (this week):
- Task 1 (6 pts): Spin up local registry, sign the Juice Shop image with Cosign, demonstrate tamper detection
- Task 2 (4 pts): Attach SBOM (from Lab 4) + provenance attestations
- Bonus (2 pts): Sign a blob/tarball with
cosign sign-blobβ the Codecov 2021 mitigation pattern
- π Lecture 9 (next week): Monitoring, Compliance & Maturity β Falco runtime detection + Conftest policy-as-code + the metrics that make a program (MTTR, vuln age, OWASP SAMM). We start verifying the signatures you just attached, at runtime
Books:
| π Book | βοΈ Why |
|---|---|
| Software Supply Chain Security β Cassie Crossley (Manning, 2024) | Best single book; chapters 6β7 are pure attestation + signing |
| Securing the Software Supply Chain β Tom Stuart (O'Reilly, 2024) | Pairs perfectly with this lecture; lighter touch, ch. 4 on Sigstore |
| Reflections on Trusting Trust β Ken Thompson (1984, free PDF, 6 pages) | The original β still the most important essay in supply chain security |
Talks & specs:
- π₯ "Sigstore: Software Signing for Everybody" β Luke Hinds (Sigstore founder), KubeCon 2022
- π₯ "The xz Backdoor β Engineering Postmortem" β Andres Freund, BSD Can 2024
- π Sigstore Documentation
- π SLSA v1.0
- π in-toto Attestation Spec
- π Cosign reference
Takeaways:
| # | π§ Insight |
|---|---|
| 1 | Supply chain attacks bypass SAST, DAST, and SCA. Signing + attestation are the layer designed to catch them. |
| 2 | Cosign keyless signing removes the "private key leak" failure mode entirely. Use it. |
| 3 | A signature proves WHO. An attestation proves WHAT. You usually need both. |
| 4 | The transparency log (Rekor) is the breakthrough β signing becomes publicly auditable. |
| 5 | xz-utils 2024 is the modern xy: even with signing, a patient maintainer can backdoor. Reproducible builds + community signing are the deeper defenses. |
| 6 | A signature you don't verify is decoration. Admission-time verification (L9) is non-optional. |
π¬ "You cannot trust code that you did not totally create yourself." β Ken Thompson, Turing Award lecture, 1984. The original supply-chain paper. Read it before lecture 9.