5.1 KiB
5.1 KiB
Here’s a compact, plug‑and‑play blueprint for making “unknown” a first‑class, auditable state in your VEX pipeline (fits Stella Ops 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 tie‑break.
- Carry a provenance bundle with every hop:
{source_id, timestamp, proof_hash}. - Every merged outcome and score is DSSE‑wrapped and Rekor‑anchored.
Four readiness gates (fail fast)
-
ingest_validation
- Schema‑valid OpenVEX/CSAF + DSSE envelope present.
- Reject →
unvalidated(record reasons).
-
proof_anchor
- Rekor entry (UUID) + inclusion proof persisted.
- Reject if no inclusion proof or log not reachable (offline mode: queue + mark
unknown).
-
merge_precheck
- Deterministic timestamp precedence; evidence sufficiency (at least one attestation + SBOM ref).
- Reject if conflicts unresolved → stay at
proof_anchoredand set target outcomeunknown.
-
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 → byte‑identical 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/air‑gap: stage to local transparency log; when online, bridge to Rekor and backfill inclusion proofs.
Scoring model (example)
- Base score source (e.g., CVSS/CVSS‑SR).
- Dampener if outcome=
unknown: apply policy (e.g., cap at 6.9 or bump to triage queue). - Require
replay_success_ratio ≥ thresholdandprovenance.complete=truebefore emitting scores.
Acceptance tests (must‑pass)
-
missing_evidence_defaults_unknown
- Ingest CSAF missing justification → merged outcome is
unknownwith rationale.
- Ingest CSAF missing justification → merged outcome is
-
dsse_anchor_presence
- Attempt score without Rekor inclusion → gate fails.
-
timestamp_precedence_tiebreak
- Two equal timestamps from different sources → lexicographic
source_iddecides, deterministic.
- Two equal timestamps from different sources → lexicographic
-
merge_idempotence
- Re‑run merge with same inputs → identical
dsse_signed_mergedhash.
- Re‑run merge with same inputs → identical
-
scoring_gate_replay_success
- Corrupt one signature in replay set →
replay_success_ratiodrops; scoring blocked.
- Corrupt one signature in replay set →
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: side‑by‑side justifications, conflicts, deterministic decision summary.
- Scoring Gate: replay bar (ratio), provenance checklist.
- Triage (unknown): prioritized queue with “missing evidence” chips and one‑click requests.
Drop‑in for Stella Ops 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.