Back to Blog
DevOps15 min readJun 2026

Securing the Software Supply Chain (SLSA, SBOM, Signing)

Most of your production code isn't yours, it's dependencies, base images, and build tools you didn't write. The supply chain is now the attack surface. Here's how to shift security left: SBOMs, artifact signing and provenance with Sigstore, the SLSA levels, and least-privilege CI.

Supply ChainSLSASBOMDevSecOpsSecurity
SB

Sri Balaji

Founder ยท TheSimplifiedTech

On this page

You didn't write most of your production code

Open your package.json or go.mod and count the direct dependencies. Now count the *transitive* ones, the dependencies of your dependencies. It's usually hundreds, often over a thousand. Add your base container image, your build tools, your CI runner, and the plugins in your pipeline. The uncomfortable truth: most of the code running in your production environment was written by strangers, pulled in automatically, and trusted by default.

That's the software supply chain, and it's now the favoured attack surface. SolarWinds, Codecov, the event-stream and xz backdoors, none of these broke in through your code. They came in through something you trusted. Securing the supply chain means treating every input to your build as untrusted until proven otherwise: knowing exactly what's in your artifacts (SBOMs), proving they're unmodified (signing and provenance), and hardening the build itself (SLSA). This article covers all three.

Who this is for

Engineers who own a CI/CD pipeline and want to make it defensible. No security background needed, we define SBOM, provenance, SLSA, and signing from scratch. Familiarity with a pipeline (build โ†’ test โ†’ publish) is assumed.

Shift left: security in the pipeline, not after it

Shifting left means moving security checks as early in the pipeline as possible, catching a vulnerable dependency at pull-request time, not in a pen-test six months after it shipped.

The later you find a problem, the more it costs. A vulnerable library caught by a scanner on the PR is a one-line bump. The same library found in production after an incident is an outage, a disclosure, and a postmortem. So the supply-chain discipline is to push every check as far upstream as it'll go, into the build, the PR, the merge.

๐Ÿ“‹ Know every ingredient in every dishSBOM, inventory of what's in your artifact
๐Ÿšš Verify the supplier sealed the deliverySigning, prove the artifact is unmodified
๐Ÿ“น Cameras on the whole prep processProvenance, how the artifact was built
โญ A health-inspection grade on the doorSLSA level, how trustworthy the build is
Securing a restaurant kitchen.

SBOM: an ingredients label for your software

A Software Bill of Materials is a complete, machine-readable list of every component in an artifact, every library, version, and license, transitive ones included. You can't secure what you can't see, and when the next log4shell drops, the difference between "are we affected?" taking five minutes versus five days is whether you have SBOMs. Generate one for every build and store it as an artifact.

sbom.sh
bash
# Generate an SBOM for a built container image (Syft)
syft registry.example.com/api:v2 -o spdx-json > sbom.spdx.json

# Later: instantly answer 'are we exposed to CVE-2025-XXXX?'
grep -i "log4j" sbom.spdx.json

# Or scan the SBOM itself against the vuln database (Grype)
grype sbom:sbom.spdx.json --fail-on high

Pro tip

SPDX and CycloneDX are the two standard SBOM formats, pick either; both are widely supported. The win isn't the format, it's the habit: an SBOM per build, stored and queryable, so 'what's affected?' is a grep and not an archaeology project.

Signing and provenance: prove it's really yours

An SBOM tells you what's *inside* an artifact. Signing tells you the artifact is the one you built and hasn't been tampered with since. Without a signature, an attacker who compromises your registry can swap your image for theirs and your cluster will happily run it. Sigstore (via the cosign tool) made signing practical: keyless signing tied to your CI's OIDC identity, with the signature published to a public transparency log.

Provenance goes one step further: a signed statement of *how* the artifact was built, which source commit, which builder, which steps. Together, signature + provenance let a deployer verify "this image came from our repo, built by our CI, from this exact commit" before it ever runs.

.github/workflows/release.yml
yaml
# A scan + sign + provenance stage in CI
jobs:
  publish:
    permissions:
      id-token: write      # OIDC identity for keyless signing
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: docker build -t $IMAGE .

      - name: Scan before publishing
        run: grype $IMAGE --fail-on high   # block on high/critical CVEs

      - name: Generate SBOM
        run: syft $IMAGE -o spdx-json > sbom.spdx.json

      - name: Push image
        run: docker push $IMAGE

      - name: Sign (keyless, via OIDC)
        run: cosign sign --yes $IMAGE

      - name: Attach SBOM as a signed attestation
        run: cosign attest --yes --predicate sbom.spdx.json \
               --type spdxjson $IMAGE
verify-before-deploy.sh
bash
# Deployers verify the signature and the source repo BEFORE running it
cosign verify $IMAGE \
  --certificate-identity-regexp 'https://github.com/acme/.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

# In a cluster, enforce this automatically with an admission policy
# (e.g. Sigstore policy-controller / Kyverno) so unsigned images are rejected.

Signing without verification is theatre

Signing your images does nothing if nothing checks the signature. The value only materialises when an admission controller (policy-controller, Kyverno, or a Connaisseur-style gate) rejects unsigned or wrongly-signed images at deploy time. Sign in CI, then enforce verification in the cluster, both halves, or neither.

SLSA: a maturity ladder for your build

SLSA (Supply-chain Levels for Software Artifacts, said "salsa") is a framework that grades how trustworthy your *build process* is. It's a ladder, each level is a concrete, achievable step, not an all-or-nothing certification. You don't need the top rung; you need to know which rung you're on and climb one.

LevelWhat it requiresWhat it stops
L1Build is scripted; provenance existsNothing built by hand / undocumented
L2Hosted build service; signed provenanceTampering with provenance after the fact
L3Hardened, isolated builds; non-falsifiable provenanceA compromised build job forging its own provenance
Climb one level at a time. Most teams should target L2 โ†’ L3 as a realistic, high-value goal.

The jump from L1 to L2 is mostly "use a real CI service and sign the provenance", achievable in an afternoon. L3 (isolated, ephemeral, hardened build runners) is where you stop a compromised build step from lying about what it produced. Pick a target, measure where you are, and treat the gap as a backlog.

Least-privilege CI and dependency hygiene

Your pipeline is itself a high-value target, it has registry credentials, signing identity, and often cluster access. Treat it like production. And the dependencies it pulls deserve the same suspicion as external input, because that's exactly what they are.

  • Pin dependencies by hash, not just version. A version tag can be re-pointed; a hash can't. Use lockfiles with integrity hashes and pin GitHub Actions to a commit SHA, never a moving tag like @v4.
  • Scope CI tokens to the minimum. Per-job, least-privilege, short-lived. A workflow that builds shouldn't hold deploy credentials. Prefer OIDC over long-lived secrets.
  • Scan dependencies and images on every PR, and fail the build on high/critical CVEs. Make the safe path the default path.
  • Use a private proxy/registry for dependencies so a deleted or hijacked upstream package can't break or poison your builds.
  • Review what you add. A new dependency is new code from a stranger running with your privileges. Weigh whether you need it at all.

The moving-tag trap

Pinning a GitHub Action to @v4 means you run whatever the maintainer (or whoever compromises their account) points v4 at, automatically, on your next build, with your secrets. Pin to a full commit SHA and update deliberately. This single change closes one of the most common CI attack paths.

Common mistakes that cost hours

  1. No SBOM, so every new CVE is an archaeology project. When the next log4shell drops, 'are we affected?' should be a grep, not a week of spelunking.
  2. Signing images but never verifying them. A signature nothing checks is decoration. Enforce verification at deploy time with an admission policy.
  3. Pinning Actions and base images to moving tags. @v4 and :latest mean you silently run whatever someone repoints them to. Pin by SHA / digest.
  4. Over-privileged CI. A single broad token turns a compromised build into a compromised registry, signing key, and cluster. Scope tokens per job; prefer OIDC.
  5. Scanning only in production / on a schedule. By then it's an incident. Scan on the PR and fail the build on high-severity findings.
  6. Treating SLSA as pass/fail. It's a ladder. Not knowing your level is the real failure, measure, then climb one rung.

Takeaways

Supply-chain security in six lines

  • Most of your production code is other people's, the supply chain is the attack surface.
  • Shift left: catch vulnerable dependencies on the PR, not in a post-incident pen-test.
  • SBOM = an ingredients label per build, so 'are we affected?' is a grep.
  • Sign artifacts (cosign/Sigstore) AND enforce verification at deploy, both halves or neither.
  • Provenance proves how an artifact was built; SLSA grades how trustworthy that build is.
  • Pin by hash, scope CI tokens tightly, scan every PR, your pipeline is production.

Where to go next

Supply-chain security lives inside your pipeline and is one expression of a broader 'trust nothing by default' mindset:

Want to go deeper?

This article covers concepts taught hands-on in the Cloud Engineer and DevOps career paths, with real terminal labs, production scenarios, and structured lessons.