Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Introduced `SbomService` tasks documentation. - Updated `StellaOps.sln` to include new projects: `StellaOps.AirGap.Time` and `StellaOps.AirGap.Importer`. - Added unit tests for `BundleImportPlanner`, `DsseVerifier`, `ImportValidator`, and other components in the `StellaOps.AirGap.Importer.Tests` namespace. - Implemented `InMemoryBundleRepositories` for testing bundle catalog and item repositories. - Created `MerkleRootCalculator`, `RootRotationPolicy`, and `TufMetadataValidator` tests. - Developed `StalenessCalculator` and `TimeAnchorLoader` tests in the `StellaOps.AirGap.Time.Tests` namespace. - Added `fetch-sbomservice-deps.sh` script for offline dependency fetching.
5.3 KiB
5.3 KiB
Findings Ledger Export HTTP Surface
Prep task: PREP-LEDGER-EXPORT-35-001-NO-HTTP-API-SURFACE (Sprint 0121)
Goals
- Publish an HTTP surface for deterministic, offline-friendly exports of Findings Ledger data (findings, VEX, advisories, SBOMs) so downstream SDK/OpenAPI tasks can proceed.
- Define filter contract, pagination, media types, and provenance fields required by Evidence Locker and Policy Engine consumers.
Non-goals
- Implementing the endpoints (covered by LEDGER-EXPORT-35-001).
- Final OAS/SDK generation (tracked in PREP-LEDGER-OAS-61-001/002/62-001/63-001).
Base Service
- Host: findings-ledger service (minimal API) under
src/Findings/StellaOps.Findings.Ledger. - Base path:
/ledger/export. - Auth: service-to-service bearer token; require
scope=ledger.export.read. - Tenancy:
X-Stella-Tenantheader (strictly required); responses never mix tenants. - Determinism: server sorts by
(event_sequence, projection_version, cycle_hash); pagination tokens encode the last emitted tuple and filter set. No wall-clock dependence. - Media types: default
application/x-ndjson; clients may requestapplication/json(array) for small result sets. Always emitContent-Encoding: gzipwhenAccept-Encodingallows.
Endpoints
1) Findings
GET /ledger/export/findings- Filters (all optional unless noted):
shape(required):canonical|compact(controls payload shape; compact strips verbose provenance fields for air-gap bundles)since_sequence(long, ≥0),until_sequence(long, inclusive) — enables range slicing.since_observed_at,until_observed_at(ISO-8601 UTC) — observation window.advisory_id(repeatable),component_purl(repeatable),finding_status(open|fixed|dismissed),severity(critical|high|medium|low|unknown).risk_profile_version(string) — filters by attached risk profile revision.
- Response item (canonical shape):
finding_id,event_sequence,observed_at,component(purl, version, source),advisories(ids, cwes),status,severity,risk(score, severity, profile_version, explanation_id),projection_version,cycle_hash,evidence_bundle_ref(digest, dsse_digest, timeline_ref),provenance(ledger_root, projector_version, policy_version, datasource_ids).
2) VEX
GET /ledger/export/vex- Filters:
shape,since_sequence/until_sequence,since_observed_at/until_observed_at,product_id(repeatable),advisory_id,status(affected|not_affected|under_investigation),statement_type(exploitation|justification). - Response item adds
vex_statement_id,product(purl or CPE),status_justification,known_exploited(bool),timestamp,projection_version,cycle_hash,provenance.
3) Advisories
GET /ledger/export/advisories- Filters:
shape,since_sequence/until_sequence,severity,source(feed id),cwe_id,kev(bool),cvss_version,cvss_score_min,cvss_score_max. - Response item:
advisory_id,source,title,description,cwes,cvss(version, vector, base_score),published,modified,status,epss(score, percentile),projection_version,cycle_hash,provenance.
4) SBOMs
GET /ledger/export/sboms- Filters:
shape,since_sequence/until_sequence,since_observed_at/until_observed_at,subject_digest(OCI digest),sbom_format(spdx-json|cyclonedx-json),component_purl(repeatable),contains_native(bool),slsa_build_type(string). - Response item:
sbom_id,subject(digest, media_type),sbom_format,created_at,components_count,has_vulnerabilities(bool),materials(digests),projection_version,cycle_hash,provenance.
Pagination
- Cursor param:
page_token(opaque base64url JSON:{ "last": { "event_sequence": long, "projection_version": string, "cycle_hash": string }, "filters_hash": sha256 }). - Page size:
page_size(default 500, max 5000). Server rejects ifpage_sizediffers across pages for same token. - Response envelope (for both NDJSON and JSON array):
- Header
X-Stella-Next-Page-Tokenwhen more data exists. - Header
X-Stella-Result-Countwith items count. - When
Prefer: return=minimalis set, omit envelope body for NDJSON; clients rely on headers.
- Header
Error Contract
- 400: unknown filter, invalid range, or filter combination mismatch with
filters_hashinsidepage_token. - 401/403: missing or insufficient scope.
- 409: requested
shapenot compatible with downstream air-gap mode (compact requested where policy mandates canonical). - 429: enforcement of determinism guard (server detected projection drift vs cycle_hash); include
X-Stella-Drift-Reason.
Observability & Determinism Hooks
- Emit structured logs
ledger.export.requestandledger.export.emitwith tenant, endpoint, filters_hash, page_size, result_count, duration_ms. - Counters:
ledger_export_items_total{endpoint,tenant}andledger_export_failures_total{endpoint,reason}. - Traces: span name
ledger.export.{endpoint}; attachfilters_hash,page_size,next_page_token_presentattributes.
artefact location
- This document:
docs/modules/findings-ledger/export-http-surface.md(hash stability: keep deterministic ordering as authored on 2025-11-20). - Link from sprint 0121 PREP-LEDGER-EXPORT-35-001; downstream tasks should reference this path for contract details.