DEV Community

Cover image for Securing the Software Supply Chain (SLSA, SBOM, Signing)
Sri Balaji
Sri Balaji

Posted on • Originally published at thesimplifiedtech.com

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

TL;DR: Most of your production code is dependencies you didn't write, so the supply chain is the attack surface. Shift security left with SBOMs, signing and provenance via Sigstore, the SLSA levels, and least-privilege CI.

Contents

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.

In the real world In tech
📋 Know every ingredient in every dish SBOM, inventory of what's in your artifact
🚚 Verify the supplier sealed the delivery Signing, prove the artifact is unmodified
📹 Cameras on the whole prep process Provenance, how the artifact was built
⭐ A health-inspection grade on the door SLSA 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

# 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
Enter fullscreen mode Exit fullscreen mode

💡 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

# 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
Enter fullscreen mode Exit fullscreen mode

verify-before-deploy.sh

# 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.
Enter fullscreen mode Exit fullscreen mode

⚠️ 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.

Level What it requires What it stops
L1 Build is scripted; provenance exists Nothing built by hand / undocumented
L2 Hosted build service; signed provenance Tampering with provenance after the fact
L3 Hardened, isolated builds; non-falsifiable provenance A 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:


Originally published on TheSimplifiedTech, where this guide is interactive, with in-browser terminal labs and diagrams. Learn cloud and DevOps by doing, no videos.

Top comments (0)