Why SBOMs Alone Aren't Enough
The Software Bill of Materials (SBOM) mandate from the 2021 US Executive Order did the industry a real service β it forced organisations to actually know what dependencies they were shipping. But SBOMs have a fundamental scope limitation that's worth being clear about.
An SBOM tells you: "this build artifact contains these components at these versions." It doesn't tell you:
- Whether the build system that produced the artifact was compromised
- Whether the source code that was compiled matched the source code in your repository
- Whether a malicious dependency was injected into the build environment without appearing in the dependency manifest
- Whether the person who triggered the build had the authority to do so
These are the vectors that supply chain attacks actually exploit. The Solarwinds attack didn't need to tamper with the SBOM β it injected malicious code into the build process. An SBOM would have correctly listed Solarwinds' own components, none of which were "vulnerable" in the CVE sense.
The GhostAction Attack and 2025 Supply Chain Incidents
The GhostAction attack of early 2025 was a watershed moment for GitHub Actions supply chain security. Attackers compromised a widely-used third-party GitHub Action by gaining access to the action's repository and modifying the workflow code to exfiltrate secrets from any repository that used the action. Because the action was pinned by tag rather than commit SHA, every repository using @v1 automatically pulled the compromised version.
The scale was significant: The compromised action had millions of weekly uses across tens of thousands of repositories. In many cases, organisations were using it as part of their CI/CD pipelines with access to production deployment credentials. The attacker had a window of several hours before the action was taken down.
The npm ecosystem saw similar incidents in 2025 β packages with hundreds of thousands of weekly downloads were compromised through account takeovers of maintainers who had enabled SMS-only MFA. Once the maintainer account was compromised, the attacker published a new version with a postinstall script that exfiltrated environment variables.
In both cases, an SBOM would have been accurate and useless. The package versions listed were the latest legitimate versions β which happened to be compromised. What was missing was a way to verify that the published artifact matched the expected build process.
What Build Provenance Actually Means
Build provenance is a cryptographically verifiable record of how an artifact was produced. It answers questions like:
- What source code commit was this built from?
- What build system and version ran the build?
- What environment variables and build parameters were set?
- Who triggered the build, and when?
- Was the build environment isolated from external influence?
The provenance record is signed by the build system, and the signature can be verified independently by anyone who consumes the artifact. If the provenance says "built from commit abc123 on GitHub Actions runner version 2.310.2", you can verify that the artifact you're using actually came from that process β not from someone's laptop or a compromised build server.
SLSA Framework Levels Explained
Supply-chain Levels for Software Artifacts (SLSA, pronounced "salsa") is a framework from Google that defines progressively stronger guarantees about build integrity.
SLSA Level 1: Provenance exists
The build process generates a provenance document. It may not be signed or verified, but the information is present. This establishes a baseline β you at least know what claims are being made about the build.
SLSA Level 2: Signed provenance from a hosted build service
The provenance is generated and signed by a hosted build service (like GitHub Actions, GitLab CI, or Google Cloud Build). The signature can be verified, establishing that the provenance wasn't tampered with after the fact. This is achievable by most teams without major infrastructure changes.
SLSA Level 3: Hardened build environment
The build environment is isolated and hardened β each build runs in an ephemeral environment that can't be influenced by previous builds or external state. The build is reproducible, and the provenance includes enough information to fully reconstruct the build environment.
SLSA Level 4: Two-party review and hermetic builds
Source code changes require two-party review. Builds are hermetic β all dependencies are pre-declared and fetched deterministically, with no network access during the build. This level provides strong guarantees but requires significant process and infrastructure investment.
Where to start: Most organisations should target SLSA Level 2 as their near-term goal. It's achievable on GitHub Actions or GitLab CI in a day or two of work, and it dramatically raises the bar against the most common supply chain attack patterns. Level 3 is the right target for critical components or regulated industries.
Sigstore: Keyless Signing in Practice
Sigstore is an open-source project (backed by Google, Red Hat, and Purdue University, now a Linux Foundation project) that provides free, keyless artifact signing using short-lived certificates tied to OIDC identities.
The "keyless" part is significant. Traditional code signing requires managing long-lived private keys β storing them securely, rotating them, revoking them if compromised. Sigstore's approach uses short-lived certificates issued by the Fulcio certificate authority, tied to an OIDC token (from GitHub Actions, Google, or other OIDC providers).
Every signing event is recorded in an append-only, publicly auditable transparency log called Rekor. You can verify that a signature was made at a specific time by a specific identity β and that the transparency log record hasn't been tampered with.
Cosign for Container Image Signing
Cosign is the Sigstore tool for signing and verifying container images. It stores signatures in the same OCI registry as the image itself, using a separate tag. Verification pulls the signature from the registry and checks it against the transparency log.
# Sign a container image (keyless, using OIDC identity) cosign sign --yes ghcr.io/myorg/myapp:v1.2.3 # Verify a container image signature cosign verify \ --certificate-identity-regexp="https://github.com/myorg/myapp" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ ghcr.io/myorg/myapp:v1.2.3 # Attach a custom attestation (e.g., SBOM) cosign attest --predicate sbom.json --type spdxjson \ ghcr.io/myorg/myapp:v1.2.3 # Verify the attestation cosign verify-attestation \ --type spdxjson \ --certificate-identity-regexp="https://github.com/myorg/myapp" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ ghcr.io/myorg/myapp:v1.2.3
in-toto Attestation Format
in-toto is a framework for supply chain integrity that defines a standard format for attestations β signed statements about an artifact. The in-toto Attestation Framework (ITE-6) defines a common envelope that Sigstore, SLSA, and other tools use to represent provenance claims consistently.
An in-toto attestation has three parts: the statement type (what kind of claim this is), the subject (the artifact being attested), and the predicate (the actual claim data). SLSA provenance is expressed as an in-toto attestation with the SLSA predicate type.
GitHub Actions Provenance Generation
GitHub Actions has native SLSA provenance generation via the actions/attest-build-provenance action. It generates a signed provenance attestation for any artifact you build, tied to the GitHub Actions OIDC token for the workflow run.
jobs: build-and-attest: runs-on: ubuntu-latest permissions: id-token: write # required for OIDC signing attestations: write # required for attest action contents: read steps: - uses: actions/checkout@v4 - name: Build artifact run: | make build sha256sum dist/myapp > dist/myapp.sha256 - name: Generate SLSA provenance attestation uses: actions/attest-build-provenance@v1 with: subject-path: dist/myapp # Attestation is signed with OIDC token and stored in # GitHub's attestation store, verifiable via gh CLI
Verifying Provenance in CI/CD
Generating provenance is only half the picture. You also need to verify it when consuming artifacts β in your deployment pipeline, in your container admission controller, and anywhere else you pull external artifacts.
# Verify GitHub attestation for a release artifact gh attestation verify dist/myapp \ --owner myorg \ --predicate-type https://slsa.dev/provenance/v1 # Policy: only accept artifacts built from main branch cosign verify-attestation \ --type slsaprovenance \ --policy policy.cue \ ghcr.io/myorg/myapp:latest
// Only accept builds from main branch via GitHub Actions predicate: { buildType: "https://slsa.dev/provenance/v1" builder: id: =~"https://github.com/slsa-framework" invocation: configSource: ref: "refs/heads/main" }
Adoption Roadmap
Here's a practical sequence for teams that are starting from zero:
- Pin your GitHub Actions by commit SHA, not tag. This is a five-minute change that protects against the GhostAction class of attack immediately. Use a tool like Dependabot or
pin-github-actionto automate this. - Add container image signing with cosign. Add a signing step to your release workflow. This gets you SLSA Level 2 for container artifacts with minimal effort.
- Generate SLSA provenance for releases. Use
actions/attest-build-provenanceor the SLSA GitHub generator for binary artifacts. - Add provenance verification to your deployment pipeline. Reject artifacts that can't be verified against your provenance policy.
- Tighten build environment isolation. Move toward SLSA Level 3 by restricting network access during builds and enforcing ephemeral build environments.
The tooling is much better now: Two years ago, setting up Sigstore and SLSA was a multi-week project. Today, GitHub's built-in attestation support and the maturity of the cosign and slsa-github-generator projects mean you can get to SLSA Level 2 for most artifact types in an afternoon.
Detect Supply Chain Risks in Your Dependencies
AquilaX SCA scanner detects dependency confusion attacks, typosquatting, and malicious package patterns across your full dependency tree.
Start Free Scan