CI SBOM Attestation Pipeline Guide

This guide is for CI/CD engineers wiring Stella Ops evidence checks into a build pipeline. It shows how to canonicalize an SBOM, produce and verify a DSSE attestation, and validate VEX mapping using the bundled Gitea Actions workflow templates, including an air-gapped configuration.

Overview

This guide explains how to integrate Stella Ops’ SBOM canonicalization, DSSE attestation, and VEX mapping checks into your CI/CD pipeline using Gitea Actions workflow templates.

The pipeline consists of three stages:

  1. SBOM Canonicalization - Validate schema and compute deterministic canonical_id
  2. DSSE Attest + Verify - Sign attestation and verify transparency log inclusion
  3. VEX Mapping - Validate VEX documents and verify artifact targeting

Prerequisites

Quick Start

Stage 1: SBOM Canonicalization Check

Add to your workflow:

jobs:
  sbom-check:
    uses: ./.gitea/workflows/templates/sbom-canonicalization-check.yml
    with:
      bom_path: sbom.json
      # Optional: pin to a known canonical_id for regression testing
      # expected_canonical_id: 'sha256:abc123...'

What it does:

Outputs:

Stage 2: DSSE Attestation + Verification

jobs:
  attest:
    needs: sbom-check
    uses: ./.gitea/workflows/templates/dsse-attest-verify-check.yml
    with:
      subject_ref: 'ghcr.io/org/repo@sha256:...'
      predicate_path: sbom.json
      signing_mode: keyless  # or 'key'
      predicate_type: 'https://cyclonedx.org/bom'
      # skip_rekor: true  # For air-gapped environments

What it does:

Signing Modes:

ModeDescriptionWhen to Use
keylessFulcio/OIDC ephemeral certificateCI runners with OIDC (default)
keyCosign key pair (PEM)Air-gapped, self-managed keys

Stage 3: VEX Mapping Check

jobs:
  vex-check:
    needs: sbom-check
    uses: ./.gitea/workflows/templates/vex-mapping-check.yml
    with:
      vex_path: vex.json
      vex_format: openvex  # or 'cyclonedx'
      canonical_id: ${{ needs.sbom-check.outputs.canonical_id }}

What it does:

Complete Pipeline Example

name: SBOM Evidence Pipeline

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build-and-scan:
    runs-on: ubuntu-latest
    outputs:
      sbom_path: sbom.json
      image_ref: ${{ steps.build.outputs.image_ref }}
    steps:
      - uses: actions/checkout@v4
      - name: Build image
        id: build
        run: |
          # Build your container image
          docker build -t myapp .
          echo "image_ref=ghcr.io/org/myapp@sha256:..." >> $GITHUB_OUTPUT
      - name: Generate SBOM
        run: stella scan --image myapp --output-sbom sbom.json --format cyclonedx-1.7
      - uses: actions/upload-artifact@v4
        with:
          name: evidence
          path: sbom.json

  canonicalize:
    needs: build-and-scan
    uses: ./.gitea/workflows/templates/sbom-canonicalization-check.yml
    with:
      bom_path: sbom.json

  attest:
    needs: [build-and-scan, canonicalize]
    if: github.event_name != 'pull_request'
    uses: ./.gitea/workflows/templates/dsse-attest-verify-check.yml
    with:
      subject_ref: ${{ needs.build-and-scan.outputs.image_ref }}
      predicate_path: sbom.json
      signing_mode: keyless

  vex-check:
    needs: canonicalize
    if: hashFiles('vex.json') != ''
    uses: ./.gitea/workflows/templates/vex-mapping-check.yml
    with:
      vex_path: vex.json
      vex_format: openvex
      canonical_id: ${{ needs.canonicalize.outputs.canonical_id }}

Air-Gap Mode

For environments without internet access:

  1. Skip Rekor: Set skip_rekor: true in the attestation workflow
  2. Use keyed signing: Pre-distribute cosign keys, use signing_mode: key
  3. Bundle schemas locally: Schemas are already in docs/schemas/ (no network fetch)
  4. Verification: Use bundled checkpoints for offline Rekor verification:
    stella attest verify --offline --checkpoint-bundle /path/to/checkpoints
    

Failure Modes

AssertionFailureWhat to Do
Schema validationInvalid CycloneDXCheck SBOM generator version; validate against docs/schemas/cyclonedx-bom-1.7.schema.json
Determinism checkHash differs between runsNon-deterministic SBOM generation; check for timestamps, random values, unstable ordering
Regression checkcanonical_id changedSBOM content changed; update expected_canonical_id or investigate drift
Attestation signingCosign errorCheck signing key/OIDC token; verify registry access
Attestation verificationSignature invalidKey mismatch or tampered attestation; re-sign
Rekor proofEntry not foundMay be pending; retry after 30s; check Rekor connectivity
VEX schemaInvalid documentCheck VEX format matches vex_format input; validate manually
VEX field assertionsMissing statusVEX statements must have status field; check VEX generator
Target mismatchcanonical_id not in VEXVEX document targets different artifact; verify PURL/product matching