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

Flags (common)

Inputs/outputs

Determinism rules

Offline/air-gap notes


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

OptionAliasDescription
--sbom <path>-sRequired. SBOM file to scan (CycloneDX or SPDX JSON).
--vuln-db <path>-dCompact vuln-db file or directory. Default: $STELLA_VULN_DB_DIR, else ~/.stella/vuln-db/latest.
--format <fmt>-fReport 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>-oWrite 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-onlyAlias for --scope deployed (drop dev/test/build from the gate).
--exclude-devAlias for --scope deployed.
--include-devAlias for --scope all (gate over dev/test/build too when combined with --fail-on-scope all).
--exclude-optionalMove 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.
--reachabilityAlso 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-vexDrop 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:

--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

CodeMeaning
0No finding in the gated scope at or above --fail-on (Section-B presence does not fail unless --fail-on-scope all).
1Gate breach in the gated scope.
2Usage / 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:

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.baseImage sub-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 in SbomCheckCommand.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 }

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

OptionAliasDescription
--archive <path>-aRequired. Path to signed SBOM archive (tar.gz).
--offlinePerform offline verification using bundled certificates.
--trust-root <dir>-rPath to trust root directory containing CA certificates.
--output <file>-oWrite verification report to file instead of stdout.
--format <fmt>-fOutput format: json, summary (default), or html.
--strictFail if any optional verification step fails.
--verboseShow detailed verification progress.

Verification Checks

The command performs the following verification checks:

  1. Archive Integrity: Validates all file hashes against manifest.json.
  2. DSSE Envelope Signature: Verifies the DSSE envelope structure and signatures.
  3. SBOM Schema: Validates SBOM content against SPDX or CycloneDX schemas.
  4. Tool Version: Verifies tool version metadata is present and valid.
  5. Timestamp Validity: Checks generation timestamp is within acceptable window.

2026-02-26 parity note

Exit Codes

CodeMeaning
0Verification passed
1Verification failed (one or more checks failed)
2Error (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

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

OptionAliasDescription
--image <ref>-iRequired. Target image reference (registry/repo@sha256:...). Must include digest.
--file <path>-fPath 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.
--overwriteSupersede the current active SBOM referrer for this image.
--registry-url <url>Override registry URL (defaults to parsed from --image).
--verboseShow detailed output including blob digest and normalization info.

Behavior

  1. 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.json for the authoritative field list.
  2. Publication: The canonical SBOM bytes are pushed as an OCI artifact with:

    • artifactType: application/vnd.stellaops.sbom.cdx+json or application/vnd.stellaops.sbom.spdx+json
    • subject: points to the image manifest digest
    • Annotations: dev.stellaops/sbom-version, dev.stellaops/sbom-format
  3. Overwrite/Supersede: When --overwrite is specified:

    • The current active SBOM referrer is resolved (highest version number).
    • A new referrer is pushed with version = prior + 1 and a dev.stellaops/sbom-supersedes annotation pointing to the prior manifest digest.
    • No registry deletes are performed (purely additive).

Exit Codes

CodeMeaning
0Publication succeeded
1Publication failed (registry error, auth failure)
2Error (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