Files
git.stella-ops.org/docs-archived/product/advisories/2026-03-01 - Auditable ‘unknown’ VEX lifecycle design.md

5.1 KiB
Raw Permalink Blame History

Heres a compact, plugandplay blueprint for making “unknown” a firstclass, auditable state in your VEX pipeline (fits StellaOps nicely).

Why this matters (quick)

VEX (OpenVEX/CSAF) often forces binary “affected/not_affected.” In practice, evidence is missing, conflicting, or stale. Treating unknown as a deliberate, signed, and replayable decision keeps you compliant (CRA/NIS2/DORA) and operationally honest.

Lifecycle & precedence

unvalidated
  → evidence_ingested
  → proof_anchored
  → merge_candidate
  → merged_outcome { affected | not_affected | unknown }
  → scored
  → triage
  • Default unknown at ingest if anything is missing/ambiguous.
  • Precedence: latest valid timestamp wins; hard tie → lexicographic source_id tiebreak.
  • Carry a provenance bundle with every hop: {source_id, timestamp, proof_hash}.
  • Every merged outcome and score is DSSEwrapped and Rekoranchored.

Four readiness gates (fail fast)

  1. ingest_validation

    • Schemavalid OpenVEX/CSAF + DSSE envelope present.
    • Reject → unvalidated (record reasons).
  2. proof_anchor

    • Rekor entry (UUID) + inclusion proof persisted.
    • Reject if no inclusion proof or log not reachable (offline mode: queue + mark unknown).
  3. merge_precheck

    • Deterministic timestamp precedence; evidence sufficiency (at least one attestation + SBOM ref).
    • Reject if conflicts unresolved → stay at proof_anchored and set target outcome unknown.
  4. scoring_precondition

    • replay_success_ratio (e.g., ≥0.95) on verification of DSSE/rekor bundles + provenance presence.
    • Reject if below threshold or provenance gaps → do not score; outcome remains unknown.

Deterministic merge rules

  • Normalize identifiers (CVE, package PURL, image digest).
  • Collapse equivalent justifications (OpenVEX) and product trees (CSAF).
  • If any required justification absent or conflicting → merged_outcome = unknown with rationale snapshot.
  • Merge is idempotent: same inputs → byteidentical output and provenance trace.

API surface (minimal)

POST /v1/vex/ingest
Body: DSSE-envelope { payload: OpenVEX|CSAF, signatures:[] }
Resp: { state: "evidence_ingested"|"unvalidated", provenance_bundle, rekor_hint? }

POST /v1/vex/merge
Body: { product_ref, candidates:[{openvex_or_csaf_ref, provenance_bundle}], strategy:"timestamp_lexi_tiebreak" }
Resp: { merged_outcome, provenance_trace[], dsse_signed_merged }

POST /v1/score
Body: { merged_outcome_ref, policy_id, replay_window }
Resp: { signed_score_dsse, replay_verification:{ratio, failures[]}, gate_passed:boolean }

GET  /v1/triage?outcome=unknown
Resp: [{ product_ref, vuln_id, last_timestamp, missing_evidence[], next_actions[] }]

Evidence & storage

  • EvidenceLocker (your module) keeps: raw docs, DSSE envelopes, Rekor inclusion proofs, SBOM/attestation refs, provenance bundles.
  • Hash all decision artifacts; store {artifact_hash → Rekor UUID} map.
  • Offline/airgap: stage to local transparency log; when online, bridge to Rekor and backfill inclusion proofs.

Scoring model (example)

  • Base score source (e.g., CVSS/CVSSSR).
  • Dampener if outcome=unknown: apply policy (e.g., cap at 6.9 or bump to triage queue).
  • Require replay_success_ratio ≥ threshold and provenance.complete=true before emitting scores.

Acceptance tests (mustpass)

  1. missing_evidence_defaults_unknown

    • Ingest CSAF missing justification → merged outcome is unknown with rationale.
  2. dsse_anchor_presence

    • Attempt score without Rekor inclusion → gate fails.
  3. timestamp_precedence_tiebreak

    • Two equal timestamps from different sources → lexicographic source_id decides, deterministic.
  4. merge_idempotence

    • Rerun merge with same inputs → identical dsse_signed_merged hash.
  5. scoring_gate_replay_success

    • Corrupt one signature in replay set → replay_success_ratio drops; scoring blocked.

CLI hints (nice DX)

stella vex ingest --file advisories/openvex.json --sign key.pem --rekor-url $REKOR
stella vex merge  --product my/image:sha256:… --inputs dir:./evidence
stella score     --policy default --replay-window 30d
stella triage    --outcome unknown --limit 50

UI touchpoints (lean)

  • Evidence Ingest: file drop (OpenVEX/CSAF), DSSE status, Rekor anchor badge.
  • Merge Review: sidebyside justifications, conflicts, deterministic decision summary.
  • Scoring Gate: replay bar (ratio), provenance checklist.
  • Triage (unknown): prioritized queue with “missing evidence” chips and oneclick requests.

Dropin for StellaOps modules

  • Concelier: orchestrates gates.
  • Attestor: DSSE wrap/verify.
  • EvidenceLocker: storage + provenance.
  • AdvisoryAI: explanation/triage suggestions (surfacing unknowns first).
  • ReleaseOrchestrator: policy “block if unknown>0 and critical path”.

If you want, I can generate:

  • a ready OpenAPI spec for the 4 endpoints,
  • the DSSE/Rekor wiring stubs (C#) and a minimal SQLite/Postgres schema,
  • a Playwright test suite implementing the five acceptance tests.