Skip to content

Tutorial 26 โ€” SBOM Generation and Artifact Signing

Secure your software supply chain with Software Bills of Materials (SBOMs) and cryptographic artifact signing. This tutorial covers generating SPDX and CycloneDX SBOMs, signing artifacts with Ed25519, verifying signatures, and integrating everything into CI/CD pipelines.

Package: agent-compliance (Python) ยท GitHub Actions workflows Formats: SPDX JSON, CycloneDX JSON Signing: Ed25519 (SDK), Sigstore (Python packages), ESRP (.NET packages)


What you'll learn

Section Topic
What is an SBOM? Why SBOMs matter for agent security
Generating SBOMs SPDX and CycloneDX generation
Ed25519 Artifact Signing Sign artifacts using the SDK's identity system
Verifying Signatures Verify artifact integrity
CI/CD Integration Automated SBOM and signing pipelines
SBOM Attestation Attesting SBOMs to releases
Cross-Reference Related tutorials

Prerequisites


What is an SBOM?

A Software Bill of Materials (SBOM) is a machine-readable inventory of all components, libraries, and dependencies in a software product. It's the software equivalent of a food ingredients label.

Why SBOMs Matter for Agent Systems

Agent systems have unique supply chain risks:

  1. Transitive dependencies โ€” An agent framework may pull in hundreds of packages, each a potential attack vector
  2. Tool definitions โ€” MCP tool descriptions are executable attack surface (see Tutorial 27)
  3. Model weights โ€” LLM models themselves are part of the supply chain
  4. Plugin ecosystem โ€” Third-party plugins introduce untrusted code

An SBOM lets you: - Audit what's actually in your deployed agent - Respond to CVEs by quickly checking if you're affected - Comply with regulatory requirements (Executive Order 14028, EU CRA) - Verify that the deployed artifact matches what was built

SBOM Formats

Format Standard Best For
SPDX ISO/IEC 5962:2021 Licence compliance, open source
CycloneDX OWASP standard Vulnerability tracking, security

The toolkit generates both formats โ€” use SPDX for licence compliance and CycloneDX for vulnerability management.


Generating SBOMs

ยง2.1 Using the Anchore SBOM Action

The toolkit uses the anchore/sbom-action to generate SBOMs from the repository:

# Generate SPDX SBOM
- uses: anchore/sbom-action@v0
  with:
    output-file: sbom.spdx.json
    format: spdx-json
    artifact-name: sbom-spdx

# Generate CycloneDX SBOM
- uses: anchore/sbom-action@v0
  with:
    output-file: sbom.cdx.json
    format: cyclonedx-json
    artifact-name: sbom-cyclonedx

ยง2.2 Local SBOM Generation

Generate SBOMs locally using Syft (the CLI behind the Anchore action):

# Install Syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s

# Generate SPDX SBOM
syft . -o spdx-json=sbom.spdx.json

# Generate CycloneDX SBOM
syft . -o cyclonedx-json=sbom.cdx.json

ยง2.3 SBOM Contents

An SPDX SBOM includes:

{
  "spdxVersion": "SPDX-2.3",
  "dataLicense": "CC0-1.0",
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "agent-governance-toolkit",
  "packages": [
    {
      "SPDXID": "SPDXRef-Package-python-agent-os-kernel",
      "name": "agent-os-kernel",
      "versionInfo": "2.1.0",
      "supplier": "Organization: Microsoft",
      "downloadLocation": "https://pypi.org/project/agent-os-kernel/",
      "licenseConcluded": "MIT"
    }
  ],
  "relationships": [
    {
      "spdxElementId": "SPDXRef-DOCUMENT",
      "relatedSpdxElement": "SPDXRef-Package-python-agent-os-kernel",
      "relationshipType": "DESCRIBES"
    }
  ]
}

A CycloneDX SBOM includes:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "components": [
    {
      "type": "library",
      "name": "agent-os-kernel",
      "version": "2.1.0",
      "purl": "pkg:pypi/agent-os-kernel@2.1.0",
      "licenses": [{"license": {"id": "MIT"}}]
    }
  ]
}

Ed25519 Artifact Signing

Use the toolkit's Ed25519 identity system to sign any artifact โ€” release binaries, SBOMs, policy files, or audit logs.

ยง3.1 Signing with the TypeScript package

import { AgentIdentity } from '@microsoft/agentmesh-sdk';
import { readFileSync, writeFileSync } from 'fs';

// 1. Generate (or load) a signing identity
const signer = AgentIdentity.generate('release-signer', ['sign.artifacts'], {
  name: 'Release Signing Identity',
  organization: 'security-team',
});

// 2. Read the artifact to sign
const artifact = readFileSync('dist/agent-os-kernel-2.1.0.tar.gz');

// 3. Sign the artifact
const signature = signer.sign(new Uint8Array(artifact));

// 4. Save the signature
writeFileSync(
  'dist/agent-os-kernel-2.1.0.tar.gz.sig',
  Buffer.from(signature),
);

// 5. Export the public key for verification
const publicIdentity = signer.toJSON();
writeFileSync(
  'dist/signing-identity.json',
  JSON.stringify(publicIdentity, null, 2),
);

console.log('Artifact signed successfully');
console.log('Signature:', Buffer.from(signature).toString('hex').slice(0, 32) + '...');
console.log('Signer DID:', signer.did);

ยง3.2 Signing with the Rust crate

use agentmesh::AgentIdentity;
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Generate a signing identity
    let signer = AgentIdentity::generate(
        "release-signer",
        vec!["sign.artifacts".into()],
    )?;

    // Read and sign the artifact
    let artifact = fs::read("dist/artifact.tar.gz")?;
    let signature = signer.sign(&artifact)?;

    // Save the signature
    fs::write("dist/artifact.tar.gz.sig", &signature)?;

    // Export public identity for verification
    let pub_json = signer.to_json()?;
    fs::write("dist/signing-identity.json", &pub_json)?;

    println!("Signed by: {}", signer.did);
    Ok(())
}

ยง3.3 Signing with the Go module

package main

import (
    "log"
    "os"

    agentmesh "github.com/microsoft/agent-governance-toolkit/agent-governance-golang"
)

func main() {
    // Generate signing identity
    signer, err := agentmesh.GenerateIdentity("release-signer", []string{"sign.artifacts"})
    if err != nil {
        log.Fatal(err)
    }

    // Read and sign the artifact
    artifact, err := os.ReadFile("dist/artifact.tar.gz")
    if err != nil {
        log.Fatal(err)
    }

    signature, err := signer.Sign(artifact)
    if err != nil {
        log.Fatal(err)
    }

    // Save signature
    os.WriteFile("dist/artifact.tar.gz.sig", signature, 0644)

    // Export public identity
    pubJSON, _ := signer.ToJSON()
    os.WriteFile("dist/signing-identity.json", pubJSON, 0644)

    log.Printf("Signed by: %s\n", signer.DID)
}

Verifying Signatures

ยง4.1 Verification with the TypeScript package

import { AgentIdentity } from '@microsoft/agentmesh-sdk';
import { readFileSync } from 'fs';

// 1. Load the signer's public identity
const signerJSON = JSON.parse(
  readFileSync('dist/signing-identity.json', 'utf-8'),
);
const signer = AgentIdentity.fromJSON(signerJSON);

// 2. Read the artifact and signature
const artifact = readFileSync('dist/agent-os-kernel-2.1.0.tar.gz');
const signature = readFileSync('dist/agent-os-kernel-2.1.0.tar.gz.sig');

// 3. Verify
const valid = signer.verify(
  new Uint8Array(artifact),
  new Uint8Array(signature),
);

if (valid) {
  console.log('โœ… Signature valid โ€” artifact is authentic');
  console.log('Signed by:', signer.did);
} else {
  console.error('โŒ Signature verification failed โ€” artifact may be tampered');
  process.exit(1);
}

ยง4.2 Verification with the Go module

// Load signer's public identity
pubJSON, _ := os.ReadFile("dist/signing-identity.json")
signer, _ := agentmesh.FromJSON(pubJSON)

// Read artifact and signature
artifact, _ := os.ReadFile("dist/artifact.tar.gz")
signature, _ := os.ReadFile("dist/artifact.tar.gz.sig")

// Verify
if signer.Verify(artifact, signature) {
    fmt.Println("โœ… Signature valid")
} else {
    fmt.Println("โŒ Verification failed")
    os.Exit(1)
}

CI/CD Integration

ยง5.1 Full SBOM + Signing Workflow

# .github/workflows/release.yml
name: Release with SBOM and Signing
on:
  release:
    types: [published]

permissions:
  contents: write
  id-token: write
  attestations: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Build the package
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install build && python -m build

      # Generate SBOMs
      - uses: anchore/sbom-action@v0
        with:
          output-file: sbom.spdx.json
          format: spdx-json

      - uses: anchore/sbom-action@v0
        with:
          output-file: sbom.cdx.json
          format: cyclonedx-json

      # Sign Python artifacts with Sigstore
      - uses: sigstore/gh-action-sigstore-python@v3
        with:
          inputs: dist/*.tar.gz dist/*.whl

      # Attest build provenance
      - uses: actions/attest-build-provenance@v2
        with:
          subject-path: dist/*

      # Upload to release
      - run: |
          gh release upload ${{ github.ref_name }} \
            sbom.spdx.json \
            sbom.cdx.json \
            dist/*.tar.gz \
            dist/*.whl \
            --clobber
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

ยง5.2 .NET Package Signing

For .NET packages, the toolkit supports ESRP-based signing:

# Sign NuGet packages (when ESRP is configured)
- name: Sign NuGet package
  run: |
    # ESRP signing (enterprise environments)
    # Authenticode sign the assembly
    # NuGet sign the package
    dotnet nuget verify dist/*.nupkg

ยง5.3 Verification in CI

Add a verification step to your deployment pipeline:

# Verify before deploying
- name: Verify artifact signature
  run: |
    node -e "
      const { AgentIdentity } = require('@microsoft/agentmesh-sdk');
      const fs = require('fs');
      const signer = AgentIdentity.fromJSON(
        JSON.parse(fs.readFileSync('signing-identity.json', 'utf-8'))
      );
      const artifact = fs.readFileSync('dist/artifact.tar.gz');
      const sig = fs.readFileSync('dist/artifact.tar.gz.sig');
      if (!signer.verify(new Uint8Array(artifact), new Uint8Array(sig))) {
        console.error('Signature verification failed');
        process.exit(1);
      }
      console.log('Signature valid:', signer.did);
    "

SBOM Attestation

GitHub's attestation system binds SBOMs to specific releases, providing a verifiable link between the SBOM and the artifact it describes.

How Attestation Works

  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚  Release v2.1.0                                     โ”‚
  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
  โ”‚  โ”‚  artifact.whl โ”‚  โ”‚  SBOM attestation          โ”‚   โ”‚
  โ”‚  โ”‚               โ”‚โ—„โ”€โ”‚  โ€ข subject: artifact.whl   โ”‚   โ”‚
  โ”‚  โ”‚               โ”‚  โ”‚  โ€ข predicate: sbom.spdx    โ”‚   โ”‚
  โ”‚  โ”‚               โ”‚  โ”‚  โ€ข signed by: GitHub OIDC  โ”‚   โ”‚
  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Creating Attestations

# Attest the SBOM
- uses: actions/attest-sbom@v2
  with:
    subject-path: dist/artifact.whl
    sbom-path: sbom.spdx.json

Verifying Attestations

# Verify attestation using GitHub CLI
gh attestation verify dist/artifact.whl \
  --owner microsoft \
  --format json

Cross-Reference

Concept Tutorial
Security hardening Tutorial 25 โ€” Security Hardening
Audit and compliance Tutorial 04 โ€” Audit & Compliance
Compliance verification Tutorial 18 โ€” Compliance Verification
Plugin marketplace signing Tutorial 10 โ€” Plugin Marketplace
Ed25519 identity Tutorial 02 โ€” Trust & Identity
Shift-left governance Tutorial 45 โ€” Shift-Left Governance

Source Files

Component Location
Security scanner agent-governance-python/agent-compliance/src/agent_compliance/security/scanner.py
SBOM workflow .github/workflows/sbom.yml
Publish workflow .github/workflows/publish.yml
Ed25519 identity (TS) agent-governance-typescript/src/identity.ts
Ed25519 identity (Rust) agent-governance-rust/agentmesh/src/identity.rs
Ed25519 identity (Go) agent-governance-golang/identity.go

Next Steps

  • Generate an SBOM for your agent deployment and review the component list
  • Set up Sigstore signing for Python package releases
  • Add SBOM attestation to your release workflow
  • Verify signatures as part of your deployment pipeline
  • Read Tutorial 25 (Security Hardening) for the full security tooling stack
  • Read Tutorial 27 (MCP Scan CLI) for scanning tool definitions as part of supply chain security