stella sbom — Command Guide
Overview
The stella sbom command group generates, composes, verifies, and publishes Software Bills of Materials (SBOMs) for container images and source trees. It is the primary CLI surface for producing deterministic SPDX and CycloneDX documents, verifying signed SBOM archives, and attaching canonical SBOMs to images as OCI referrers.
Audience: release engineers, security engineers, and CI authors who need verifiable, supply-chain-grade SBOMs from the Stella Ops scanner.
Related: For offline-first, per-layer SBOM generation with frozen timestamps, see the
stella sbomercommand group.
Commands
stella sbom generate --image <ref> [--output sbom.spdx.json] [--platform linux/amd64] [--offline]stella sbom compose --fragment <path> --output composition.json --offlinestella sbom check --sbom <sbom> [--vuln-db <file|dir>] [--scope ...] [--fail-on <sev>]— Match an SBOM against a local compact vuln DB (offline; report-only)stella sbom verify --file <sbom> --signature <sig> --key <keyfile>stella sbom verify --archive <path.tar.gz> [--offline] [--trust-root <dir>]— Verify signed SBOM archive
Flags (common)
--offline: no network pulls; use local cache/OCI archive.--platform/--arch(generate): target platform/architecture for multi-arch images (e.g.linux/amd64,arm64,x86_64). Defaults to the host architecture; a digest-pinned--imagealready selects a single manifest, so the flag is ignored there.--format:spdx-json(default) orcyclonedx-json.--attest: emit DSSE attestation alongside SBOM.--hash: include layer/file hashes (deterministic ordering).
Inputs/outputs
- Inputs: container image, directory, or fragments.
- Outputs: deterministic SPDX/CycloneDX JSON, optional DSSE + checksums.
- Exit codes follow the canonical output and exit codes reference; verification failure uses exit code 3 or 4 depending on cause.
Determinism rules
- Stable ordering of packages/files; timestamps UTC.
- Hashes hex-lowercase; no host-specific paths.
Offline/air-gap notes
- With
--offline, image sources must already be cached (tar/OCI archive); command fails with exit code 5 if it would fetch remotely. - Verification uses local trust roots; no remote key fetch.
stella sbom check — Offline Vulnerability Match (declared → deployed scope)
Synopsis
stella sbom check --sbom <sbom.json> [options]
Matches an SBOM’s PURLs against a locally-cached compact vuln DB and reports vulnerabilities. Fully offline: no backend, no network, no policy (report-only, Trivy-style). Reuses the shared LocalAdvisoryMatcher + CompactVulnDbReader so verdicts match the server.
The command consumes the dependency scope carried in the SBOM (the stellaops.scope.* properties and CycloneDX dependencies[] graph; see SBOM Scope Property Registry v1) to partition findings into a deployed headline and a declared-only informational section — closing the dev/build/test noise gap without ever dropping a finding.
Options
| Option | Alias | Description |
|---|---|---|
--sbom <path> | -s | Required. SBOM file to scan (CycloneDX or SPDX JSON). |
--vuln-db <path> | -d | Compact vuln-db file or directory. Default: $STELLA_VULN_DB_DIR, else ~/.stella/vuln-db/latest. |
--format <fmt> | -f | Report format: table (default) | json | sarif. |
--fail-on <sev> | Exit non-zero (1) if any gated finding is at or above this severity: critical | high | medium | low. | |
--output <file> | -o | Write the report to a file instead of stdout. |
--scope <sel> | Repeatable. Which scopes appear in the HEADLINE (gated) section: all | deployed | runtime | dev | test | build. Default: deployed. | |
--prod-only | Alias for --scope deployed (drop dev/test/build from the gate). | |
--exclude-dev | Alias for --scope deployed. | |
--include-dev | Alias for --scope all (gate over dev/test/build too when combined with --fail-on-scope all). | |
--exclude-optional | Move optional/peer/unknown findings into the non-deployed section (stricter image-parity). | |
--fail-on-scope <sel> | Which section --fail-on is computed over: deployed (default) | all. | |
--reachability | Also classify which found CVEs are reachable via the offline reachability engine. Requires --call-graph or --source. | |
--call-graph <path> | Pre-extracted call-graph snapshot JSON for --reachability. | |
--source <dir> | Source tree to extract a call graph from in-process for --reachability. | |
--reach-lang <lang> | Language of --source for extraction: go (default) | rust. | |
--vex <file> | VEX file(s) (OpenVEX or CycloneDX-VEX JSON) to apply. Repeatable. The offline compact-DB VEX store is consulted in addition. | |
--suppress-vex | Drop findings a VEX statement marks not_affected/fixed (default: annotate only, never drop). |
The default: layered report, gate on deployed, never drop
stella sbom check does not silently drop dev/build/test findings (the way Trivy’s prod-only default does). Instead it partitions and warns:
- Section A — DEPLOYED (headline, gated): findings on
deployed=truecomponents (runtime + bundled + provided + optional + peer + unknown). This is the image-parity view comparable to Trivy. - Section B — DECLARED-ONLY / NON-DEPLOYED (informational): findings on
dev/test/buildcomponents, shown under “Declared-but-not-deployed (dev/build/test) — not in --fail-on gate.” These are real supply-chain surface (a compromisedeslint-plugin-*orbabelruns in CI with secrets and registry creds) — so they are never hidden, just separated.
--fail-on evaluates against Section A only by default (--fail-on-scope deployed). Supply-chain-strict operators set --fail-on-scope all (or --include-dev) to gate on Section B too.
Why not drop dev by default? Dropping dev/build hides a real attack class Stella exists to catch; flattening everything (the old behavior) buries the gate-relevant findings under 90k+ dev nodes. Partitioning + gating-on-deployed is the safe middle: image-parity gate first, full SCA view first-class and gateable on demand. unknown stays deployed-side (fail-safe) — on degraded/3rd-party inputs we never prove non-deployment, so the finding stays in the headline.
Declared-only prediction caveat
When the SBOM has no install evidence (lockfile transcribed, stellaops.lang.meta.declaredOnly=true), deployed is a prediction — “would-be-deployed if you npm ci --omit=dev”, not an observation. If the image actually installs devDependencies, the Section-B findings ARE present. So on a declared-only SBOM with a non-empty Section B the command prints a visible warning to stderr and surfaces the excluded count:
WARNING: gate excludes N dev/build/test finding(s) on a declared-only SBOM;
if your image installs devDependencies these ARE present — re-run with --scope all to gate on them.
The warning is advisory (the exit code still reflects Section A) but the count is never silently swallowed. Scope must never silently relax a release gate on prediction-only data.
Exit codes
| Code | Meaning |
|---|---|
| 0 | No finding in the gated scope at or above --fail-on (Section-B presence does not fail unless --fail-on-scope all). |
| 1 | Gate breach in the gated scope. |
| 2 | Usage / IO error (SBOM not found, bad flag value, DB not found, parse error). |
JSON output (stellaops.sbom-check/v1.1)
--format json emits schema stellaops.sbom-check/v1.1 (additive over v1). Each finding gains deployed (the gate bit), effectiveScope (the canonical scope token, null when the SBOM carried no signal), and priority (the composed scope × reachability rank). A top-level scopeSummary block:
"scopeSummary": {
"deployedFindings": 146,
"declaredOnlyFindings": 38,
"declaredOnlySbom": true,
"byScope": { "dev": 30, "runtime": 140, "test": 8, "unknown": 6 }
}
scope and reachability are independent finding fields. With --reachability, each finding’s reachability is reachable:proven / reachable:likely / not-observed / unknown, or null when the scope gate skipped it (by default only deployed findings are assessed — see below). priority composes them per the §6 two-axis matrix: a NOT-deployed finding caps at P4 even if source-reachable, and a deployed+proven finding tops the gate at P0 — reachability can never re-inflate dev noise to the headline.
SARIF (--format sarif): non-deployed findings emit properties.stellaops.deployed=false and downgrade to note so consumers route them separately; deployed findings keep their severity-mapped level.
Reachability composition
--reachability assesses only Section A (deployed) findings by default — call-graph budget is not spent proving a dev tool reachable for the headline. --include-dev --reachability assesses Section B too, labelled by its own scope band. The found → deployed → executed tiers stay independently visible; scope is the middle tier.
VEX application (offline, BP-12)
stella sbom check applies VEX (Vulnerability Exploitability eXchange) statements fully offline, from two sources consulted on every run:
- The compact DB’s
vextable — the air-gapped VEX store the CLI already ships. The exporter projects the Excititor consensus into this table; the check command reads it viaCompactVulnDbReader.GetVexStatements. (The full Excititor subsystem — Mongo, CSAF/OpenVEX/CycloneDX connectors, consensus — is server-side and not consultable offline by the CLI; the compact DB is its offline projection.) - Operator-supplied
--vex <file>— repeatable; accepts OpenVEX (statements[]) and CycloneDX-VEX (vulnerabilities[]withanalysis.state) JSON. CycloneDX states are mapped onto the canonical vocabulary (resolved→fixed,exploitable→affected,in_triage→under_investigation,not_affected/false_positive→not_affected).
Each finding is matched by vuln id (including aliases) + product (purl or canonical eco|name); a file statement beats a baked-DB statement at equal product specificity, and a product-specific statement beats a whole-product * statement. Every finding then carries:
"vexStatus": "not_affected", // not_affected | affected | fixed | under_investigation | none
"vexJustification": "vulnerable_code_not_in_execute_path",
"vexSource": "file:openvex" // file:openvex | file:cyclonedx | vuln-db | null
plus a top-level vexSummary { annotated, byStatus } roll-up.
Default = annotate only. VEX never changes which findings are reported, so existing baselines and counts do not shift. Pass --suppress-vexto drop findings a VEX statement marks not_affected or fixed. Suppression is reflected in findingCount, the gate, and vexSummary.
Remediation breaking-change-risk
Each finding’s remediation object adds breakingRisk = major | minor | patch | unknown — the semver delta from installedVersion to remediation.minimalUpgrade, classified exactly like the benchmark BP-05 minimal-upgrade distribution (strip a leading v/V, drop +build / -prerelease suffixes, split on ., compare major→minor→patch). major signals breaking-change risk. When the jump is not a clean numeric triple on both sides, or there is no orderable minimal upgrade (range fix / missing installed version), breakingRisk is unknown (never fabricated).
Deferred — base-image-recommendation. A
remediation.baseImagesub-feature (recommend a less-vulnerable base image) is intentionally not built here. It requires real base-image layer analysis + a base-image catalog, neither of which the metadata-image benchmark can exercise; it is only meaningful on real-image scans. See the note inSbomCheckCommand.BuildRemediation.
Affected vs. unknown (matchState) roll-up
Each finding carries matchState = affected (a present version range proved the install in-range, or the advisory is malware) vs. unknown (name-only / ecosystem-name / no usable version range — operator-adjudicable, and not counted as a confirmed hit by the gate). The headline findingCount / severitySummary / scopeSummary count over all findings, including unknown, so a naive consumer can over-read unknowns as positives. The top-level split surfaces the same partition the gate already applies, without re-scanning findings[]:
"affectedFindingCount": 366,
"unknownFindingCount": 45,
"matchStateSummary": { "affected": 366, "unknown": 45 },
"affectedSeveritySummary": { "critical": 12, "high": 140, "low": 30, "medium": 184 }
affectedFindingCount+unknownFindingCount==findingCount(the split partitions the full set).matchStateSummaryis the per-matchStatecount;affectedSeveritySummarymirrorsseveritySummarybut excludes the adjudicableunknownfindings.findingCount,severitySummary, andscopeSummaryare unchanged — every existing baseline number is preserved.
Note: severitySummary may already contain an unknown key, but that is the severity bucket (advisory with no CVSS severity), not matchState; do not conflate the two.
JSON schema version
--format json emits stellaops.sbom-check/v1.4 (additive over v1.3): the top-level affectedFindingCount / unknownFindingCount / matchStateSummary / affectedSeveritySummary roll-ups (the affected-vs-unknown split above). v1.3 added per-finding vexStatus / vexJustification / vexSource and remediation.breakingRisk, plus the top-level vexSummary block.
Examples
# Default: layered report, gate on deployed, dev/test/build shown but not gated
stella sbom check --sbom app.cdx.json --fail-on high
# Trivy-parity: drop dev/build/test from the gate entirely
stella sbom check --sbom app.cdx.json --fail-on high --prod-only
# Supply-chain-strict: gate over dev/test/build too
stella sbom check --sbom app.cdx.json --fail-on high --fail-on-scope all
# Stricter image-parity: treat optional/peer/unknown as non-deployed
stella sbom check --sbom app.cdx.json --fail-on critical --exclude-optional
# Machine output with scope summary
stella sbom check --sbom app.cdx.json --format json -o report.json
# Add reachability over the deployed findings (Go source)
stella sbom check --sbom app.cdx.json --reachability --source ./src --reach-lang go
# Apply an OpenVEX document (annotate-only — findings are tagged but not dropped)
stella sbom check --sbom app.cdx.json --vex vex.openvex.json --format json
# Opt in to suppression: drop findings marked not_affected/fixed by VEX
stella sbom check --sbom app.cdx.json --vex vex.openvex.json --suppress-vex --fail-on high
stella sbom verify — Signed Archive Verification
Synopsis
stella sbom verify --archive <path.tar.gz> [options]
Verify a signed SBOM archive (tar.gz) containing SBOM, DSSE envelope, manifest, and verification materials.
Options
| Option | Alias | Description |
|---|---|---|
--archive <path> | -a | Required. Path to signed SBOM archive (tar.gz). |
--offline | Perform offline verification using bundled certificates. | |
--trust-root <dir> | -r | Path to trust root directory containing CA certificates. |
--output <file> | -o | Write verification report to file instead of stdout. |
--format <fmt> | -f | Output format: json, summary (default), or html. |
--strict | Fail if any optional verification step fails. | |
--verbose | Show detailed verification progress. |
Verification Checks
The command performs the following verification checks:
- Archive Integrity: Validates all file hashes against
manifest.json. - DSSE Envelope Signature: Verifies the DSSE envelope structure and signatures.
- SBOM Schema: Validates SBOM content against SPDX or CycloneDX schemas.
- Tool Version: Verifies tool version metadata is present and valid.
- Timestamp Validity: Checks generation timestamp is within acceptable window.
2026-02-26 parity note
stella sbom verifynow follows verification-first behavior and no longer relies on structural placeholder checks.- Deterministic failure reasons are surfaced for missing trust roots, malformed signatures, and verification mismatch paths.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Verification passed |
| 1 | Verification failed (one or more checks failed) |
| 2 | Error (file not found, parse error, etc.) |
Examples
# Verify a signed SBOM archive with summary output
stella sbom verify --archive signed-sbom-sha256-abc123.tar.gz
# Verify offline with custom trust root
stella sbom verify --archive signed-sbom.tar.gz --offline --trust-root /path/to/roots/
# Generate JSON verification report
stella sbom verify --archive signed-sbom.tar.gz --format json --output report.json
# Generate HTML report for sharing
stella sbom verify --archive signed-sbom.tar.gz --format html --output report.html
# Strict mode (fail on optional check failures)
stella sbom verify --archive signed-sbom.tar.gz --strict
Sample Output
SBOM Verification Report
========================
Archive: signed-sbom-sha256-abc123.tar.gz
Status: VERIFIED
Checks:
[PASS] Archive integrity (All 4 file hashes verified)
[PASS] DSSE envelope signature (Valid, 1 signature(s))
[PASS] SBOM schema (Valid, SPDX 2.3)
[PASS] Tool version (Suite: 2027.Q1, Scanner: 1.2.3)
[PASS] Timestamp validity (Within validity window, 2026-01-15)
SBOM Details:
Format: SPDX 2.3
Components: 142
Artifact: sha256:abc123def456
Generated: 2026-01-15T10:30:00Z
Tool: StellaOps Scanner v2027.Q1
Archive Format
The signed SBOM archive follows the format defined in SPRINT_20260112_016_SCANNER_signed_sbom_archive_spec:
signed-sbom-{digest}-{timestamp}.tar.gz
├── sbom.spdx.json (or sbom.cdx.json)
├── sbom.dsse.json
├── manifest.json
├── metadata.json
├── certs/
│ ├── signing-cert.pem
│ └── fulcio-root.pem
├── rekor-proof/
│ ├── inclusion-proof.json
│ └── checkpoint.sig
├── schemas/
│ └── ...
└── VERIFY.md
Related Commands
stella sbom generate— Generate SBOM from container imagestella sbom publish— Publish canonical SBOM as OCI referrerstella attest verify --offline— Verify attestation bundles offlinestella evidence export— Export evidence bundle with signed SBOM
stella sbom publish — OCI SBOM Publication
Synopsis
stella sbom publish --image <ref> [--file <path>] [--format cdx|spdx] [--overwrite]
Publishes a canonical (volatile-fields-stripped, key-sorted) SBOM as an OCI referrer artifact attached to the specified container image. The published artifact is discoverable via the OCI Distribution Spec 1.1 referrers API.
Options
| Option | Alias | Description |
|---|---|---|
--image <ref> | -i | Required. Target image reference (registry/repo@sha256:...). Must include digest. |
--file <path> | -f | Path to SBOM file. If omitted, fetches from Scanner CAS for this image. |
--format <fmt> | SBOM format: cdx (CycloneDX) or spdx. Auto-detected from file content if omitted. | |
--overwrite | Supersede the current active SBOM referrer for this image. | |
--registry-url <url> | Override registry URL (defaults to parsed from --image). | |
--verbose | Show detailed output including blob digest and normalization info. |
Behavior
Normalization: The SBOM is canonicalized before publication:
- Volatile fields stripped:
serialNumber,metadata.tools,metadata.authors,metadata.timestamp(CycloneDX);creationInfo.created,creationInfo.creators,creationInfo.licenseListVersion(SPDX). - Object keys sorted lexicographically (ordinal).
- Arrays of objects sorted by deterministic keys (bom-ref, purl, name@version).
- See
docs/contracts/sbom-volatile-fields.jsonfor the authoritative field list.
- Volatile fields stripped:
Publication: The canonical SBOM bytes are pushed as an OCI artifact with:
artifactType:application/vnd.stellaops.sbom.cdx+jsonorapplication/vnd.stellaops.sbom.spdx+jsonsubject: points to the image manifest digest- Annotations:
dev.stellaops/sbom-version,dev.stellaops/sbom-format
Overwrite/Supersede: When
--overwriteis specified:- The current active SBOM referrer is resolved (highest version number).
- A new referrer is pushed with
version = prior + 1and adev.stellaops/sbom-supersedesannotation pointing to the prior manifest digest. - No registry deletes are performed (purely additive).
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Publication succeeded |
| 1 | Publication failed (registry error, auth failure) |
| 2 | Error (file not found, invalid image reference, parse error) |
Examples
# Publish a CycloneDX SBOM to an image
stella sbom publish --image registry.example.com/myapp@sha256:abc123... --file app.cdx.json
# Publish with explicit format
stella sbom publish --image registry.example.com/myapp@sha256:abc123... --file app.json --format cdx
# Overwrite existing SBOM (supersede)
stella sbom publish --image registry.example.com/myapp@sha256:abc123... --file improved.cdx.json --overwrite
# Verbose output
stella sbom publish --image registry.example.com/myapp@sha256:abc123... --file app.cdx.json --verbose
Sample Output
Published SBOM as OCI referrer:
Blob digest: sha256:e3b0c44298fc1c149afbf4c8996fb924...
Manifest digest: sha256:7d865e959b2466918c9863afca942d0f...
Version: 1
Artifact type: application/vnd.stellaops.sbom.cdx+json
Verifier Discovery
Third-party verifiers can discover published SBOMs via the OCI referrers API:
# List SBOM referrers for an image (using oras CLI)
oras discover registry.example.com/myapp@sha256:abc123... \
--artifact-type application/vnd.stellaops.sbom.cdx+json
# Pull the latest SBOM
oras pull registry.example.com/myapp@sha256:abc123... \
--artifact-type application/vnd.stellaops.sbom.cdx+json
