# SBOM Generation Flow ## Overview The SBOM Generation Flow describes how StellaOps creates Software Bills of Materials for container images using its 11 language-specific analyzers. The flow covers layer extraction, dependency detection, transitive resolution, and final SBOM assembly in SPDX 3.0.1 or CycloneDX 1.6 format. **Business Value**: Complete, accurate SBOMs enable precise vulnerability matching and regulatory compliance (Executive Order 14028, EU CRA). ## Actors | Actor | Type | Role | |-------|------|------| | Scanner | Service | Orchestrates SBOM generation | | Layer Extractor | Component | Unpacks OCI layers | | OS Detector | Component | Identifies base operating system | | Language Analyzers (11) | Components | Detect ecosystem-specific dependencies | | SBOM Assembler | Component | Merges results into final SBOM | | Attestor | Service | Signs SBOM with DSSE envelope | ## Prerequisites - Container image accessible (registry or local) - Registry credentials if private registry - Analyzer plugins enabled for target ecosystems ## Language Analyzers StellaOps includes 11 specialized analyzers: | Analyzer | Ecosystems | Lock Files | Manifests | |----------|------------|------------|-----------| | **DotNet** | NuGet, .NET | `packages.lock.json`, `*.deps.json` | `*.csproj`, `*.fsproj`, `Directory.Packages.props` | | **Java** | Maven, Gradle | `pom.xml`, `build.gradle.kts` | `gradle.lockfile`, `maven-dependency-tree.txt` | | **Node** | npm, yarn, pnpm | `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` | `package.json` | | **Python** | pip, Poetry, Pipenv | `requirements.txt`, `poetry.lock`, `Pipfile.lock` | `pyproject.toml`, `setup.py` | | **Go** | Go Modules | `go.sum` | `go.mod` | | **Rust** | Cargo | `Cargo.lock` | `Cargo.toml` | | **PHP** | Composer | `composer.lock` | `composer.json` | | **Ruby** | Bundler | `Gemfile.lock` | `Gemfile` | | **Deno** | Deno, JSR | `deno.lock` | `deno.json`, `import_map.json` | | **Bun** | Bun | `bun.lockb` | `package.json` | | **Binary** | Native ELF/PE/Mach-O | N/A | Symbol tables, build IDs | ## Flow Diagram ``` ┌─────────────────────────────────────────────────────────────────────────────────┐ │ SBOM Generation Flow │ └─────────────────────────────────────────────────────────────────────────────────┘ ┌─────────┐ ┌───────────┐ ┌────────────┐ ┌───────────────────────────────┐ │ Scanner │ │ Layer │ │ OS │ │ Language Analyzers │ │ │ │ Extractor │ │ Detector │ │ (DotNet, Java, Node, etc.) │ └────┬────┘ └─────┬─────┘ └──────┬─────┘ └───────────────┬───────────────┘ │ │ │ │ │ Fetch image │ │ │ │ manifest │ │ │ │──────┐ │ │ │ │ │ │ │ │ │<─────┘ │ │ │ │ │ │ │ │ Extract │ │ │ │ layers │ │ │ │─────────────>│ │ │ │ │ │ │ │ │ Unpack to │ │ │ │ work dir │ │ │ │───────┐ │ │ │ │ │ │ │ │ │<──────┘ │ │ │ │ │ │ │ │ Layers ready │ │ │<─────────────│ │ │ │ │ │ │ │ Detect OS │ │ │ │──────────────────────────────>│ │ │ │ │ │ │ │ │ Parse /etc/os-release │ │ │ │ Check package manager │ │ │ │──────────┐ │ │ │ │ │ │ │ │ │<─────────┘ │ │ │ │ │ │ OS: Alpine 3.19 │ │ │<──────────────────────────────│ │ │ │ │ │ │ Fan-out to │ │ │ │ all analyzers│ │ │ │────────────────────────────────────────────────────────>│ │ │ │ │ │ │ │ │ ┌─────────┐ │ │ │ │ │ DotNet │ │ │ │ │ └────┬────┘ │ │ │ │ │ │ │ │ Scan *.csproj │ │ │ Parse deps.json │ │ │ │<─────┘ │ │ │ │ │ │ │ │ ┌─────────┐ │ │ │ │ │ Java │ │ │ │ │ └────┬────┘ │ │ │ │ │ │ │ │ Parse pom.xml │ │ │ Run gradle deps │ │ │ │<─────┘ │ │ │ │ │ │ │ │ ┌─────────┐ │ │ │ │ │ Node │ │ │ │ │ └────┬────┘ │ │ │ │ │ │ │ │ Parse lockfiles │ │ │ Build dep tree │ │ │ │<─────┘ │ │ │ │ │ │ │ ... (8 more analyzers) │ │ │ │ │ Analyzer │ │ │ │ results │ │ │ │<────────────────────────────────────────────────────────│ │ │ │ │ │ ┌────────────────┐ │ │ │ │ SBOM Assembler │ │ │ │ └───────┬────────┘ │ │ │ │ │ │ │ Merge & │ │ │ │ dedupe │ │ │ │──────────> │ │ │ │ │ │ │ SBOM │ │ │ │ document │ │ │ │<─────────│ │ │ │ │ │ │ │ ┌─────────┐ │ │ │ │Attestor │ │ │ │ └────┬────┘ │ │ │ │ │ │ │ Sign │ │ │ │───────> │ │ │ │ │ │ │ DSSE │ │ │ │<──────│ │ │ │ │ │ │ ``` ## Step-by-Step ### 1. Image Manifest Fetch Scanner retrieves OCI image manifest: ```http GET /v2/library/nginx/manifests/1.25 HTTP/1.1 Host: registry-1.docker.io Accept: application/vnd.oci.image.manifest.v1+json ``` Response contains layer digests: ```json { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "config": { "digest": "sha256:config123..." }, "layers": [ {"digest": "sha256:layer1...", "size": 31456789}, {"digest": "sha256:layer2...", "size": 1234567} ] } ``` ### 2. Layer Extraction Layer Extractor downloads and unpacks each layer: ``` work/ ├── layer-0/ # Base OS layer (Alpine, Debian, etc.) │ ├── etc/ │ ├── lib/ │ └── usr/ ├── layer-1/ # Application layer │ ├── app/ │ │ ├── node_modules/ │ │ ├── package.json │ │ └── package-lock.json │ └── ... └── merged/ # Union filesystem view ``` ### 3. OS Detection OS Detector identifies the base operating system: | Detection Method | Files Checked | |-----------------|---------------| | `/etc/os-release` | `ID`, `VERSION_ID` | | `/etc/alpine-release` | Alpine version | | `/etc/debian_version` | Debian version | | Package manager | `apk`, `dpkg`, `rpm` | Result: ```json { "os": "alpine", "version": "3.19", "package_manager": "apk", "architecture": "amd64" } ``` ### 4. Parallel Analyzer Execution All 11 analyzers run in parallel on the merged filesystem: #### DotNet Analyzer ``` Scanning for: - *.csproj, *.fsproj, *.vbproj - *.deps.json (runtime dependencies) - packages.lock.json (NuGet lock) - Directory.Packages.props (central management) Output: { "packages": [ {"purl": "pkg:nuget/Newtonsoft.Json@13.0.3", "scope": "runtime"}, {"purl": "pkg:nuget/Microsoft.Extensions.Logging@8.0.0", "scope": "runtime"} ] } ``` #### Java Analyzer ``` Scanning for: - pom.xml (Maven) - build.gradle, build.gradle.kts (Gradle) - *.jar in lib/ directories - MANIFEST.MF inside JARs Output: { "packages": [ {"purl": "pkg:maven/com.google.guava/guava@32.1.2-jre", "scope": "compile"}, {"purl": "pkg:maven/org.slf4j/slf4j-api@2.0.9", "scope": "runtime"} ] } ``` #### Node Analyzer ``` Scanning for: - package.json + package-lock.json (npm) - yarn.lock (Yarn) - pnpm-lock.yaml (pnpm) - node_modules/.package-lock.json Output: { "packages": [ {"purl": "pkg:npm/express@4.18.2", "scope": "runtime"}, {"purl": "pkg:npm/lodash@4.17.21", "scope": "runtime"} ] } ``` ### 5. Transitive Resolution Each analyzer resolves transitive dependencies: ``` express@4.18.2 ├── accepts@1.3.8 │ ├── mime-types@2.1.35 │ │ └── mime-db@1.52.0 │ └── negotiator@0.6.3 ├── body-parser@1.20.1 │ ├── bytes@3.1.2 │ └── ... └── ... ``` ### 6. SBOM Assembly SBOM Assembler merges all analyzer results: 1. **Deduplication**: Remove duplicate PURLs across analyzers 2. **Relationship mapping**: Build component dependency graph 3. **Metadata enrichment**: Add licenses, hashes, supplier info 4. **Format conversion**: Output as SPDX 3.0.1 or CycloneDX 1.6 ```json { "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.6", "version": 1, "metadata": { "timestamp": "2024-12-29T10:30:00Z", "tools": [{"name": "stellaops-scanner", "version": "2.1.0"}], "component": { "type": "container", "name": "docker.io/library/nginx", "version": "1.25" } }, "components": [ { "type": "library", "bom-ref": "pkg:npm/express@4.18.2", "name": "express", "version": "4.18.2", "purl": "pkg:npm/express@4.18.2", "hashes": [{"alg": "SHA-256", "content": "abc123..."}] } ], "dependencies": [ { "ref": "pkg:npm/express@4.18.2", "dependsOn": ["pkg:npm/accepts@1.3.8", "pkg:npm/body-parser@1.20.1"] } ] } ``` ### 7. SBOM Attestation Attestor creates DSSE envelope for the SBOM: ```json { "_type": "https://in-toto.io/Statement/v1", "subject": [ { "name": "docker.io/library/nginx", "digest": {"sha256": "abc123..."} } ], "predicateType": "https://spdx.dev/Document", "predicate": { "sbom": "base64-encoded-sbom...", "generator": "stellaops-scanner@2.1.0", "timestamp": "2024-12-29T10:30:00Z" } } ``` Signed with DSSE: ```json { "payloadType": "application/vnd.in-toto+json", "payload": "base64-encoded-statement...", "signatures": [ { "keyid": "sha256:signer-key-fingerprint", "sig": "base64-encoded-signature..." } ] } ``` ## Data Contracts ### Analyzer Output Schema ```typescript interface AnalyzerOutput { analyzer: string; ecosystem: string; packages: Array<{ purl: string; name: string; version: string; scope: 'runtime' | 'dev' | 'optional'; locations: string[]; hashes?: Record; licenses?: string[]; }>; relationships: Array<{ parent: string; child: string; type: 'depends-on' | 'dev-depends-on'; }>; } ``` ### SBOM Output Formats | Format | Schema Version | Use Case | |--------|---------------|----------| | CycloneDX | 1.6 | Default, rich dependency graph | | SPDX | 3.0.1 | Regulatory compliance, legal | | SPDX | 2.3 | Legacy compatibility | ## Error Handling | Error | Recovery | |-------|----------| | Layer download failed | Retry with exponential backoff | | Analyzer timeout | Mark analyzer as partial, continue | | Lock file parse error | Fall back to manifest parsing | | Invalid PURL | Log warning, skip component | | Attestation failed | Return SBOM without attestation | ## Observability ### Metrics | Metric | Type | Labels | |--------|------|--------| | `sbom_generation_duration_seconds` | Histogram | `image_size`, `layer_count` | | `sbom_analyzer_duration_seconds` | Histogram | `analyzer` | | `sbom_components_total` | Counter | `analyzer`, `ecosystem` | | `sbom_size_bytes` | Histogram | `format` | ### Key Log Events | Event | Level | Fields | |-------|-------|--------| | `sbom.generation.start` | INFO | `image`, `digest` | | `sbom.analyzer.complete` | DEBUG | `analyzer`, `package_count` | | `sbom.assembly.complete` | INFO | `total_components`, `format` | | `sbom.attestation.signed` | INFO | `digest`, `keyid` | ## Related Flows - [Scan Submission Flow](02-scan-submission-flow.md) - Parent flow - [Binary Delta Attestation Flow](15-binary-delta-attestation-flow.md) - Binary-level analysis - [Evidence Bundle Export Flow](13-evidence-bundle-export-flow.md) - SBOM packaging