SbomService
The canonical SBOM repository for Stella Ops — storage, versioning, lineage, and the artifact association graph.
Status: Implemented Source: src/SbomService/ Owner: Scanner Guild
Purpose
SbomService is the system of record for Software Bills of Materials in the Stella Ops vulnerability scanner. It provides SBOM storage, versioning, and lineage tracking over a canonical repository that supports the CycloneDX and SPDX formats, including temporal (point-in-time and range) ledger queries, version diffing, dependency-path queries, and artifact lineage graph analysis.
The service also owns the artifact association graph for source snapshots, build runs, OCI image digests, SBOM documents, scan jobs, detector reports, findings, and evidence bundles. See artifact-links-api.md for the API contract and CLI/UI workflow.
Supported SBOM formats (validated at upload by
SbomNormalizationService/SbomUploadService): CycloneDX 1.4, 1.5, 1.6 and SPDX 2.3 and 3.0. Other versions are rejected with a validation error. Detection keys offbomFormat(CycloneDX) andspdxVersion(SPDX).
Components
Services:
StellaOps.SbomService- Main SBOM service host. Exposes the HTTP surface (ASP.NET Core minimal APIs) and business logic, wires persistence/lineage, and registers as the Stella Router microservicesbomservice.
Libraries:
StellaOps.SbomService.Persistence- PostgreSQL persistence library (SbomServiceDataSource, catalog/component-lookup/entrypoint/orchestrator/projection repositories, embedded SQL migrations, startup auto-migration).StellaOps.SbomService.Lineage- Lineage domain library (EF CoreLineageDbContext, lineage-edge / verdict-link / VEX-delta entities and repositories, graph services).
Tests:
StellaOps.SbomService.Tests- Host/endpoint and service unit tests.StellaOps.SbomService.Persistence.Tests- PostgreSQL persistence integration tests.StellaOps.SbomService.Lineage.Tests- Lineage domain/service tests.
Note: the MVC controllers under
Controllers/(LineageController,LineageStreamController,RegistrySourceController,RegistryWebhookController) remain present in source, butProgram.csdoes not callAddControllers()/MapControllers(). The live HTTP surface is the set of minimal-API endpoints registered inProgram.cs. Registry-source management is live throughRegistrySourceEndpointExtensions.MapRegistrySourceEndpoints(...); lineage-stream controller routes remain controller-only until explicitly mapped.
Authentication & Authorization
SbomService validates Authority bearer tokens and Router-generated signed identity envelopes through the shared resource-server pipeline. Raw x-tenant-id/x-user-id headers cannot authenticate a request. Four named policies are defined in Auth/SbomPolicies.cs; each requires an authenticated principal plus its canonical scope:
Sbom.Read/sbom:read- tenant-scoped versions, ledger, lineage, projection, entrypoint, registry-source, and artifact-link reads.Sbom.Write/sbom:write- mutating endpoints (upload, entrypoints, registry sources, artifact-link status, replay verify).Sbom.Attest/sbom:attest- signed lineage evidence-pack export.Sbom.Internal/sbom:operate- installation-wide maintenance, tenant-scoped internal orchestrator control, and the legacy installation-wide catalog/component/path views until those schemas are tenantized.
The authenticated stellaops:tenant claim is the sole tenant authority. Existing body/query tenant fields are compatibility assertions only: omitted uses the authenticated tenant, equal is accepted, and mismatch is rejected. Authenticated subject identity also overrides caller-supplied actor fields.
API surface (minimal APIs in Program.cs)
All routes require the policy noted; health/OpenAPI are anonymous.
Health & metadata:
GET /healthz,GET /readyz(anonymous),GET /openapi/*(anonymous), build-info endpoint (viaBuildInfo).
SBOM upload & ledger:
POST /sbom/uploadandPOST /api/v1/sbom/upload(alias) - upload/ingest an SBOM (Write).GET /sbom/ledger/history|/sbom/ledger/point|/sbom/ledger/range|/sbom/ledger/diff|/sbom/ledger/lineage(Read).
SBOM queries:
GET /console/sboms- installation-wide legacy catalog (Operate).GET /components/lookup- installation-wide legacy component lookup (Operate).GET /sbom/context- tenant timeline plus installation-wide dependency paths (Operate;501if no dependency-path backend).GET /sbom/paths- installation-wide dependency paths (Operate;501if no dependency-path backend).GET /sbom/versions- version timeline for an artifact (Read).GET /sboms/{snapshotId}/projection- normalized projection for a snapshot (Read).GET /entrypoints/POST /entrypoints- service entrypoints (Read/Write).
Artifact links (association graph):
POST /api/v1/artifact-links,POST /api/v1/artifact-links/import(Write).GET /api/v1/artifact-links/resolve,/unlinked,/candidates(Read).POST /api/v1/artifact-links/{linkId:guid}/confirm|/reject|/verify(Write).
Registry sources:
GET /api/v1/registry-sources,POST /api/v1/registry-sources,GET|PUT|DELETE /api/v1/registry-sources/{id}(Read/Write).POST /api/v1/registry-sources/test,POST /api/v1/registry-sources/{id}/test,POST /api/v1/registry-sources/{id}/trigger,POST /api/v1/registry-sources/{id}/pause,POST /api/v1/registry-sources/{id}/resume(Write).GET /api/v1/registry-sources/{id}/runsreturns run history with trigger type, counts, scan job ids, and matched images.GET /api/v1/registry-sources/{id}/discover/repositories,/discover/tags/{repository}, and/discover/imagesreturn discovery previews filtered by enabled mask records.POST /api/v1/registry-sources/{id}/discover-and-scandiscovers matching images and submits Scanner jobs immediately.GET /api/v1/registry-sources/{id}/imagesand/images/sbomquery matched image rows and SBOM status for operator review.- Enabled path/tag mask records require a registry write credential reference (
authRefUri/credentialRef) because matching images are expected to receive SBOM/CDX OCI referrer metadata. Missing write credentials fail connection tests and create/update preflight; stale sources without credentials record failed runs so the Web Schedule Health panel can show that nightly refresh is not working. - Mask records do not create independent schedules. The persisted schedule belongs to the registry source; removing a mask removes it from automatic mask-change/nightly selection, and removing all enabled masks makes the nightly refresh skip that source instead of leaving orphan mask work behind.
Lineage graph & compare:
GET /api/v1/lineage/{artifactDigest},/{artifactDigest}/children,/{artifactDigest}/parents(Read).GET /api/v1/lineage/diff,/api/v1/lineage/hover,/api/v1/lineage/compare(Read).POST /api/v1/lineage/export(Attest;413over 50 MB,501if export backend unavailable).POST /api/v1/lineage/verify(Write),POST /api/v1/lineage/compare-drift(Read) - deterministic replay verification.POST /api/change-traces/build,GET /api/change-traces/{traceId}- change-trace compatibility shim (Read).GET /api/compare/baselines/{scanDigest}- baseline recommendation stub (Read).
Internal/operational (Sbom.Internal / sbom:operate):
GET /internal/sbom/events,/asset-events,/inventory,/resolver-feed,/resolver-feed/export(NDJSON),/ledger/audit,/analysis/jobs.POST /internal/sbom/events/backfill,/inventory/backfill,/resolver-feed/backfill,/retention/prune.GET|POST /internal/orchestrator/sources,/control,/watermarks.
Configuration
Configuration is bound from appsettings.json plus environment variables prefixed SBOM_. Keys live under the SbomService configuration root.
Key settings:
SbomService:PostgreSQL(PostgresOptions, incl.ConnectionString,SchemaName) - durable store. When no connection string is set, the service throws on startup unless an explicit local harness is enabled (SbomService:LocalHarness:Enabled=true, or theDevelopmentLocalHarness/TestingLocalHarnessenvironment); the canonical event, ledger, watermark, lineage, and registry-source stores are durable-only.SbomService:Ledger(SbomLedgerOptions) - retention:MaxVersionsPerArtifact(default 50),MaxAgeDays,MinVersionsToKeep(default 1).SbomService:HoverCache(UseDistributed,Ttl) - lineage hover-card cache over the signed Router messaging cache factory (five-minute default TTL).SbomService:CompareCache(UseDistributed) - directional lineage compare cache over the same factory (ten-minute default TTL).SbomService:Concelier(ConcelierLearnOptions:LearnOnUpload,TimeoutSeconds) - best-effort forward of uploaded SBOMs to Concelier/api/v1/learn/sbom.SbomService:RegistryHttp,SbomService:ScannerHttp(HTTP client timeouts),SbomService:RegistrySources,SbomService:LineageExport(incl.StorageRootPathfor the content-addressed export blob store).SbomService:RegistrySources:NightlyRefreshEnabled,NightlyRefreshHourUtc,NightlyRefreshMinuteUtc, andNightlyRefreshPollSecondscontrol the registry-source nightly SBOM refresh hosted service. Defaults are enabled at 02:00 UTC with a 60-second poll interval.- Registry-source nightly failures are persisted in
/api/v1/registry-sources/{id}/runsand surfaced in the Web source detail Schedule Health panel. Sources with no enabled mask records are skipped by nightly refresh. - Local-harness fixture paths:
SbomService:ComponentLookupPath,SbomService:CatalogPath,SbomService:ProjectionsPath. - Authority/Router integration (
Routersection), CORS, and unified audit emission (events posted to the Timeline service).
Persistence
PostgreSQL schema sbom (SbomServiceDataSource.DefaultSchemaName = "sbom"). Migrations are embedded SQL applied via AddStartupMigrations(...) (forward-only, auto-migrate on startup):
001_v1_sbomservice_baseline.sql- collapsed pre-1.0 baseline covering catalog, lookups, entrypoints, orchestrator state, projections, lineage/verdict links, durable ledger/event/inventory/runtime state, registry-source tables, and artifact associations.002_registry_source_masks_and_run_reports.sql- paired registry mask records plus richer run reports (scan_job_ids,matched_images).
The Lineage library carries its own migration (00001_InitialSchema.sql) for sbom.sbom_lineage_edges, sbom.sbom_verdict_links, and vex.vex_deltas.
Observability
- Metrics meter
StellaOps.SbomService(SbomMetrics):sbom_paths_latency_seconds,sbom_paths_queries_total,sbom_timeline_latency_seconds,sbom_timeline_queries_total,sbom_projection_seconds,sbom_projection_size_bytes,sbom_projection_queries_total,sbom_events_backlog,sbom_orchestrator_control_updates,sbom_resolver_feed_published,sbom_ledger_uploads_total,sbom_ledger_diffs_total,sbom_ledger_retention_pruned_total. - Tracing ActivitySource
StellaOps.SbomService(SbomTracing); spans includeentrypoints.list/upsert,console.sboms,components.lookup,sbom.projection,lineage.graph/diff/hover/children/parents/export/compare/verify/compare-drift,events.list,inventory.list, etc. - A Grafana dashboard ships at
Observability/sbomservice-grafana-dashboard.json.
Dependencies
- PostgreSQL (schema:
sbom). - Authority/Router - validated bearer or signed-envelope identity and canonical scopes.
- Scanner - SBOM generation source (HTTP client
Scanner,SbomService:ScannerHttp). - Concelier - best-effort learn-on-upload forwarding to
/api/v1/learn/sbom(config-gated). - Timeline - unified audit emission target.
- Attestor - SBOM attestation integration.
- ExportCenter - SBOM export and distribution.
- Signed Router messaging cache factory (Valkey in the standard Compose profile) - distributed hover/compare caches when
UseDistributedis set. SBOM does not compose an independent Redis client or a Router-disabled fallback.
Related Documentation
- Architecture:
./architecture.md - API reference:
./api-reference.md - Artifact links API:
./artifact-links-api.md - Ledger & lineage:
./ledger-lineage.md,./lineage-ledger.md - SPDX 3 profile support:
./spdx3-profile-support.md - Scanner:
../scanner/ - Attestor:
../attestor/ - Data Schemas:
../../technical/DATA_SCHEMAS.md
Current Status
Implemented with a PostgreSQL storage backend (schema sbom). Supports SBOM ingestion, versioning (ledger with point-in-time/range/diff queries), artifact lineage graphs, deterministic replay verification, the artifact association graph, and a resolver/inventory feed for downstream policy/scanner consumers. The host expects durable PostgreSQL-backed state for all canonical runtime stores; fixture-backed and in-memory repositories are injected only by explicit test/local-harness configuration.
