🏗️ Tutorial: Build an SBOM From Your Code
Generate a CycloneDX SBOM straight from a go.mod manifest, attach CPE/PURL identifiers to each component, enrich with vulnerabilities, and export. Drop the result into your release artifacts or feed it to a downstream scanner.
Goal
Take the contents of a go.mod file and produce a CycloneDX JSON document whose components carry PURLs, CPEs, and (optionally) vulnerability findings.
Prerequisites
- Go 1.25+
go get github.com/scagogogo/cpe-skills- A
go.modyou can read into a string
Steps
1. Build the SBOM from a manifest
BuildSBOMFromManifest inspects the filename extension to pick a parser (Go mod, package.json, requirements.txt, etc.), parses it, and wraps every dependency as an SBOMComponent.
package main
import (
"fmt"
"os"
cpeskills "github.com/scagogogo/cpe-skills"
)
func main() {
content, err := os.ReadFile("go.mod")
if err != nil {
panic(err)
}
sbom, err := cpeskills.BuildSBOMFromManifest("go.mod", string(content), "my-app")
if err != nil {
panic(err)
}
fmt.Printf("SBOM has %d components\n", sbom.ComponentCount())2. Attach a CPE and PURL to a component
Fetch a component by reference and stamp an explicit CPE + PURL onto it. This is how you bridge ecosystems that the heuristic mapper cannot infer confidently.
if comp := sbom.GetComponent("pkg:golang/github.com/apache/log4j@v2.14.0"); comp != nil {
comp.SetCPE(cpeskills.MustParse("cpe:2.3:a:apache:log4j:2.14.0:*:*:*:*:*:*:*"))
purl, _, _ := cpeskills.CPEToPURL(comp.CPE)
if purl != nil {
comp.SetPURL(purl)
}
}3. Enrich with vulnerabilities
If you already downloaded NVD data (see the Identify Vulnerabilities tutorial), attach findings to every component in one call.
nvdData, _ := cpeskills.DownloadAllNVDData(nil)
if err := sbom.EnrichWithVulnerabilities(nvdData); err != nil {
fmt.Printf("enrich skipped: %v\n", err)
}4. Export to CycloneDX
out, err := cpeskills.ExportSBOMToCycloneDX(sbom)
if err != nil {
panic(err)
}
if err := os.WriteFile("sbom.cdx.json", out, 0o644); err != nil {
panic(err)
}
fmt.Println("wrote sbom.cdx.json")
}Pipeline at a glance
Build sequence
The flowchart hides the per-component loop. The sequence diagram makes the iteration explicit:
Expected output
SBOM has 23 components
wrote sbom.cdx.jsonThe generated sbom.cdx.json is a valid CycloneDX BOM you can validate with cyclonedx validate.
Notes
BuildSBOMFromManifestdelegates to thepkg/parserssubpackage; the parser is chosen by filename, so pass the real filename (e.g."package.json", not a placeholder).EnrichWithVulnerabilitiesonly fills findings for components that carry a CPE — components without a CPE are silently skipped.- Prefer explicit
SetCPEover the heuristic mapper for products you know; the mapper's confidence score (the second return ofCPEToPURL) is the signal to trust it or not.
Recap
You turned a manifest into an SBOM, annotated components with CPE/PURL, enriched with NVD findings, and exported CycloneDX. The same SBOM object also serializes to SPDX via ExportSBOMToSPDX.