← Back to dashboard
supply-chaintoolingfresh

SLSA Provenance & Supply-Chain Integrity

Text
███████╗██╗     ███████╗ █████╗
██╔════╝██║     ██╔════╝██╔══██╗
███████╗██║     ███████╗███████║
╚════██║██║     ╚════██║██╔══██║
███████║███████╗███████║██║  ██║
╚══════╝╚══════╝╚══════╝╚═╝  ╚═╝

SLSA (Supply-chain Levels for Software Artifacts) is an OpenSSF framework for build integrity. It answers one question for whoever installs your artifact: "Here is the verifiable, unforgeable record of how and where this was built." This guide is policy for codeAmani: decide a provenance target at plan time, not at release.


What SLSA is (and isn't)

SLSA is not a package, an action, or a vendor. It is a maturity framework. The famous numbers (L1/L2/L3) are assurance levels on the Build track — each adds a stronger guarantee about how trustworthy an artifact's provenance is.

LevelGuaranteeRoughly achieved by
Build L1Provenance exists — an automated build emits a record of how the artifact was made. Falsifiable.A build script + recorded metadata
Build L2Provenance is signed & authenticated, built on a hosted platform. Binds artifact → source repo + builder.npm publish --provenance from GitHub Actions
Build L3+ Build isolation / non-falsifiable — signing happens in a trusted control plane the build steps cannot reach.slsa-github-generator isolated builder

SLSA v1.1 also defines a Source track (commit/review integrity). The L1–L3 most people mean are Build-track. This guide targets the Build track.

The L2 → L3 jump is a trust-model choice

  • L2 says "I trust each developer." The platform signs from its control plane, but the build environment is tenant-controlled — anyone who can influence the pipeline can influence the build.
  • L3 says "I trust the platform, not the tenant." Provenance is generated in an isolated control plane, structurally resistant to forgery by build steps.

That is why npm publish --provenance is only L2 — build and signing share one tenant-controlled job. L3 requires the isolated builder below.


Decision rubric — does this project ship an artifact?

This is the only question that matters. SLSA protects distributed artifacts. If nothing is downloaded, there is nothing to attest.

Project archetypeShips a downloadable artifact?TargetRecipe
Knowledge base / docs (e.g. the tech-stack repo)Non/a — pin Actions, document build
Next.js app on Netlify/Vercel (dashboard, mail, kipaji-web)No — a deploy, not an artifactSLSA-aware onlyhardened CI, no provenance
Published npm package / CLI / MCP serverYes (registry)Build L3npm isolated builder ↓
GitHub Release tarballYes (release asset)Build L3generic generator ↓
Container imageYes (registry)Build L3container generator

Netlify/Vercel deployments do not emit SLSA3 provenance. To put provenance on a deployed app you would have to build the deployable artifact in GitHub Actions with the generic generator and deploy that attested output — heavy, and it fights Vercel's build-on-push model. Not worth it unless an artifact genuinely ships.


Recipe A — npm package → Build L3

Use the Node.js isolated builder. It builds in a control plane your scripts can't reach, generates non-falsifiable provenance, and the companion publish action pushes the package + provenance to npm.

Prerequisites on the package:

  • private: false (or publish to a private registry), plus repository, license, and files fields so only intended files ship.
  • An npm automation token stored as the NPM_TOKEN repo secret.
  • A version bump + a v* git tag to trigger the release.

.github/workflows/release-npm-slsa3.yml:

YAML
name: release-npm-slsa3
on:
  push:
    tags: ["v*"]

permissions: read-all   # tighten per-job below

jobs:
  build:
    permissions:
      id-token: write   # OIDC token for Sigstore signing
      contents: read    # checkout
      actions: read     # read workflow run metadata
    if: startsWith(github.ref, 'refs/tags/')
    uses: slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml@v2.1.0
    with:
      # In a monorepo, point at the package dir, e.g. packages/mcp-server
      run-scripts: "ci, test, build"   # run INSIDE the isolated builder before packing

  publish:
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - name: Set up npm registry auth
        uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: "https://registry.npmjs.org"
      - name: publish (package + provenance)
        uses: slsa-framework/slsa-github-generator/actions/nodejs/publish@v2.1.0
        with:
          access: public
          node-auth-token: ${{ secrets.NPM_TOKEN }}
          package-name: ${{ needs.build.outputs.package-name }}
          package-download-name: ${{ needs.build.outputs.package-download-name }}
          package-download-sha256: ${{ needs.build.outputs.package-download-sha256 }}
          provenance-name: ${{ needs.build.outputs.provenance-name }}
          provenance-download-name: ${{ needs.build.outputs.provenance-download-name }}
          provenance-download-sha256: ${{ needs.build.outputs.provenance-download-sha256 }}

The reusable workflow must be pinned to a @vX.Y.Z tag (not a branch or short SHA) — the generator refuses to run otherwise. Pin your other actions (setup-node, checkout) to full commit SHAs per house security policy.


Recipe B — GitHub Release tarball → Build L3

When you ship a downloadable asset (not a registry package), build it yourself, then hand the subjects (sha256 of each artifact, base64-encoded) to the generic generator, which signs and attaches .intoto.jsonl provenance to the Release.

.github/workflows/release-tarball-slsa3.yml:

YAML
name: release-tarball-slsa3
on:
  push:
    tags: ["v*"]

permissions: read-all

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci && npm run build
      - name: pack artifact
        run: tar -czf dist.tar.gz dist/
      - name: compute subjects
        id: hash
        run: echo "hashes=$(sha256sum dist.tar.gz | base64 -w0)" >> "$GITHUB_OUTPUT"

  provenance:
    needs: [build]
    permissions:
      actions: read     # detect the Actions environment
      id-token: write   # Sigstore signing
      contents: write   # upload provenance to the Release
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      upload-assets: true   # attach <artifact>.intoto.jsonl to the Release

Verification (the half people skip)

Provenance that nobody verifies buys nothing. Two consumer-side paths:

npm packages — simplest is the npm CLI after install:

Bash
npm audit signatures        # reports registry signature + provenance attestation status

Release tarballs / generic artifacts — use slsa-verifier:

Bash
# install (Go) — or grab a release binary
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@v2.7.0

slsa-verifier verify-artifact dist.tar.gz \
  --provenance-path dist.tar.gz.intoto.jsonl \
  --source-uri github.com/codeAmani-Solutions/<repo> \
  --source-tag v1.2.3

verify-artifact fails unless the artifact's digest, the source repo, and the builder identity all match — this is what makes a forged or swapped artifact detectable.

CI gate: for any dependency that publishes provenance, add a verify step in CI so an unverifiable build fails rather than silently proceeding.


What SLSA3 does NOT do (be honest about the boundary)

SLSA attests build integrity, not source benevolence. As of 2026 (see the OpenSSF "Mini Shai-Hulud" analysis), L3 provenance will faithfully sign an artifact built from malicious source or a compromised dependency — it proves the how/where, not that the code is safe. Provenance complements, does not replace:

  • dependency review / lockfile pinning,
  • secret scanning (this repo's gitleaks workflow),
  • code review and the SLSA Source track.

It defeats substitution attacks (dependency confusion, tampered artifacts, impostor publishers) — which is real, high-value coverage — not insider attacks.


codeAmani policy (planning & build integration)

  1. Plan-time decision. Every new project's plan records a Provenance target using the rubric above. Default for anything that ships an artifact: Build L3.
  2. Template-scaffolded. New shippable projects inherit a commented release workflow from codeAmani-labs-projects/_TEMPLATE-PROJECT/.github/workflows/release-slsa3.yml — enable it on first publish; zero retrofit.
  3. Always-loaded policy. The summary rubric lives in the root CLAUDE.md (## Supply-Chain & Provenance (SLSA)), so every Claude Code session inherits it.
  4. Freshness-tracked. The docs: URLs above are watched by the tech-stack checker; when the builder ships a new major (v2 → v3) this guide is flagged for review.

References