Definition
- SBOM (Software Bill of Materials) – a formal, machine-readable inventory of every component in a piece of software, including all direct and transitive dependencies.
- SPDX – an ISO-standardized format originating from the Linux Foundation, covering both license compliance and security metadata.
- CycloneDX – an OWASP format designed from the start around security and supply-chain use cases, lightweight and popular in security tooling.
- Operational value – when a new vulnerability is disclosed, you query your SBOMs to answer instantly which services contain the affected component and version.
- Generation – produced automatically in the build pipeline on every build, stored in a central index alongside the artifact.
An SBOM is like the complete ingredients-and-suppliers list for every product the bank's kitchen serves, down to which farm supplied each egg. If a supplier announces a contaminated batch, a kitchen without the list inspects every dish by hand for days; a kitchen with the list runs one query and knows exactly which dishes used that batch.
Understanding the Concepts
What is an SBOM?
A Software Bill of Materials is a formal, machine-readable list of every component in a piece of software. It captures the full resolved dependency graph with exact versions, cryptographic hashes, supplier information, and license identifiers.
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"metadata": {
"component": {
"type": "application",
"name": "bank-transfer-service",
"version": "2.4.1"
}
},
"components": [
{
"type": "library",
"name": "spring-boot-starter-web",
"version": "3.2.0",
"purl": "pkg:maven/org.springframework.boot/spring-boot-starter-web@3.2.0"
},
{
"type": "library",
"name": "jackson-databind",
"version": "2.16.0",
"purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.16.0"
}
]
}
What are SPDX and CycloneDX?
The two dominant standard formats for SBOMs serve different origins but both produce machine-readable output.
| Aspect | SPDX | CycloneDX |
|--------|------|-----------|
| Origin | Linux Foundation | OWASP |
| Standard | ISO/IEC 5962:2021 | OWASP standard |
| Primary focus | License compliance + security | Security + supply chain |
| Formats | JSON, XML, RDF, tag-value | JSON, XML, Protobuf |
| Strengths | Mature, ISO-backed, broad ecosystem | Lightweight, security-first, popular in DevSecOps |
# Generate CycloneDX SBOM with Maven plugin
mvn org.cyclonedx:cyclonedx-maven-plugin:makeBom
# Output: target/bom.json (CycloneDX format)
# Generate SPDX SBOM with Syft
syft bank/transfer-service:latest -o spdx-json > sbom-spdx.json
Why is an SBOM an incident-response capability, not a compliance checkbox?
Many organizations first produce SBOMs because a customer or regulator demands one. They treat it as a document to file and forget. The real value appears on the day a serious vulnerability is disclosed.
The Log4Shell scenario:
Day 0: CVE-2021-44228 is disclosed against log4j-core 2.x
Without SBOMs:
- Send panicked emails: "Does anyone use Log4j?"
- Grep source trees across 200 repositories
- Miss transitive dependencies nobody remembers
- Days of incomplete searching while exposure window stays open
With queryable SBOMs:
- Run ONE query against the SBOM index
- Get exact list: 12 services, specific versions, including transitive uses
- Begin patching within minutes, highest-risk services first
# Querying SBOMs for a vulnerable component
# Assuming SBOMs are stored in a central index (e.g., DependencyTrack)
curl -s "https://sbom-index.bank.in/api/v1/search?purl=pkg:maven/org.apache.logging.log4j/log4j-core" \
| jq '.[] | {service: .project.name, version: .component.version}'
# Output:
{"service": "bank-transfer-service", "version": "2.14.1"}
{"service": "bank-loan-service", "version": "2.14.1"}
{"service": "bank-statement-generator", "version": "2.16.0"}
# Instant, complete answer in seconds
How do you generate SBOMs in the pipeline?
SBOMs are generated automatically on every build so they are always accurate and versioned alongside the artifact.
<!-- pom.xml -- CycloneDX Maven plugin --syn-kw">>
syn-kw"><pluginsyn-kw">>
syn-kw"><groupIdsyn-kw">>org.cyclonedxsyn-kw"></groupIdsyn-kw">>
syn-kw"><artifactIdsyn-kw">>cyclonedx-maven-pluginsyn-kw"></artifactIdsyn-kw">>
syn-kw"><versionsyn-kw">>2.7.11syn-kw"></versionsyn-kw">>
syn-kw"><executionssyn-kw">>
syn-kw"><executionsyn-kw">>
syn-kw"><phasesyn-kw">>packagesyn-kw"></phasesyn-kw">>
syn-kw"><goalssyn-kw">>
syn-kw"><goalsyn-kw">>makeBomsyn-kw"></goalsyn-kw">>
syn-kw"></goalssyn-kw">>
syn-kw"></executionsyn-kw">>
syn-kw"></executionssyn-kw">>
syn-kw"><configurationsyn-kw">>
syn-kw"><projectTypesyn-kw">>applicationsyn-kw"></projectTypesyn-kw">>
syn-kw"><outputFormatsyn-kw">>jsonsyn-kw"></outputFormatsyn-kw">>
syn-kw"><includeCompileScopesyn-kw">>truesyn-kw"></includeCompileScopesyn-kw">>
syn-kw"><includeRuntimeScopesyn-kw">>truesyn-kw"></includeRuntimeScopesyn-kw">>
syn-kw"></configurationsyn-kw">>
syn-kw"></pluginsyn-kw">>
# GitHub Actions -- generate and store SBOM on every build
- name: Generate SBOM
run: mvn cyclonedx:makeBom
- name: Upload SBOM to index
run: |
curl -X POST https://sbom-index.bank.in/api/v1/bom \
-H "X-API-Key: ${{ secrets.SBOM_API_KEY }}" \
-F "project=bank-transfer-service" \
-F "bom=@target/bom.json"
How do SBOMs pair with continuous vulnerability feeds?
An SBOM alone is a static inventory. Its power is realized when you match components against newly disclosed vulnerabilities automatically.
Continuous SBOM monitoring workflow:
┌──────────┐ ┌──────────────┐ ┌─────────────────┐
│ Build │────▶│ SBOM Index │◀────│ Vulnerability │
│ Pipeline │ │ (all SBOMs) │ │ Feed (NVD, OSV) │
└──────────┘ └──────┬───────┘ └─────────────────┘
│
▼
┌──────────────┐
│ Alert: │
│ log4j 2.14 │
│ in 3 svcs │
└──────────────┘
package com.bank.security;
import java.util.List;
import java.util.Map;
public class SbomQueryService {
// Query SBOM index for affected services
public List<Map<String, String>> findAffectedServices(String componentName, String version) {
// In a real implementation, queries DependencyTrack or Grype API
System.out.println("Searching SBOMs for " + componentName + " version " + version);
// Returns: [{service: "transfer-service", version: "2.14.1"}, ...]
return List.of(
Map.of("service", "bank-transfer-service", "version", version),
Map.of("service", "bank-loan-service", "version", version)
);
}
}
How do SBOMs strengthen supply-chain defense?
Pairing SBOMs with artifact signing and provenance attestations lets you prove an artifact's integrity and origin.
# Sign the SBOM with cosign for provenance
cosign attest --predicate target/bom.json \
--type cyclonedx \
bank/transfer-service:abc123
# Verify at deployment time
cosign verify-attestation --type cyclonedx \
bank/transfer-service:abc123
What are common mistakes with SBOMs?
# WRONG: generating an SBOM as a PDF and filing it away
# A PDF is not machine-readable, cannot be queried, and is stale immediately
# CORRECT: generate a JSON/XML SBOM on every build, index it, query it
mvn cyclonedx:makeBom # produces target/bom.json
# Upload to DependencyTrack for continuous monitoring
# WRONG: generating an SBOM only for top-level dependencies
# Vulnerabilities frequently hide several levels deep in transitive dependencies
# CORRECT: generate a full resolved dependency tree including all transitives
# Both CycloneDX Maven plugin and Syft capture the full graph
What should a senior engineer know about SBOMs?
The senior nuance is that an SBOM converts a regulatory requirement into genuine operational capability. For a bank, it reduces mean-time-to-respond during a vulnerability incident from days of manual searching to a single database query. The SBOM becomes a build output as routine as the JAR or the container image, generated on every build, indexed by service and version, and matched against vulnerability feeds continuously.
Visual Overview
SBOM in the Build Pipeline
============================
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐
│ Code │────▶│ Build │────▶│ JAR │────▶│ Deploy │
└─────────┘ └────┬────┘ └─────────┘ └──────────┘
│
▼
┌───────────┐
│ SBOM │ (generated alongside the artifact)
│ bom.json │
└─────┬─────┘
│
▼
┌───────────────┐
│ SBOM Index │ (central, queryable)
│ Keyed by: │
│ - service │
│ - version │
│ - build date │
└───────┬───────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Query │ │ Vuln │ │ License │
│ affected │ │ Feed │ │ Audit │
│ services │ │ Match │ │ │
└──────────┘ └──────────┘ └──────────┘
Incident Response: With vs Without SBOMs
==========================================
Without: CVE drops ──▶ Panic emails ──▶ Grep repos ──▶ DAYS to find
With: CVE drops ──▶ Query index ──▶ Exact list ──▶ MINUTES to patch
Key Takeaway
A Software Bill of Materials is a machine-readable inventory of every component in your software, including all direct and transitive dependencies with exact versions, hashes, and licenses. The two standard formats are SPDX (ISO-standardized, from the Linux Foundation) and CycloneDX (OWASP, security-first). The critical property is that it is machine-readable so tools can query it.
The reframing from compliance checkbox to incident-response capability is the heart of the matter. When a serious vulnerability is disclosed, the only question that matters is which services contain the affected component. With queryable SBOMs generated on every build and stored in a central index, you run one query and get an exact answer in minutes, including transitive uses nobody would find by memory.
At National Bank of India the Log4Shell scramble took days because we could not quickly say which services carried the vulnerable Log4j. We moved to generating a CycloneDX SBOM on every build and indexing it by service and version. The next time a library vulnerability dropped, we answered the affected-services question in a single query.
Remember one sentence: an SBOM is an incident-response tool that turns days of frantic searching into a single database query when a vulnerability drops.
---