SSDF Attestation in CI: A Step-by-Step Guide

Angle: Turn the OMB M-24-04/CISA secure-software attestation into code by wiring SSDF 1.1 CI/CD controls, software provenance, and SBOM in builds directly into your pipeline—so Legal can file confidently and Engineering keeps shipping.

7 Proven Steps for SSDF 1.1 CI/CD Attestation

Who this is for: Engineering leaders, platform teams, and DevSecOps owners who need a secure software attestation that’s reproducible from CI signals—without building a whole new stack.


What you’ll build in 20 minutes

  • SSDF 1.1 gates for: secure build environments, dependency hygiene, signing/provenance, and release reviews—with artifacts auto-exported for the attestation form.
  • SBOM in builds (CycloneDX) + vuln scanning that fails on critical issues.
  • Software provenance (SLSA-style) + artifact signing (cosign) + verify-at-deploy.
  • An immutable evidence bucket and a tiny script that maps pipeline checks to the attestation questions.

Step 1 — Lock down the build environment (ephemeral, minimal, signed)

SSDF 1.1 CI/CD emphasizes trustworthy builds. Use ephemeral runners and least-privilege tokens, then sign everything your pipeline emits.

# .github/workflows/secure-build.yml
name: secure-build
on: [push]

permissions:
  contents: read
  id-token: write        # for OIDC -> cloud
  packages: read
  security-events: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 1 }  # no untrusted history in workspace

      - name: Harden runner
        run: |
          set -euo pipefail
          sudo sysctl -w kernel.unprivileged_bpf_disabled=1
          sudo mount -o remount,ro /boot || true

      - name: Set up Go/Node (example)
        uses: actions/setup-node@v4
        with: { node-version: '20' }

      - name: Build
        run: npm ci && npm run build

Why it matters: Predictable, ephemeral builds reduce the chance of cross-build contamination, a key pillar for secure software attestation.


Step 2 — Generate an SBOM in builds (CycloneDX)

Create an SBOM in builds and keep it next to your artifact.

  sbom:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v4
      - name: Install CycloneDX Node tool
        run: npm i -g @cyclonedx/cyclonedx-npm
      - name: Make SBOM (CycloneDX JSON)
        run: cyclonedx-npm --output-file sbom.cdx.json --spec-version 1.5
      - uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.cdx.json

Step 3 — Scan dependencies & fail on critical vulns

Treat “critical” as build-failing and keep the report for attestation evidence.

  deps-scan:
    runs-on: ubuntu-latest
    needs: sbom
    steps:
      - uses: actions/download-artifact@v4
        with: { name: sbom }
      - name: Install grype
        run: |
          curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
      - name: Scan SBOM
        run: grype sbom:sbom.cdx.json -o json > grype.json || true
      - name: Fail on CRITICAL
        run: |
          jq -e '.matches | any(.vulnerability.severity=="Critical")' grype.json && \
          { echo "Critical vulns found"; exit 1; } || echo "No criticals"
      - uses: actions/upload-artifact@v4
        with:
          name: grype-report
          path: grype.json

Quick external posture snapshot (to seed fixes)

Before release, run a fast check of your public assets and attach the result to your evidence:

Free Website Vulnerability Scanner Landing Page:

Screenshot of the free tools webpage where you can access security assessment tools for different vulnerability detection.
Screenshot of the free tools webpage where you can access security assessment tools for different vulnerability detection.

Step 4 — Sign your artifact and emit SLSA-style provenance

Use cosign for signing and a provenance attestation that records who built what, when, and from which commit.

  sign-and-attest:
    runs-on: ubuntu-latest
    needs: build
    env:
      IMAGE: ghcr.io/acme/app:${{ github.sha }}
    steps:
      - uses: actions/checkout@v4
      - name: Build container
        run: docker build -t $IMAGE .
      - name: Login to GHCR
        run: echo $CR_PAT | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin
        env:
          CR_PAT: ${{ secrets.CR_PAT }}
      - name: Push image
        run: docker push $IMAGE
      - name: Install cosign
        run: curl -sSfL https://raw.githubusercontent.com/sigstore/cosign/main/install.sh | sh -s -- -b /usr/local/bin
      - name: Keyless sign (OIDC)
        run: cosign sign $IMAGE --yes
      - name: Generate provenance (in-toto statement)
        run: |
          cat > provenance.json <<'JSON'
          {
            "_type": "https://in-toto.io/Statement/v0.1",
            "subject": [{"name": "${{ env.IMAGE }}"}],
            "predicateType": "https://slsa.dev/provenance/v1",
            "predicate": {
              "builder": {"id": "github-actions"},
              "buildType": "container",
              "invocation": {"parameters": {"sha": "${{ github.sha }}"}}
            }
          }
          JSON
      - name: Attach provenance (cosign attach)
        run: cosign attest --attachment-tag=provenance $IMAGE --predicate provenance.json --yes
      - uses: actions/upload-artifact@v4
        with: { name: provenance, path: provenance.json }

Step 5 — Verify at deploy (policy gate)

Only deploy images that are signed and have valid provenance.

# .github/workflows/deploy.yml (snippet)
name: deploy
on: [workflow_dispatch]

jobs:
  verify-and-deploy:
    runs-on: ubuntu-latest
    env:
      IMAGE: ghcr.io/acme/app:${{ inputs.sha }}
    steps:
      - name: Install cosign
        run: curl -sSfL https://raw.githubusercontent.com/sigstore/cosign/main/install.sh | sh -s -- -b /usr/local/bin
      - name: Verify signature
        run: cosign verify $IMAGE --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp "https://github.com/acme/.+"
      - name: Verify provenance (attestation)
        run: cosign verify-attestation $IMAGE --type slsaprovenance --certificate-oidc-issuer https://token.actions.githubusercontent.com
      - name: Deploy (only if both checks passed)
        run: ./scripts/deploy.sh $IMAGE

Step 6 — Release review + evidence capture

Enforce a release review and persist the decision (who approved, what was reviewed, links to SBOM, scan, provenance).

  release-review:
    runs-on: ubuntu-latest
    needs: [deps-scan, sign-and-attest]
    steps:
      - uses: actions/checkout@v4
      - name: Require code owners + 2 approvals
        run: |
          [[ "${{ github.event.pull_request.mergeable_state }}" != "blocked" ]] || \
          { echo "Approvals missing"; exit 1; }
      - name: Compose release-evidence.json
        run: |
          jq -n --arg sha "${{ github.sha }}" \
            --arg approver "${{ github.actor }}" \
            --slurpfile sbom sbom.cdx.json \
            --slurpfile grype grype.json \
            --slurpfile prov provenance.json \
            '{commit:$sha, approver:$approver, sbom:$sbom[0], scan:$grype[0], provenance:$prov[0]}' \
            > release-evidence.json
      - uses: actions/upload-artifact@v4
        with: { name: release-evidence, path: release-evidence.json }

Step 7 — Store artifacts in an immutable evidence bucket

Keep your secure software attestation proofs safe with S3 Object Lock + versioning.

# evidence_store.tf (Terraform)
resource "aws_s3_bucket" "evidence" {
  bucket = "org-secure-evidence"
  object_lock_enabled = true
}

resource "aws_s3_bucket_versioning" "evidence" {
  bucket = aws_s3_bucket.evidence.id
  versioning_configuration { status = "Enabled" }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "evidence" {
  bucket = aws_s3_bucket.evidence.id
  rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } }
}

resource "aws_s3_bucket_object_lock_configuration" "evidence" {
  bucket = aws_s3_bucket.evidence.id
  rule {
    default_retention { mode = "COMPLIANCE" days = 365 }
  }
}

CI upload example:

  publish-evidence:
    runs-on: ubuntu-latest
    needs: [release-review]
    steps:
      - uses: actions/download-artifact@v4
        with: { path: artifacts }
      - name: Upload to S3 (immutable)
        run: |
          aws s3 cp artifacts s3://org-secure-evidence/${{ github.run_id }}/ --recursive --sse AES256

Map pipeline checks → attestation questions (auto-export)

Export a tiny JSON summary the compliance team can drop into the form.

# attest_summary.py
import json, pathlib

def emit():
    sbom_exists = pathlib.Path("sbom.cdx.json").exists()
    grype = json.loads(pathlib.Path("grype.json").read_text()) if pathlib.Path("grype.json").exists() else {}
    criticals = [m for m in grype.get("matches", []) if m.get("vulnerability",{}).get("severity")=="Critical"]

    out = {
      "ssdf_1_1": True,
      "secure_build_env": True,
      "sbom_in_builds": sbom_exists,
      "vuln_policy": "fail_on_critical",
      "criticals_found": len(criticals),
      "provenance_emitted": pathlib.Path("provenance.json").exists(),
      "artifact_signed": True,
      "release_review": pathlib.Path("release-evidence.json").exists()
    }
    print(json.dumps(out, indent=2))

if __name__ == "__main__":
    emit()

Add to CI:

  attest-export:
    runs-on: ubuntu-latest
    needs: [release-review]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with: { path: . }
      - name: Emit attestation summary
        run: python attest_summary.py > attestation-summary.json
      - uses: actions/upload-artifact@v4
        with: { name: attestation-summary, path: attestation-summary.json }

One-file blueprint: SSDF 1.1 CI/CD starter

Drop this reusable workflow into .github/workflows/ssdf11.yml and call it from your apps.

name: ssdf11-starter
on:
  workflow_call:
    inputs:
      image_name: { required: true, type: string }
    secrets:
      CR_PAT: { required: true }

permissions:
  contents: read
  id-token: write
  security-events: write
  packages: write

jobs:
  build-sbom-scan-sign:
    uses: ./.github/workflows/secure-build.yml

  verify-release:
    uses: ./.github/workflows/deploy.yml
    with:
      sha: ${{ github.sha }}

“Attach evidence” playbook for Legal & Sales

  • Bundle: sbom.cdx.json, grype.json, provenance.json, cosign verification output, release-evidence.json, and attestation-summary.json.
  • Store in the immutable bucket (Object Lock).
  • Reference the evidence bundle in vendor questionnaires and secure software attestation filings to cut turnaround time.

Sample vulnerability report—to check Website Vulnerability:

An example of a vulnerability assessment report generated using our free tool provides valuable insights into potential vulnerabilities.
An example of a vulnerability assessment report generated using our free tool provides valuable insights into potential vulnerabilities.

Where Cyber Rely helps next

  • Need a gap view before you wire the SSDF gates? Book a Risk Assessment to prioritize quick wins:
    👉 Risk Assessment Services
  • Have findings from SBOM/vuln scans or missing provenance? We’ll help remediate with developers:
    👉 Remediation Services

For more developer-ready playbooks, explore the Cyber Rely blog and these recent reads:


Wrap-up

You don’t need a new platform to satisfy secure software attestation—you need SSDF 1.1 CI/CD gates that produce software provenance and evidence as a by-product of normal shipping. Use the workflows above, attach your evidence bundle, and you’re ready for CISA’s attestation form—and vendor due diligence—without slowing delivery.

If you want a second set of eyes on your plan, talk to us at Cyber Rely: https://www.cybersrely.com/ or run a quick snapshot now: https://free.pentesttesting.com/.


Free Consultation

If you have any questions or need expert assistance, feel free to schedule a Free consultation with one of our security engineers>>

🔐 Frequently Asked Questions (FAQs)

Find answers to commonly asked questions about SSDF 1.1 CI/CD attestation.

Get a Quote

Leave a Comment

Your email address will not be published. Required fields are marked *

Cyber Rely Logo cyber security
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.