Skip to content

A hands-on workshop on Software Bill of Materials.

Notifications You must be signed in to change notification settings

idlab-discover/sbom-workshop-short

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

49 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SBOM Workshop - CRACY Event

Hands-on Software Bill of Materials Workshop

Learn how to generate, sign, and scan SBOMs for vulnerability detection and supply chain security.

🎯 What You'll Learn

  • Generate SBOMs from Go applications with Syft
  • Sign SBOMs cryptographically with Cosign
  • Automate SBOM workflows with GitHub Actions
  • Detect vulnerabilities with Grype
  • Visualize SBOMs with Sunshine

βš™οΈ Prerequisites

  1. WSL installed - See WSL-INSTALL.md
  2. Tools installed - See SETUP-TOOLS.md
    • This guide includes fork and clone instructions

Note

For the best hands-on experience open this repository in Visual Studio Code.

code .

Warning

Make sure the repository is forked to your own account and cloned from there.

πŸ“š Workshop

Part 1: Examine the Vulnerable Application

Navigate to the Go application:

cd go-app

Examine the dependencies in go.mod:

cat go.mod

Notice we're using deliberately vulnerable packages:

  • github.com/gin-gonic/gin v1.7.0 - Old version with CVEs
  • github.com/dgrijalva/jwt-go v3.2.0 - Deprecated, critical vulnerabilities
  • gopkg.in/yaml.v2 v2.2.8 - Known security issues

Part 2: Generate SBOM with Syft

1. Generate SBOM with source-only scan

First, let's generate an SBOM by scanning the source code only:

syft . -o cyclonedx-json=sbom-source.json

2. Inspect the source-only SBOM

View the dependencies discovered from the source:

jq '.components[] | {name: .name, version: .version}' sbom-source.json

Notice the count of components and which packages are included.

Note

To fully inspect the SBOM, I recommend opening the file in vscode and formatting the json output with shift + alt + f.

3. Build the Go application

Now let's build the application, which resolves all transitive dependencies:

go mod download
go build -o workshop-app ./cmd/app

Run the application:

./workshop-app

Test the endpoints in another terminal:

curl http://localhost:8080/
curl http://localhost:8080/token

Stop the server with Ctrl+C.

4. Generate SBOM with full scan (source+binary)

Perform a full scan of the source code and compiled binary to capture all resolved dependencies:

syft . -o cyclonedx-json=sbom-full.json

Note

You can also generate in the SPDX specification (instead of CycloneDX).

syft . -o spdx-json=sbom-full-spdx.json

5. Inspect the full scan (source + binary) SBOM

View the dependencies discovered by the full scan:

jq '.components[] | {name: .name, version: .version}' sbom-full.json

This gives you the most complete SBOM.

6. Generate SBOM with binary-only scan

Now generate an SBOM by scanning only the compiled binary (without scanning the source directory):

syft ./workshop-app -o cyclonedx-json=sbom-binary.json

This gives you the most accurate picture of what's actually in your deployed artifact.

7. Inspect the binary-only scan SBOM

View the dependencies discovered by the binary-only scan:

jq '.components[] | {name: .name, version: .version}' sbom-binary.json

8. Compare all three SBOMs

Now compare the component counts across all three SBOM files:

echo "Source-only scan SBOM components:"
jq '.components | length' sbom-source.json

echo "Full scan (source+binary) SBOM components:"
jq '.components | length' sbom-full.json

echo "Binary-only scan SBOM components:"
jq '.components | length' sbom-binary.json

Compare the SBOMs to understand the differences:

First, let's see what's in each SBOM by type of component:

echo "=== SOURCE-ONLY SCAN ==="
echo "Go modules:"
jq -r '.components[] | select(.type == "library") | .name' sbom-source.json | sort -u
echo ""
echo "Other components:"
jq -r '.components[] | select(.type != "library") | "\(.type): \(.name)"' sbom-source.json | sort -u
echo "=== FULL SCAN (SOURCE+BINARY) ==="
echo "Go modules:"
jq -r '.components[] | select(.type == "library") | .name' sbom-full.json | sort -u
echo ""
echo "Other components:"
jq -r '.components[] | select(.type != "library") | "\(.type): \(.name)"' sbom-full.json | sort -u
echo "=== BINARY-ONLY SCAN ==="
echo "Go modules:"
jq -r '.components[] | select(.type == "library") | .name' sbom-binary.json | sort -u
echo ""
echo "Other components:"
jq -r '.components[] | select(.type != "library") | "\(.type): \(.name)"' sbom-binary.json | sort -u

Now find the key differences:

Dependencies discovered only when scanning the built binary:

echo "Dependencies found in binary but missing from source-only scan:"
comm -23 <(jq -r '.components[] | select(.type == "library") | .name' sbom-binary.json | sort -u) <(jq -r '.components[] | select(.type == "library") | .name' sbom-source.json | sort -u)

This typically returns stdlib β€” the Go standard library is linked into the executable and is only visible when scanning the binary.

Test dependencies and unused packages (in source but not in binary):

echo "Packages in source but not actually used in binary (test deps & unused transitive deps):"
comm -23 <(jq -r '.components[] | select(.type == "library") | .name' sbom-source.json | sort -u) <(jq -r '.components[] | select(.type == "library") | .name' sbom-binary.json | sort -u)

The list above shows packages declared in source but excluded from the compiled binary. Typical reasons:

  • Test-only dependencies used by libraries (e.g. github.com/stretchr/testify, github.com/davecgh/go-spew, github.com/pmezard/go-difflib, gopkg.in/check.v1, github.com/go-playground/assert/v2)
  • Unused transitive or alternative implementations (e.g. github.com/json-iterator/go, github.com/modern-go/concurrent, github.com/modern-go/reflect2)

What do these results show?

The comparison reveals three different SBOM approaches:

  1. Source-only scan (sbom-source.json): 23 components

    • Reads go.mod and go.sum directly
    • Lists ALL declared dependencies (including transitive dependencies)
    • Enables a complete dependency graph
    • Best for: License compliance, understanding the full dependency tree
  2. Full scan (source+binary) (sbom-full.json): 39 components (23+16)

    • Scans source tree + built binary + go.mod files
    • Most complete SBOM with duplicate entries because of the combination of both source and binary
    • Includes everything: all declared deps + binary analysis + file artifacts
    • Best for: Complete supply chain visibility during development
  3. Binary-only scan (sbom-binary.json): 16 components

    • Analyzes ONLY what was compiled into the final executable
    • Most accurate for production: reflects the actual runtime supply chain
    • Excludes unused dependencies and test-related packages of other libraries
    • Best for: Production vulnerability scanning and deployment SBOMs

Why the binary has fewer dependencies:

Go's compiler excludes packages not used in your application:

  • Test frameworks used by your dependencies (like testify used by gin's tests)
  • Alternative implementations your app doesn't need (like json-iterator when standard JSON suffices)

When you import "github.com/gin-gonic/gin", you only use gin's runtime code, not its testing tools or unused optimization libraries.

Part 3: Sign SBOM with Cosign

Local Signing

Generate a key pair:

cosign generate-key-pair

Enter a password when prompted (e.g., "workshop"). This creates:

  • cosign.key (private key)
  • cosign.pub (public key)

Sign the SBOM (we will use the binary-only one for the rest of the workshop):

cosign sign-blob --key cosign.key --bundle sbom.bundle.json sbom-binary.json

Enter your password.

The resulting bundle is a self-contained, verifiable proof package that carries the signature plus the transparency-log and timestamp evidence needed to prove when and where the blob (in this case SBOM) was signed. Anyone can verify integrity, provenance and signing time.

Verify the signature:

cosign verify-blob --key cosign.pub --bundle sbom.bundle.json sbom-binary.json

You should see: Verified OK

What this proves:

  • The SBOM hasn't been tampered with
  • It was signed by the holder of the private key
  • Integrity is guaranteed

Important

Best Practice 1: Always sign the SBOM for integrity and authenticity.

Part 4: Scan for Vulnerabilities with Grype

Now the exciting part: let's find those vulnerabilities!

1. Basic scanning

Scan the SBOM:

grype sbom:./sbom-binary.json

Important

Best Practice 2: Prefer the binary-only SBOM for production vulnerability scans as these dependencies represent the true attack surface.

Expected output: You'll see a table of vulnerabilities like:

NAME                    INSTALLED  FIXED-IN  TYPE   VULNERABILITY   SEVERITY
github.com/gin-gonic/gin v1.7.0    v1.7.7    go-module  CVE-2021-XXXXX  High
github.com/dgrijalva/jwt-go  v3.2.0  (no fix)  go-module  CVE-2020-26160  Critical
...

2. Create an enriched SBOM with vulnerabilities embedded

grype sbom:./sbom-binary.json -o cyclonedx-json=sbom-binary-vulnerabilities.json

This creates a new SBOM file that includes all the vulnerability data embedded in the CycloneDX format.

Analysis:

  • Critical vulnerabilities in crypto library
  • Multiple high-severity issues in JWT library
  • Some have fixes available
  • jwt-go is abandoned (no fix possible - must migrate)

3. Export results (Optional)

grype sbom:./sbom-binary.json -o json > vulnerabilities.json
grype sbom:./sbom-binary.json -o table > vulnerabilities.txt

Part 5: Automate with GitHub Actions

Let's automate everything in CI/CD!

GitHub Actions workflows are YAML files stored under workflows that define automated CI/CD pipelinesβ€”jobs made of ordered steps and runners that trigger on events (push, pull request, schedule, or manual runs); they let you reliably run build, test, SBOM generation and signing, vulnerability scanning, and artifact archival in a reproducible, auditable way.

The workflow file .github/workflows/sbom-pipeline.yml is already created. Let's examine it:

cd ..
cat .github/workflows/sbom-pipeline.yml

Key steps in the workflow:

  1. Checkout code
- name: Checkout code
   uses: actions/checkout@v4

Purpose: Fetches the repository so subsequent steps can build and scan the code.

  1. Set up Go
- name: Set up Go
   uses: actions/setup-go@v5
   with:
      go-version: '1.25'
      cache-dependency-path: go-app/go.sum

Purpose: Installs the requested Go toolchain and enables dependency caching for faster CI runs.

  1. Build application
- name: Build application
   working-directory: go-app
   run: go build -o workshop-app ./cmd/app

Purpose: Compiles the Go binary that will be inspected and shipped.

  1. Generate SBOM with Syft
- name: Install Syft
   uses: anchore/sbom-action/[email protected]

- name: Generate SBOM (CycloneDX)
   working-directory: go-app
   run: syft ./workshop-app -o cyclonedx-json=sbom-binary.json

Purpose: Produces a CycloneDX SBOM for the compiled artifact (binary-only SBOM recommended for production).

  1. Sign SBOM with Cosign (keyless)
- name: Install Cosign
   uses: sigstore/[email protected]

- name: Sign SBOM (Keyless)
   working-directory: go-app
   run: |
      cosign sign-blob --yes \
         --oidc-issuer=https://token.actions.githubusercontent.com \
         --bundle=sbom.bundle.json \
         sbom-binary.json

Purpose: Creates a verifiable signature bundle for the SBOM using GitHub Actions OIDC (no pre-shared private key required).

  1. Scan vulnerabilities with Grype
- name: Install Grype
   uses: anchore/scan-action/download-grype@v4

- name: Scan for vulnerabilities
   working-directory: go-app
   run: |
      grype sbom:./sbom-binary.json -o table

Purpose: Detects known vulnerabilities in the SBOM; CI can fail or alert based on severity thresholds.

Create enriched CycloneDX SBOM with embedded vulnerabilities

- name: Create enriched SBOM (CycloneDX with vulnerabilities)
   working-directory: go-app
   run: |
      grype sbom:./sbom-binary.json -o cyclonedx-json=sbom-binary-vulnerabilities.json

Purpose: Produces a CycloneDX SBOM that embeds Grype's vulnerability findings so the SBOM and visualizations include vulnerability metadata.

  1. Upload artifacts
- name: Upload artifacts
   uses: actions/upload-artifact@v4
   with:
      name: workflow-sbom-artifacts
      path: |
         go-app/workshop-app
         go-app/sbom*.json
         go-app/sbom.bundle.json
         go-app/vulnerabilities.json

Purpose: Archives the built binary, SBOMs, signature bundle, and scan results for later inspection or release packaging.

Important

Best Practice 3: Automate SBOM generation, signing, scanning, and artifact archival in CI. Archive SBOMs with build artifacts and verify signatures (certificate identity) before promoting releases.

Trigger the workflow:

git add .
git commit --allow-empty -m "Run SBOM pipeline"
git push

Note

You might need to enable GitHub Actions first on your repository website -> Actions.

Monitor execution:

  1. Go to your GitHub repository
  2. Click Actions tab
  3. Watch the workflow run

Download artifacts (manual):

Steps (manual):

  • In GitHub go to your repository β†’ Actions β†’ select the completed sbom-pipeline.yml run.
  • Click Artifacts β†’ download workflow-sbom-artifacts.zip to your machine (e.g., ~/Downloads).
  • Extract the ZIP locally (file manager or unzip ~/Downloads/workflow-sbom-artifacts.zip -d ~/Downloads/workflow-sbom-artifacts).
  • Move or drag the extracted workflow-sbom-artifacts folder into this repository's root so it lives at ./workflow-sbom-artifacts/.

Once the artifact folder is present in the repository, verify the keyless signature locally using cosign against the bundle and SBOM inside that folder.

Warning

Replace YOUR-USERNAME with the username of the forked repository.

cosign verify-blob \
  --bundle ./workflow-sbom-artifacts/sbom.bundle.json \
  --certificate-identity "https://github.com/YOUR-USERNAME/sbom-workshop-short/.github/workflows/sbom-pipeline.yml@refs/heads/main" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ./workflow-sbom-artifacts/sbom-binary.json

You should see: Verified OK

If the --certificate-identity check fails, inspect the certificate embedded in the bundle to find the exact identity string and metadata recorded by the workflow:

jq -r '.cert' ./workflow-sbom-artifacts/sbom.bundle.json | base64 -d | openssl x509 -text -noout

Look for extensions showing:

  • Repository URL
  • Workflow path
  • Commit SHA
  • Who triggered it

Part 6: Visualize with Sunshine

Sunshine provides a beautiful web-based visualization of your SBOM with vulnerabilities.

Open Sunshine:

  1. Go to: https://cyclonedx.github.io/Sunshine/
  2. Click "Upload" or drag-and-drop an SBOM file
  3. Select your enriched SBOM file with vulnerabilities:
    • Navigate to your sbom-workshop-short/go-app/ folder
    • Choose sbom-binary-vulnerabilities.json (the file created by Grype with embedded vulnerabilities)
  4. Watch Sunshine render your SBOM with vulnerabilities highlighted!
  5. Select your enriched SBOM file with vulnerabilities based on the source or source + binary:
    • Navigate to your sbom-workshop-short/go-app/ folder
    • Choose sbom-full-vulnerabilities.json (the file created by Grype with embedded vulnerabilities)
    • Now you have an overview of the full dependency graph with their vulnerabilities highlighted!
    • Notice it contains both binary and source dependencies so it includes duplicates.
  6. Try an enriched source-only SBOM aswell.

Important

Best Practice 4: Use enriched SBOMs for visualization. Visualization is important so you don't lose oversight of your dependencies.

πŸŽ“ What You've Accomplished

  • Built a Go application with dependencies
  • Generate SBOMs from Go applications with Syft in multiple formats (CycloneDX, SPDX) in different ways (source, binary)
  • Sign SBOMs cryptographically with Cosign (local + keyless)
  • Automate SBOM workflows with GitHub Actions
  • Detect vulnerabilities with Grype
  • Visualize SBOMs with Sunshine

πŸš€ Best Practices

Important

Best Practice 1: Always sign the SBOM for integrity and authenticity.

Important

Best Practice 2: Prefer the binary-only SBOM for production vulnerability scans as these dependencies represent the true attack surface.

Important

Best Practice 3: Automate SBOM generation, signing, scanning, and artifact archival in CI. Archive SBOMs with build artifacts and verify signatures (certificate identity) before promoting releases.

Important

Best Practice 4: Use enriched SBOMs for visualization. Visualization is important so you don't lose oversight of your dependencies.

πŸ”§ Let's fix the vulnerabilities (BONUS)

Below are practical tips to help you identify and remediate vulnerable dependencies. Use these as a checklist while you investigate fixed versions yourself.

  • Inspect all generated SBOMs (source-only, full, and binary-only) to locate packages and exact versions flagged by scanners.
  • Prioritize fixes by severity and exposure: address critical and runtime-exposed (binary) findings first.
  • For each vulnerable module, check the project's release notes, changelog, and tags on GitHub to find versions that contain fixes.
  • If a package is abandoned (no fix), look for maintained forks, community-recommended replacements, or official migration guides.
  • When migrating libraries (e.g., a JWT library), search the repo for import paths and API differences; plan small, incremental code changes and compile frequently to surface errors.
  • Use go list -m all (or equivalent for other ecosystems) to get a full inventory of resolved module versions when investigating transitive dependencies.
  • After updates, rebuild the binary and regenerate the binary-only SBOM. Then re-scan to confirm the vulnerability is resolved in the runtime artifact.
  • Run your test suite and smoke tests after changes; consider feature flags, canary deployments, or staged rollouts for risky updates.
  • Subscribe to vulnerability feeds, set up CI gates, and schedule regular dependency reviews so fixes are discovered proactively.
  • Archive SBOMs and scan reports alongside release artifacts so you can audit what was shipped and when fixes were applied.

Tip: treat this as an iterative process: update one dependency at a time, verify the build and behavior, then proceed to the next.

Try it yourself! Update the dependencies and see Grype report clean.

πŸ“– Resources

Tools:

Standards:

Compliance:

πŸ“ž Contact

Workshop by: Wiebe Vandendriessche
IDLab - Ghent University & imec
[email protected]


πŸŽ‰ Congratulations! You've completed the SBOM workshop. Now apply these practices to your own projects!

About

A hands-on workshop on Software Bill of Materials.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •