add advisories

This commit is contained in:
master
2025-12-01 17:50:11 +02:00
parent c11d87d252
commit 790801f329
7 changed files with 3723 additions and 0 deletions

View File

@@ -0,0 +1,576 @@
Heres a tight, practical blueprint to turn your SBOM→VEX links into an auditable “proof spine”—using signed DSSE statements and a perdependency trust anchor—so every VEX verdict can be traced, verified, and replayed.
# What this gives you
* A **chain of evidence** from each SBOM entry → analysis → VEX verdict.
* **Tamperevident** DSSEsigned records (offlinefriendly).
* **Deterministic replay**: same inputs → same verdicts (great for audits/regulators).
# Core objects (canonical IDs)
* **ArtifactID**: digest of package/container (e.g., `sha256:…`).
* **SBOMEntryID**: stable ID for a component in an SBOM (`sbomDigest:package@version[:purl]`).
* **EvidenceID**: hash of raw evidence (scanner JSON, reachability, exploit intel).
* **ReasoningID**: hash of normalized reasoning (rules/lattice inputs used).
* **VEXVerdictID**: hash of the final VEX statement body.
* **ProofBundleID**: merkle root of {SBOMEntryID, EvidenceID[], ReasoningID, VEXVerdictID}.
* **TrustAnchorID**: perdependency anchor (public key + policy) used to validate the above.
# Signed DSSE envelopes youll produce
1. **Evidence Statement** (per evidence item)
* `subject`: SBOMEntryID
* `predicateType`: `evidence.stella/v1`
* `predicate`: source, tool version, timestamps, EvidenceID
* **Signers**: scanner/ingestor key
2. **Reasoning Statement**
* `subject`: SBOMEntryID
* `predicateType`: `reasoning.stella/v1` (your lattice/policy inputs + ReasoningID)
* **Signers**: “Policy/Lattice Engine” key (Authority)
3. **VEX Verdict Statement**
* `subject`: SBOMEntryID
* `predicateType`: CycloneDX or CSAF VEX body + VEXVerdictID
* **Signers**: VEXer key (or vendor key if you have it)
4. **Proof Spine Statement** (the spine itself)
* `subject`: SBOMEntryID
* `predicateType`: `proofspine.stella/v1`
* `predicate`: EvidenceID[], ReasoningID, VEXVerdictID, ProofBundleID
* **Signers**: Authority key
# Trust model (perdependency anchor)
* **TrustAnchor** (per package/purl): { TrustAnchorID, allowed signers (KMS refs, PKs), accepted predicateTypes, policy version, revocation list }.
* Store anchors in **Authority** and pin them in your graph by SBOMEntryID→TrustAnchorID.
* Optional: PQC mode (Dilithium/Falcon) for longterm archives.
# Verification pipeline (deterministic)
1. Resolve SBOMEntryID → TrustAnchorID.
2. Verify every DSSE envelopes signature **against the anchors allowed keys**.
3. Recompute EvidenceID/ReasoningID/VEXVerdictID from raw content; compare hashes.
4. Recompute ProofBundleID (merkle root) and compare to the spine.
5. Emit a **Receipt**: {ProofBundleID, verification log, tool digests}. Cache it.
# Storage layout (Postgres + blob store)
* `sbom_entries(entry_id PK, bom_digest, purl, version, artifact_digest, trust_anchor_id)`
* `dsse_envelopes(env_id PK, entry_id, predicate_type, signer_keyid, body_hash, envelope_blob_ref, signed_at)`
* `spines(entry_id PK, bundle_id, evidence_ids[], reasoning_id, vex_id, anchor_id, created_at)`
* `trust_anchors(anchor_id PK, purl_pattern, allowed_keyids[], policy_ref, revoked_keys[])`
* Blobs (immutable): raw evidence, normalized reasoning JSON, VEX JSON, DSSE bytes.
# API surface (clean and small)
* `POST /proofs/:entry/spine` → submit or update spine (idempotent by ProofBundleID)
* `GET /proofs/:entry/receipt` → full verification receipt (JSON)
* `GET /proofs/:entry/vex` → the verified VEX body
* `GET /anchors/:anchor` → fetch trust anchor (for offline kits)
# Normalization rules (so hashes are stable)
* Canonical JSON (UTF8, sorted keys, no insignificant whitespace).
* Strip volatile fields (timestamps that arent part of the semantic claim).
* Version your schemas: `evidence.stella/v1`, `reasoning.stella/v1`, etc.
# Signing keys & rotation
* Keep keys in your **Authority** module (KMS/HSM; offline export for airgap).
* Publish key material via an **attestation feed** (or Rekormirror) for thirdparty audit.
* Rotate by **adding** new allowed_keyids in the TrustAnchor; never mutate old envelopes.
# CI/CD hooks
* On SBOM ingest → create/refresh SBOMEntry rows + attach TrustAnchor.
* On scan completion → produce Evidence Statements (DSSE) immediately.
* On policy evaluation → produce Reasoning + VEX, then assemble Spine.
* Gate releases on `GET /proofs/:entry/receipt` == PASS.
# UX (auditorfriendly)
* **Proof timeline** per entry: SBOM → Evidence tiles → Reasoning → VEX → Receipt.
* Oneclick “Recompute & Compare” to show deterministic replay passes.
* Red/amber flags when a signature no longer matches a TrustAnchor or a key is revoked.
# Minimal dev checklist
* [ ] Implement canonicalizers (Evidence, Reasoning, VEX).
* [ ] Implement DSSE sign/verify (ECDSA + optional PQC).
* [ ] TrustAnchor registry + resolver by purl pattern.
* [ ] Merkle bundling to get ProofBundleID.
* [ ] Receipt generator + verifier.
* [ ] Postgres schema + blob GC (contentaddressed).
* [ ] CI gates + API endpoints above.
* [ ] Auditor UI: timeline + diff + receipts download.
If you want, I can drop in a readytouse JSON schema set (`evidence.stella/v1`, `reasoning.stella/v1`, `proofspine.stella/v1`) and sample DSSE envelopes wired to your .NET 10 stack.
Heres a focused **Stella Ops Developer Guidelines** doc, specifically for the pipeline that turns **SBOM data into verifiable proofs** (your SBOM → Evidence → Reasoning → VEX → Proof Spine).
Feel free to paste this into your internal handbook and tweak names to match your repos/services.
---
# Stella Ops Developer Guidelines
## Turning SBOM Data Into Verifiable Proofs
---
## 1. Mental Model: What Youre Actually Building
For every component in an SBOM, Stella must be able to answer, *“Why should anyone trust our VEX verdict for this dependency, today and ten years from now?”*
We do that with a pipeline:
1. **SBOM Ingest**
Raw SBOM → validated → normalized → `SBOMEntryID`.
2. **Evidence Collection**
Scans, feeds, configs, reachability, etc. → canonical evidence blobs → `EvidenceID` → DSSE-signed.
3. **Reasoning / Policy**
Policy + evidence → deterministic reasoning → `ReasoningID` → DSSE-signed.
4. **VEX Verdict**
VEX statement (CycloneDX / CSAF) → canonicalized → `VEXVerdictID` → DSSE-signed.
5. **Proof Spine**
`{SBOMEntryID, EvidenceIDs[], ReasoningID, VEXVerdictID}` → merkle bundle → `ProofBundleID` → DSSE-signed.
6. **Verification & Receipts**
Re-run verification → `Receipt` that proves everything above is intact and anchored to trusted keys.
Everything you do in this area should keep this spine intact and verifiable.
---
## 2. NonNegotiable Invariants
These are the rules you dont break without an explicit, company-level decision:
1. **Immutability of Signed Facts**
* DSSE envelopes (evidence, reasoning, VEX, spines) are appendonly.
* You never edit or delete content inside a previously signed envelope.
* Corrections are made by **superseding** (new statement pointing at the old one).
2. **Determinism**
* Same `{SBOMEntryID, Evidence set, policyVersion}` ⇒ same `{ReasoningID, VEXVerdictID, ProofBundleID}`.
* No non-deterministic inputs (e.g., “current time”, random IDs) in anything that affects IDs or verdicts.
3. **Traceability**
* Every VEX verdict must be traceable back to:
* The precise SBOM entry
* Concrete evidence blobs
* A specific policy & reasoning snapshot
* A trust anchor defining allowed signers
4. **Least Trust / Least Privilege**
* Services only know the keys and data they need.
* Trust is always explicit: through **TrustAnchors** and signature verification, never “because its in our DB”.
5. **Backwards Compatibility**
* New code must continue to verify **old proofs**.
* New policies must **not rewrite history**; they produce *new* spines, leaving old ones intact.
---
## 3. SBOM Ingestion Guidelines
**Goal:** Turn arbitrary SBOMs into stable, addressable `SBOMEntryID`s and safe internal models.
### 3.1 Inputs & Formats
* Support at least:
* CycloneDX (JSON)
* SPDX (JSON / Tag-Value)
* For each ingested SBOM, store:
* Raw SBOM bytes (immutable, content-addressed)
* A normalized internal representation (your own model)
### 3.2 IDs
* Generate:
* `sbomDigest` = hash(raw SBOM, canonical form)
* `SBOMEntryID` = `sbomDigest + purl + version` (or equivalent stable tuple)
* `SBOMEntryID` must:
* Not depend on ingestion time or database IDs.
* Be reproducible from SBOM + deterministic normalization.
### 3.3 Validation & Errors
* Validate:
* Syntax (JSON, schema)
* Core semantics (package identifiers, digests, versions)
* If invalid:
* Reject the SBOM **but** record a small DSSE “failure attestation” explaining:
* Why it failed
* Which file
* Which system version
* This still gives you a proof trail for “we tried and it failed”.
---
## 4. Evidence Collection Guidelines
**Goal:** Capture all inputs that influence the verdict in a canonical, signed form.
Typical evidence types:
* SCA / vuln scanner results
* CVE feeds & advisory data
* Reachability / call graph analysis
* Runtime context (where this component is used)
* Manual assessments (e.g., security engineer verdicts)
### 4.1 Evidence Canonicalization
For every evidence item:
* Normalize to a schema like `evidence.stella/v1` with fields such as:
* `source` (scanner name, feed)
* `sourceVersion` (tool version, DB version)
* `collectionTime`
* `sbomEntryId`
* `vulnerabilityId` (if applicable)
* `rawFinding` (or pointer to it)
* Canonical JSON rules:
* Sorted keys
* UTF8, no extraneous whitespace
* No volatile fields beyond whats semantically needed (e.g., you might include `collectionTime`, but then know it affects the hash and treat that consciously).
Then:
* Compute `EvidenceID = hash(canonicalEvidenceJson)`.
* Wrap in DSSE:
* `subject`: `SBOMEntryID`
* `predicateType`: `evidence.stella/v1`
* `predicate`: canonical evidence + `EvidenceID`.
* Sign with **evidence-ingestor key** (per environment).
### 4.2 Ops Rules
* **Idempotency:**
Re-running the same scan with same inputs should produce the same evidence object and `EvidenceID`.
* **Tool changes:**
When tool version or configuration changes, thats a *new* evidence statement with a new `EvidenceID`. Do not overwrite old evidence.
* **Partial failure:**
If a scan fails, produce a minimal failure evidence record (with error details) instead of “nothing”.
---
## 5. Reasoning & Policy Engine Guidelines
**Goal:** Turn evidence into a defensible, replayable reasoning step with a clear policy version.
### 5.1 Reasoning Object
Define a canonical reasoning schema, e.g. `reasoning.stella/v1`:
* `sbomEntryId`
* `evidenceIds[]` (sorted)
* `policyVersion`
* `inputs`: normalized form of all policy inputs (severity thresholds, lattice rules, etc.)
* `intermediateFindings`: optional but useful — e.g., “reachable vulns = …”
Then:
* Canonicalize JSON and compute `ReasoningID = hash(canonicalReasoning)`.
* Wrap in DSSE:
* `subject`: `SBOMEntryID`
* `predicateType`: `reasoning.stella/v1`
* `predicate`: canonical reasoning + `ReasoningID`.
* Sign with **Policy/Authority key**.
### 5.2 Determinism
* Reasoning functions must be **pure**:
* Inputs: SBOMEntryID, evidence set, policy version, configuration.
* No hidden calls to external APIs at decision time (fetch feeds earlier and record them as evidence).
* If you need “current time” in policy:
* Treat it as **explicit input** and record it inside reasoning under `inputs.currentEvaluationTime`.
### 5.3 Policy Evolution
* When policy changes:
* Bump `policyVersion`.
* New evaluations produce new `ReasoningID` and new VEX/spines.
* Dont retroactively apply new policy to old reasoning objects; generate new ones alongside.
---
## 6. VEX Verdict Guidelines
**Goal:** Generate VEX statements that are strongly tied to SBOM entries and your reasoning.
### 6.1 Shape
* Target standard formats:
* CycloneDX VEX
* or CSAF
* Required linkages:
* Component reference = `SBOMEntryID` or a resolvable component identifier from your SBOM normalize layer.
* Vulnerability IDs (CVE, GHSA, internal IDs).
* Status (`not_affected`, `affected`, `fixed`, etc.).
* Justification & impact.
### 6.2 Canonicalization & Signing
* Define a canonical VEX body schema (subset of the standard + internal metadata):
* `sbomEntryId`
* `vulnerabilityId`
* `status`
* `justification`
* `policyVersion`
* `reasoningId`
* Canonicalize JSON → `VEXVerdictID = hash(canonicalVexBody)`.
* DSSE-envelope:
* `subject`: `SBOMEntryID`
* `predicateType`: e.g. `cdx-vex.stella/v1`
* `predicate`: canonical VEX + `VEXVerdictID`.
* Sign with **VEXer key** or vendor key (depending on trust anchor).
### 6.3 External VEX
* When importing vendor VEX:
* Verify signature against vendors TrustAnchor.
* Canonicalize to your internal schema but preserve:
* Original document
* Original signature material
* Record “source = vendor” vs “source = stella” so auditors see origin.
---
## 7. Proof Spine Guidelines
**Goal:** Build a compact, tamper-evident “bundle” that ties everything together.
### 7.1 Structure
For each `SBOMEntryID`, gather:
* `EvidenceIDs[]` (sorted lexicographically).
* `ReasoningID`.
* `VEXVerdictID`.
Compute:
* Merkle tree root (or deterministic hash) over:
* `sbomEntryId`
* sorted `EvidenceIDs[]`
* `ReasoningID`
* `VEXVerdictID`
* Result is `ProofBundleID`.
Create a DSSE “spine”:
* `subject`: `SBOMEntryID`
* `predicateType`: `proofspine.stella/v1`
* `predicate`:
* `evidenceIds[]`
* `reasoningId`
* `vexVerdictId`
* `policyVersion`
* `proofBundleId`
* Sign with **Authority key**.
### 7.2 Ops Rules
* Spine generation is idempotent:
* Same inputs → same `ProofBundleID`.
* Never mutate existing spines; new policy or new evidence ⇒ new spine.
* Keep a clear API contract:
* `GET /proofs/:entry` returns **all** spines, each labeled with `policyVersion` and timestamps.
---
## 8. Storage & Schema Guidelines
**Goal:** Keep proofs queryable forever without breaking verification.
### 8.1 Tables (conceptual)
* `sbom_entries`: `entry_id`, `bom_digest`, `purl`, `version`, `artifact_digest`, `trust_anchor_id`.
* `dsse_envelopes`: `env_id`, `entry_id`, `predicate_type`, `signer_keyid`, `body_hash`, `envelope_blob_ref`, `signed_at`.
* `spines`: `entry_id`, `proof_bundle_id`, `policy_version`, `evidence_ids[]`, `reasoning_id`, `vex_verdict_id`, `anchor_id`, `created_at`.
* `trust_anchors`: `anchor_id`, `purl_pattern`, `allowed_keyids[]`, `policy_ref`, `revoked_keys[]`.
### 8.2 Schema Changes
Always follow:
1. **Expand**
* Add new columns/tables.
* Make new code tolerant of old data.
2. **Backfill**
* Idempotent jobs that fill in new IDs/fields without touching old DSSE payloads.
3. **Contract**
* Only after all code uses the new model.
* Never drop the raw DSSE or raw SBOM blobs.
---
## 9. Verification & Receipts
**Goal:** Make it trivial (for you, customers, and regulators) to recheck everything.
### 9.1 Verification Flow
Given `SBOMEntryID` or `ProofBundleID`:
1. Fetch spine and trust anchor.
2. Verify:
* Spine DSSE signature against TrustAnchors allowed keys.
* VEX, reasoning, and evidence DSSE signatures.
3. Recompute:
* `EvidenceIDs` from stored canonical evidence.
* `ReasoningID` from reasoning.
* `VEXVerdictID` from VEX body.
* `ProofBundleID` from the above.
4. Compare to stored IDs.
Emit a **Receipt**:
* `proofBundleId`
* `verifiedAt`
* `verifierVersion`
* `anchorId`
* `result` (pass/fail, with reasons)
### 9.2 Offline Kit
* Provide a minimal CLI (`stella verify`) that:
* Accepts a bundle export (SBOM + DSSE envelopes + anchors).
* Verifies everything without network access.
Developers must ensure:
* Export format is documented and stable.
* All fields required for verification are included.
---
## 10. Security & Key Management (for Devs)
* Keys live in **KMS/HSM**, not env vars or config files.
* Separate keysets:
* `dev`, `staging`, `prod`
* Authority vs VEXer vs Evidence Ingestor.
* TrustAnchors:
* Edit via Authority service only.
* Every change:
* Requires code-reviewed change.
* Writes an audit log entry.
Never:
* Log private keys.
* Log full DSSE envelopes in plaintext logs (log IDs and hashes instead).
---
## 11. Observability & OnCall Expectations
### 11.1 Metrics
For the SBOM→Proof pipeline, expose:
* `sboms_ingested_total`
* `sbom_ingest_errors_total{reason}`
* `evidence_statements_created_total`
* `reasoning_statements_created_total`
* `vex_statements_created_total`
* `proof_spines_created_total`
* `proof_verifications_total{result}` (pass/fail reason)
* Latency histograms per stage (`_duration_seconds`)
### 11.2 Logging
Include in structured logs wherever relevant:
* `sbomEntryId`
* `proofBundleId`
* `anchorId`
* `policyVersion`
* `requestId` / `traceId`
### 11.3 Runbooks
You should maintain runbooks for at least:
* “Pipeline is stalled” (backlog of SBOMs, evidence, or spines).
* “Verification failures increased”.
* “Trust anchor or key issues” (rotation, revocation, misconfiguration).
* “Backfill gone wrong” (how to safely stop, resume, and audit).
---
## 12. Dev Workflow & PR Checklist (SBOM→Proof Changes Only)
When your change touches SBOM ingestion, evidence, reasoning, VEX, or proof spines, check:
* [ ] IDs (`SBOMEntryID`, `EvidenceID`, `ReasoningID`, `VEXVerdictID`, `ProofBundleID`) remain **deterministic** and fully specified.
* [ ] No mutation of existing DSSE envelopes or historical proof data.
* [ ] Schema changes follow **expand → backfill → contract**.
* [ ] New/updated TrustAnchors reviewed by Authority owner.
* [ ] Unit tests cover:
* Canonicalization for any new/changed predicate.
* ID computation.
* [ ] Integration test covers:
* SBOM → Evidence → Reasoning → VEX → Spine → Verification → Receipt.
* [ ] Observability updated:
* New paths emit logs & metrics.
* [ ] Rollback plan documented (especially for migrations & policy changes).
---
If you tell me which microservices/repos map to these stages (e.g. `stella-sbom-ingest`, `stella-proof-authority`, `stella-vexer`), I can turn this into a more concrete, servicebyservice checklist with example API contracts and class/interface sketches.