feat: Implement distro-native version comparison for RPM, Debian, and Alpine packages

- Add RpmVersionComparer for RPM version comparison with epoch, version, and release handling.
- Introduce DebianVersion for parsing Debian EVR (Epoch:Version-Release) strings.
- Create ApkVersion for parsing Alpine APK version strings with suffix support.
- Define IVersionComparator interface for version comparison with proof-line generation.
- Implement VersionComparisonResult struct to encapsulate comparison results and proof lines.
- Add tests for Debian and RPM version comparers to ensure correct functionality and edge case handling.
- Create project files for the version comparison library and its tests.
This commit is contained in:
StellaOps Bot
2025-12-22 09:49:38 +02:00
parent aff0ceb2fe
commit df94136727
111 changed files with 30413 additions and 1813 deletions

View File

@@ -1,751 +0,0 @@
Heres a practical, firsttimefriendly blueprint for making your security workflow both **explainable** and **provable**—from triage to approval.
# Explainable triage UX (what & why)
Show every risk score with the minimum evidence a responder needs to trust it:
* **Reachable path:** the concrete callchain (or network path) proving the vuln is actually hit.
* **Entrypoint boundary:** the external surface (HTTP route, CLI verb, cron, message topic) that leads to that path.
* **VEX status:** the exploitability decision (Affected/Not Affected/Under Investigation/Fixed) with rationale.
* **Lastseen timestamp:** when this evidence was last observed/generated.
## UI pattern (compact, 1click expand)
* **Row (collapsed):** `Score 72 • CVE202412345 • service: api-gateway • package: x.y.z`
* **Expand panel (evidence):**
* **Path:** `POST /billing/charge → BillingController.Pay() → StripeClient.Create()`
* **Boundary:** `Ingress: /billing/charge (JWT: required, scope: payments:write)`
* **VEX:** `Not Affected (runtime guard strips untrusted input before sink)`
* **Last seen:** `20251218T09:22Z` (scan: sbomer#c1a2, policy run: lattice#9f0d)
* **Actions:** “Open proof bundle”, “Re-run check”, “Create exception (timeboxed)”
## Data contract (what the panel needs)
```json
{
"finding_id": "f-7b3c",
"cve": "CVE-2024-12345",
"component": {"name": "stripe-sdk", "version": "6.1.2"},
"reachable_path": [
"HTTP POST /billing/charge",
"BillingController.Pay",
"StripeClient.Create"
],
"entrypoint": {"type":"http","route":"/billing/charge","auth":"jwt:payments:write"},
"vex": {"status":"not_affected","justification":"runtime_sanitizer_blocks_sink","timestamp":"2025-12-18T09:22:00Z"},
"last_seen":"2025-12-18T09:22:00Z",
"attestation_refs": ["sha256:…sbom", "sha256:…vex", "sha256:…policy"]
}
```
# Evidencelinked approvals (what & why)
Make “Approve to ship” contingent on **verifiable proof**, not screenshots:
* **Chain** must exist and be machineverifiable: **SBOM → VEX → policy decision**.
* Use **intoto/DSSE** attestations or **SLSA provenance** so each link has a signature, subject digest, and predicate.
* **Gate** merges/deploys only when the chain validates.
## Pipeline gate (simple policy)
* Require:
1. **SBOM attestation** referencing the exact image digest
2. **VEX attestation** covering all listed components (or explicit allowgaps)
3. **Policy decision attestation** (e.g., “risk ≤ threshold AND all reachable vulns = Not Affected/Fixed”)
### Minimal decision attestation (DSSE envelope → JSON payload)
```json
{
"predicateType": "stella/policy-decision@v1",
"subject": [{"name":"registry/org/app","digest":{"sha256":"<image-digest>"}}],
"predicate": {
"policy": "risk_threshold<=75 && reachable_vulns.all(v => v.vex in ['not_affected','fixed'])",
"inputs": {
"sbom_ref": "sha256:<sbom>",
"vex_ref": "sha256:<vex>"
},
"result": {"allowed": true, "score": 61, "exemptions":[]},
"evidence_refs": ["sha256:<reachability-proof-bundle>"],
"run_at": "2025-12-18T09:23:11Z"
}
}
```
# How this lands in your product (concrete moves)
* **Backend:** add `/findings/:id/evidence` (returns the contract above) + `/approvals/:artifact/attestations`.
* **Storage:** keep **proof bundles** (graphs, call stacks, logs) as contentaddressed blobs; store DSSE envelopes alongside.
* **UI:** one list → expandable rows; chips for VEX status; “Open proof” shows the call graph and boundary in 1 view.
* **CLI/API:** `stella verify image:<digest> --require sbom,vex,decision` returns a signed summary; pipelines fail on nonzero.
* **Metrics:**
* **% changes with complete attestations** (target ≥95%)
* **TTFE (timetofirstevidence)** from alert → panel open (target ≤30s)
* **Postdeploy reversions** due to missing proof (trend to zero)
# Starter acceptance checklist
* [ ] Every risk row expands to path, boundary, VEX, lastseen in <300ms.
* [ ] Approve button disabled until SBOM+VEX+Decision attestations validate for the **exact artifact digest**.
* [ ] Oneclick Show DSSE chain renders the three envelopes with subject digests and signers.
* [ ] Audit log captures who approved, which digests, and which evidence hashes.
If you want, I can turn this into readytodrop **.NET 10** endpoints + a small React panel with mocked data so your team can wire it up fast.
Below is a buildit guide for Stella Ops that goes past the concept level: concrete services, schemas, pipelines, signing/storage choices, UI components, and the exact invariants you should enforce so triage is **explainable** and approvals are **provably evidencelinked**.
---
## 1) Start with the invariants (the rules your system must never violate)
If you implement nothing else, implement these invariantstheyre what make the UX trustworthy and the approvals auditable.
### Artifact anchoring invariant
Every finding, every piece of evidence, and every approval must be anchored to an immutable **subject digest** (e.g., container image digest `sha256:…`, binary SHA, or SBOM digest).
* No latest tag approvals.
* No approve commit without mapping to the built artifact digest.
### Evidence closure invariant
A policy decision is only valid if it references **exactly** the evidence it used:
* `inputs.sbom_ref`
* `inputs.vex_ref`
* `inputs.reachability_ref` (optional but recommended)
* `inputs.scan_ref` (optional)
* and any config/IaC refs used for boundary/exposure.
### Signature chain invariant
Evidence is only admissible if it is:
1. structured (machine readable),
2. signed (DSSE/intoto),
3. verifiable (trusted identity/keys),
4. retrievable by digest.
DSSE is specifically designed to authenticate both the message and its type (payload type) and avoid canonicalization pitfalls. ([GitHub][1])
### Staleness invariant
Evidence must have:
* `last_seen` and `expires_at` (or TTL),
* a stale evidence behavior in policy (deny or degrade score).
---
## 2) Choose the canonical formats and where youll store “proof”
### Attestation envelope: DSSE + intoto Statement
Use:
* **intoto Attestation Framework** Statement as the payload model (“subject + predicateType + predicate”). ([GitHub][2])
* Wrap it in **DSSE** for signing. ([GitHub][1])
* If you use Sigstore bundles, the DSSE envelope is expected to carry an intoto statement and uses `payloadType` like `application/vnd.in-toto+json`. ([Sigstore][3])
### SBOM format: CycloneDX or SPDX
* SPDX is an ISO/IEC standard and has v3.0 and v2.3 lines in the ecosystem. ([spdx.dev][4])
* CycloneDX is an ECMA standard (ECMA424) and widely used for application security contexts. ([GitHub][5])
Pick one as **your canonical** (internally), but ingest both.
### VEX format: OpenVEX (practical) + map to “classic” VEX statuses
VEXs value is triage noise reduction: vendors can assert whether a product is affected, fixed, under investigation, or not affected. ([NTIA][6])
OpenVEX is a minimal, embeddable implementation of VEX intended for interoperability. ([GitHub][7])
### Where to store proof: OCI registry referrers
Use OCI subject/referrers so proofs travel with the artifact:
* OCI 1.1 introduces an explicit `subject` field and referrers graph for signatures/attestations/SBOMs. ([opencontainers.org][8])
* ORAS documentation explains linking artifacts via `subject`. ([Oras][9])
* Microsoft docs show `oras attach … --artifact-type …` patterns (works across registries that support referrers). ([Microsoft Learn][10])
---
## 3) System architecture (services + data flow)
### Services (minimum set)
1. **Ingestor**
* Pulls scanner outputs (SCA/SAST/IaC), SBOM, runtime signals.
2. **Evidence Builder**
* Computes reachability, entrypoints, boundary/auth context, score explanation.
3. **Attestation Service**
* Creates intoto statements, wraps DSSE, signs (cosign/KMS), stores to registry.
4. **Policy Engine**
* Evaluates allow/deny + reason codes, emits signed decision attestation.
* Use OPA/Rego for maintainable declarative policies. ([openpolicyagent.org][11])
5. **Stella Ops API**
* Serves findings + evidence panels to the UI (fast, cached).
6. **UI**
* Explainable triage panel + chain viewer + approve button.
### Event flow (artifactcentric)
1. Build produces `image@sha256:X`
2. Generate SBOM sign + attach
3. Run vuln scan sign + attach (optional but useful)
4. Evidence Builder creates:
* reachability proof
* boundary proof
* vex doc (or imports vendor VEX + adds your context)
5. Policy engine evaluates emits decision attestation
6. UI shows explainable triage + approve gating
---
## 4) Data model (the exact objects you need)
### Core IDs you should standardize
* `subject_digest`: `sha256:<image digest>`
* `subject_name`: `registry/org/app`
* `finding_key`: `(subject_digest, detector, cve, component_purl, location)` stable hash
* `component_purl`: package URL (PURL) canonical component identifier
### Tables (Postgres suggested)
**artifacts**
* `id (uuid)`
* `name`
* `digest` (unique)
* `created_at`
**findings**
* `id (uuid)`
* `artifact_digest`
* `cve`
* `component_purl`
* `severity`
* `raw_score`
* `risk_score`
* `status` (open/triaged/accepted/fixed)
* `first_seen`, `last_seen`
**evidence**
* `id (uuid)`
* `finding_id`
* `kind` (reachable_path | boundary | score_explain | vex | ...)
* `payload_json` (jsonb, small)
* `blob_ref` (content-addressed URI for big payloads)
* `last_seen`
* `expires_at`
* `confidence` (01)
* `source_attestation_digest` (nullable)
**attestations**
* `id (uuid)`
* `artifact_digest`
* `predicate_type`
* `attestation_digest` (sha256 of DSSE envelope)
* `signer_identity` (OIDC subject / cert identity)
* `issued_at`
* `registry_ref` (where attached)
**approvals**
* `id (uuid)`
* `artifact_digest`
* `decision_attestation_digest`
* `approver`
* `approved_at`
* `expires_at`
* `reason`
---
## 5) Explainable triage: how to compute the “Path + Boundary + VEX + Lastseen”
### 5.1 Reachable path proof (call chain / flow)
You need a uniform reachability result type:
* `reachable = true` with an explicit path
* `reachable = false` with justification (e.g., symbol absent, dead code)
* `reachable = unknown` with reason (insufficient symbols, dynamic dispatch)
**Implementation strategy**
1. **Symbol mapping**: map CVE vulnerable symbols/functions/classes
* Use one or more:
* vendor advisory patched functions
* diff mining (commit that fixes CVE) to extract changed symbols
* curated mapping in your DB for high volume CVEs
2. **Program graph extraction** at build time:
* Produce a call graph or dependency graph per language.
* Store as compact adjacency list (or protobuf) keyed by `subject_digest`.
3. **Entrypoint discovery**:
* HTTP routes (framework metadata)
* gRPC service methods
* queue/stream consumers
* cron/CLI handlers
4. **Path search**:
* BFS/DFS from entrypoints to vulnerable symbols.
* Record the shortest path + topK alternatives.
5. **Proof bundle**:
* path nodes with stable IDs
* file hashes + line ranges (no raw source required)
* tool version + config hash
* graph digest
**Reachability evidence JSON (UIfriendly)**
```json
{
"kind": "reachable_path",
"result": "reachable",
"confidence": 0.86,
"entrypoints": [
{"type":"http","route":"POST /billing/charge","auth":"jwt:payments:write"}
],
"paths": [{
"path_id": "p-1",
"steps": [
{"node":"BillingController.Pay","file_hash":"sha256:aaa","lines":[41,88]},
{"node":"StripeClient.Create","file_hash":"sha256:bbb","lines":[10,52]},
{"node":"stripe-sdk.vulnFn","symbol":"stripe-sdk::parseWebhook","evidence":"symbol-match"}
]
}],
"graph": {"digest":"sha256:callgraph...", "format":"stella-callgraph-v1"},
"last_seen": "2025-12-18T09:22:00Z",
"expires_at": "2025-12-25T09:22:00Z"
}
```
**UI rule:** never show reachable without a concrete, replayable path ID.
---
### 5.2 Boundary proof (the “why this is exposed” part)
Boundary proof answers: Even if reachable, who can trigger it?”
**Data sources**
* Kubernetes ingress/service (exposure)
* API gateway routes and auth policies
* service mesh auth (mTLS, JWT)
* IAM policies (for cloud events)
* network policies (deny/allow)
**Boundary evidence schema**
```json
{
"kind": "boundary",
"surface": {"type":"http","route":"POST /billing/charge"},
"exposure": {"internet": true, "ports":[443]},
"auth": {
"mechanism":"jwt",
"required_scopes":["payments:write"],
"audience":"billing-api"
},
"rate_limits": {"enabled": true, "rps": 20},
"controls": [
{"type":"waf","status":"enabled"},
{"type":"input_validation","status":"enabled","location":"BillingController.Pay"}
],
"last_seen": "2025-12-18T09:22:00Z",
"confidence": 0.74
}
```
**How to build it**
* Create a Surface Extractor plugin per environment:
* `k8s-extractor`: reads ingress + service + annotations
* `gateway-extractor`: reads API gateway config
* `iac-extractor`: parses Terraform/CloudFormation
* Normalize into the schema above.
---
### 5.3 VEX in Stella: statuses + justifications
VEX statuses you should support in UI:
* Not affected
* Affected
* Fixed
* Under investigation ([NTIA][6])
OpenVEX will carry the machine readable structure. ([GitHub][7])
**Practical approach**
* Treat VEX as **the decision record** for exploitability.
* Your policy can require VEX coverage for all reachable high severity vulns.
**Rule of thumb**
* If `reachable=true` AND boundary shows reachable surface + auth weak VEX defaults to `affected` until mitigations proven.
* If `reachable=false` with high confidence and stable proof VEX may be `not_affected`.
---
### 5.4 Explainable risk score (dont hide the formula)
Make score explainability firstclass.
**Recommended implementation**
* Store risk score as an additive model:
* `base = CVSS normalized`
* `+ reachability_bonus`
* `+ exposure_bonus`
* `+ privilege_bonus`
* `- mitigation_discount`
* Emit a `score_explain` evidence object:
```json
{
"kind": "score_explain",
"risk_score": 72,
"contributions": [
{"factor":"cvss","value":41,"reason":"CVSS 9.8"},
{"factor":"reachability","value":18,"reason":"reachable path p-1"},
{"factor":"exposure","value":10,"reason":"internet-facing route"},
{"factor":"auth","value":3,"reason":"scope required lowers impact"}
],
"last_seen":"2025-12-18T09:22:00Z"
}
```
**UI rule:** Score 72 must always be clickable to a stable breakdown.
---
## 6) The UI you should build (components + interaction rules)
### 6.1 Findings list row (collapsed)
Show only what helps scanning:
* Score badge
* CVE + component
* service
* reachability chip: Reachable / Not reachable / Unknown
* VEX chip
* last_seen indicator (green/yellow/red)
### 6.2 Evidence drawer (expanded)
Tabs:
1. **Path**
* show entrypoint(s)
* render call chain (simple list first; graph view optional)
2. **Boundary**
* exposure, auth, controls
3. **VEX**
* status + justification + issuer identity
4. **Score**
* breakdown bar/list
5. **Proof**
* attestation chain viewer (SBOM VEX Decision)
* Verify locally action
### 6.3 “Open proof bundle” viewer
Must display:
* subject digest
* signer identity
* predicate type
* digest of proof bundle
* last_seen + tool versions
**This is where trust is built:** responders can see that the evidence is signed, tied to the artifact, and recent.
---
## 7) Prooflinked evidence: how to generate and attach attestations
### 7.1 Statement format: intoto Attestation Framework
intotos model is:
* **Subjects** (the artifact digests)
* **Predicate type** (schema ID)
* **Predicate** (your actual data) ([GitHub][2])
### 7.2 DSSE envelope
Wrap statements using DSSE so payload type is signed too. ([GitHub][1])
### 7.3 Attach to OCI image via referrers
OCI subject/referrers makes attestations discoverable from the image digest. ([opencontainers.org][8])
ORAS provides the operational model (“attach artifacts to an image”). ([Microsoft Learn][10])
### 7.4 Practical signing: cosign attest + verify
Cosign has builtin intoto attestation support and can sign custom predicates. ([Sigstore][12])
Typical patterns (example only; adapt to your environment):
```bash
# Attach an attestation
cosign attest --predicate reachability.json \
--type stella/reachability/v1 \
<image@sha256:digest>
# Verify attestation
cosign verify-attestation --type stella/reachability/v1 \
<image@sha256:digest>
```
(Use keyless OIDC or KMS keys depending on your org.)
---
## 8) Define your predicate types (this is the “contract” Stella enforces)
Youll want at least these predicate types:
1. `stella/sbom@v1`
* embeds CycloneDX/SPDX (or references blob digest)
2. `stella/vex@v1`
* embeds OpenVEX document or references it ([GitHub][7])
3. `stella/reachability@v1`
* the reachability evidence above
* includes `graph.digest`, `paths`, `confidence`, `expires_at`
4. `stella/boundary@v1`
* exposure/auth proof and `last_seen`
5. `stella/policy-decision@v1`
* the gating result, references all input attestation digests
6. Optional: `stella/human-approval@v1`
* I approve deploy of subject digest X based on decision attestation Y
* keep it timeboxed
---
## 9) The policy gate (how approvals become prooflinked)
### 9.1 Use OPA/Rego for the gate
OPA policies are written in Rego. ([openpolicyagent.org][11])
**Gate input** should be a single JSON document assembled from verified attestations:
```json
{
"subject": {"name":"registry/org/app","digest":"sha256:..."},
"sbom": {...},
"vex": {...},
"reachability": {...},
"boundary": {...},
"org_policy": {"max_risk": 75, "max_age_hours": 168}
}
```
**Example Rego (denybydefault)**
```rego
package stella.gate
default allow := false
# deny if evidence is stale
stale_evidence {
now := time.now_ns()
exp := time.parse_rfc3339_ns(input.reachability.expires_at)
now > exp
}
# deny if any high severity reachable vuln is not resolved by VEX
unresolved_reachable[v] {
v := input.reachability.findings[_]
v.severity in {"critical","high"}
v.reachable == true
not input.vex.resolution[v.cve] in {"not_affected","fixed"}
}
allow {
input.risk_score <= input.org_policy.max_risk
not stale_evidence
count(unresolved_reachable) == 0
}
```
### 9.2 Emit a signed policy decision attestation
When OPA returns `allow=true`, emit **another attestation**:
* predicate includes the policy version/hash and all input refs.
* thats what the UI Approve button targets.
This is the evidencelinked approval: approval references the signed decision, and the decision references the signed evidence.
---
## 10) “Approve” button behavior (what Stella Ops should enforce)
### Disabled until…
* subject digest known
* SBOM attestation found + signature verified
* VEX attestation found + signature verified
* Decision attestation found + signature verified
* Decisions `inputs` digests match the actual retrieved evidence
### When clicked…
1. Stella Ops creates a `stella/human-approval@v1` statement:
* `subject` = artifact digest
* `predicate.decision_ref` = decision attestation digest
* `predicate.expires_at` = short TTL (e.g., 730 days)
2. Signs it with the approver identity
3. Attaches it to the artifact (OCI referrer)
### Audit view must show
* approver identity
* exact artifact digest
* exact decision attestation digest
* timestamp and expiry
---
## 11) Implementation details that matter in production
### 11.1 Verification library (shared by UI backend + CI gate)
Write one verifier module used everywhere:
**Inputs**
* image digest
* expected predicate types
* trust policy (allowed identities/issuers, keyless rules, KMS keys)
**Steps**
1. Discover referrers for `image@sha256:…`
2. Filter by `predicateType`
3. Verify DSSE + signature + identity
4. Validate JSON schema for predicate
5. Check `subject.digest` matches image digest
6. Return verified evidence set + errors
### 11.2 Evidence privacy
Reachability proofs can leak implementation details.
* Store file hashes, symbol names, and line ranges
* Gate raw source behind elevated permissions
* Provide redacted proofs by default
### 11.3 Evidence TTL strategy
* SBOM: long TTL (weeks/months) if digest immutable
* Boundary: short TTL (hours/days) because env changes
* Reachability: medium TTL (days/weeks) depending on code churn
* VEX: must be renewed if boundary/reachability changes
### 11.4 Handling “Unknown reachability”
Dont force false certainty.
* Mark as `unknown` and show why (missing symbols, dynamic reflection, stripped binaries)
* Policy can treat unknown as reachable for critical CVEs in internetfacing services.
---
## 12) A concrete MVP path that still delivers value
If you want a minimal but real first release:
### MVP (23 deliverables)
1. **Evidence drawer** fed by:
* scanner output + SBOM + a simple entrypoint map
2. **VEX workflow**
* allow engineers to set VEX status + justification
3. **Signed decision gating**
* even if reachability is heuristic, the chain is real
Then iterate:
* add reachability graphs
* add boundary extraction from IaC/K8s
* tighten policy (staleness, confidence thresholds)
---
## 13) Quick checklist for “done enough to trust”
* [ ] Every finding expands to: Path, Boundary, VEX, Score, Proof
* [ ] Every evidence tab shows `last_seen` + confidence
* [ ] Verify chain works: SBOM VEX Decision all signed and bound to the artifact digest
* [ ] Approve button signs a human approval attestation tied to the decision digest
* [ ] CI gate verifies the same chain before deploy
---
If you want, I can also drop in:
* a full set of JSON Schemas for `stella/*@v1` predicates,
* a reference verifier implementation outline in .NET 10 (Minimal API + a verifier class),
* and a sample UI component tree (React) that renders path/boundary graphs and attestation chains.
[1]: https://github.com/secure-systems-lab/dsse?utm_source=chatgpt.com "DSSE: Dead Simple Signing Envelope"
[2]: https://github.com/in-toto/attestation?utm_source=chatgpt.com "in-toto Attestation Framework"
[3]: https://docs.sigstore.dev/about/bundle/?utm_source=chatgpt.com "Sigstore Bundle Format"
[4]: https://spdx.dev/use/specifications/?utm_source=chatgpt.com "Specifications"
[5]: https://github.com/CycloneDX/specification?utm_source=chatgpt.com "CycloneDX/specification"
[6]: https://www.ntia.gov/sites/default/files/publications/vex_one-page_summary_0.pdf "VEX one-page summary"
[7]: https://github.com/openvex/spec?utm_source=chatgpt.com "OpenVEX Specification"
[8]: https://opencontainers.org/posts/blog/2024-03-13-image-and-distribution-1-1/?utm_source=chatgpt.com "OCI Image and Distribution Specs v1.1 Releases"
[9]: https://oras.land/docs/concepts/reftypes/?utm_source=chatgpt.com "Attached Artifacts | OCI Registry As Storage"
[10]: https://learn.microsoft.com/en-us/azure/container-registry/container-registry-manage-artifact?utm_source=chatgpt.com "Manage OCI Artifacts and Supply Chain Artifacts with ORAS"
[11]: https://openpolicyagent.org/docs/policy-language?utm_source=chatgpt.com "Policy Language"
[12]: https://docs.sigstore.dev/cosign/verifying/attestation/?utm_source=chatgpt.com "In-Toto Attestations"

View File

@@ -0,0 +1,140 @@
Heres a tight, firsttimefriendly blueprint for two StellaOps UX pillars—**Triage & Exceptions** and **Knowledge Snapshots & Merge Semantics**—with just enough background plus concrete specs your PMs/devs can ship.
---
# Triage & Exceptions (quietbydesign)
**Why it matters (plain English):** Most scanners drown users in alerts. “Quietbydesign” shows only *provable, reachable* risks and lets you create **auditable exceptions** (temporary waivers) that autofeed compliance packs.
**User flow**
1. **Inbox grouped by exploit path**
* Group key = `(artifact → package → vulnerable symbol → runtime path)`.
* Each group shows: risk score, blast radius (count of dependents), EPSS/CVSS, and a “Proof” button.
2. **Open a path → Proof bundle**
* **Reach subgraph** (who calls what).
* **Symbol map** (function/offsets; source or binary map).
* **VEX claims** (vendor/distro/internal) with trust score + signatures.
3. **Raise Exception** (timeboxed)
* **Required fields:** attested reason (dropdown + free text), expiry date, recheck policy (e.g., “fail build if new reachable path appears”, “fail if EPSS > X”).
* **Attestation:** DSSEsigned exception object, OCIattached to artifact digest.
* Autolands in **Audit Pack** (PDF/JSON bundle) and **Timeline**.
**Data model (C# POCO sketch)**
```csharp
record ExploitPathId(string ArtifactDigest, string PackagePurl, string CveId, string Symbol, string EntryPathHash);
record ExceptionObj(
string Id, ExploitPathId Path, string ReasonCode, string ReasonText,
DateTimeOffset IssuedAt, DateTimeOffset ExpiresAt,
RecheckPolicy Policy, EvidenceRef[] Evidence, AttestationMeta Att);
```
**RecheckPolicy (examples)**
* `ReachGraphChange=Block`
* `EPSSAbove=0.5`
* `EnvScope=prod-only`
* `UnknownsAbove=N → Block`
**UI essentials**
* **Inbox:** 3pane (List • Details • Proof).
* **Proof drawer:** collapsible reach subgraph; oneclick JSON export.
* **Exception modal:** expiry presets (7/30/90 days), reason templates (backport, featureflagoff, compensating control).
**APIs (REST-ish)**
* `GET /triage/inbox?scope=env:prod&quiet=true`
* `GET /triage/path/{id}/proof`
* `POST /exceptions` (body = ExceptionObj sans AttestationMeta; server returns DSSE envelope)
* `GET /audit-packs/{releaseDigest}`
**Guardrails**
* Exceptions must **never** outlive a release line by default (force renewal).
* Creating an exception **requires evidence hooks** (see below).
* Build gates read exceptions; if proof no longer holds (reach diff changed), gate fails.
---
# Knowledge Snapshots & Merge Semantics
**Plain English:** Take a sealed “photo” of everything you *know* at a point in time—SBOM, VEX, attestations, policies—so audits and incident reviews can be replayed exactly.
**Lifecycle: Snapshot → Seal → Export**
1. **Snapshot**
* Capture: SBOM (CycloneDX 1.6 + SPDX 3.0.1), VEX docs, reach subgraphs, exception objects, policies, trust scores, feed versions, rules hashes.
2. **Seal**
* Compute **Manifest of Manifests** (Merkle root) + DSSE signature (with PQ option).
* Store to **Authority** (Postgres) and attach to image digest (OCI ref).
3. **Export**
* Produce a single **Replay Bundle** (`.stella-replay.tgz`): data + `REPLAY.yaml` (inputs, versions, lattice rules).
* Offlineready.
**Policy pane with merge semantics**
* Default preview: **vendor ⊕ distro ⊕ internal** (not “vendor > distro > internal”).
* **Lattice rules** define resolution (e.g., `NOT_AFFECTED ⊕ AFFECTED → AFFECTED unless Evidence(feature_flag_off)`).
* **Evidence hooks (required):**
* “Not affected because feature X off” → must include **featureflag attestation** (envscoped, signed).
* “Backported patch” → must include **patchindex** mapping (`fixedsymbols`, commit OIDs).
* “Compensating control” → must include **control attestation** (control ID, monitoring link, SLO).
**UI essentials**
* **Snapshot panel:** shows inputs (feed versions, rules hash), diff vs last snapshot, “Seal & Export” button.
* **Policy pane:** interactive merge preview; failed hooks highlighted with “Add evidence” CTA.
* **Replay check:** “Verify determinism” runs local reeval; shows PASS/FAIL badge.
**APIs**
* `POST /snapshots` → returns `SnapshotId` + content hashes
* `POST /snapshots/{id}/seal` → returns DSSE + Merkle root
* `GET /snapshots/{id}/export` → stream bundle
* `POST /policy/preview` (inputs: sources+claims) → resolved verdict + missing-evidence list
**Storage**
* **Postgres** = system of record (immutable rows for sealed snapshots).
* **Valkey** (optional) = cache (diffs, precomputed subgraphs).
* **OCI** = distribution of attestations & snapshots alongside images.
---
# PM & Dev acceptance checklist (short)
* **Triage Inbox**
* [ ] Groups by exploit path, not by CVE alone.
* [ ] Proof bundle includes reach subgraph + symbol map + VEX claims.
* [ ] Exception modal enforces reason + expiry + recheck policy + evidence.
* [ ] Exceptions are DSSEsigned and appear in Audit Pack.
* **Knowledge Snapshot**
* [ ] Snapshot records *all* inputs (feeds, rules, versions, hashes).
* [ ] Seal produces a verifiable Merkle root + DSSE.
* [ ] Export bundle replays deterministically offline.
* [ ] Policy pane supports lattice merges + evidence hooks with blocking states.
---
# Tiny implementation notes (.NET 10)
* Use **record structs** for small immutable IDs; keep hash fields as `ReadOnlyMemory<byte>`.
* Attest with **intoto/DSSE**; provide PQ toggle (Dilithium/Falcon) at keypolicy level.
* Graphs: store **reach subgraph** as compressed adjacency lists; index by `(digest, symbol)`.
---
If you want, I can turn this into: (a) Swagger stubs, (b) EF Core schema + migrations, or (c) a Figmaready UI spec with screen flows and copy.

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,22 @@
# Reachability Drift Detection
**Date**: 2025-12-17
**Status**: ANALYZED - Ready for Implementation Planning
**Status**: ARCHIVED - Implementation Complete (Sprints 3600.2-3600.3)
**Archived**: 2025-12-22
**Related Advisories**:
- 14-Dec-2025 - Smart-Diff Technical Reference
- 14-Dec-2025 - Reachability Analysis Technical Reference
**Implementation Documentation**:
- Architecture: `docs/modules/scanner/reachability-drift.md`
- API Reference: `docs/api/scanner-drift-api.md`
- Operations Guide: `docs/operations/reachability-drift-guide.md`
**Follow-up Sprints**:
- `SPRINT_3600_0004_0001` - Node.js Babel Integration (TODO)
- `SPRINT_3600_0005_0001` - Policy CI Gate Integration (TODO)
- `SPRINT_3600_0006_0001` - Documentation Finalization (TODO)
---
## 1. EXECUTIVE SUMMARY
@@ -39,17 +50,17 @@ This advisory proposes extending StellaOps' Smart-Diff capabilities to detect **
### 2.2 What's Missing (New Implementation Required)
| Component | Advisory Ref | Gap Description |
|-----------|-------------|-----------------|
| **Call Graph Extractor (.NET)** | §7 C# Roslyn | No MSBuildWorkspace/Roslyn analysis exists |
| **Call Graph Extractor (Go)** | §7 Go SSA | No golang.org/x/tools/go/ssa integration |
| **Call Graph Extractor (Java)** | §7 | No Soot/WALA integration |
| **Call Graph Extractor (Node)** | §7 | No @babel/traverse integration |
| **`scanner.code_changes` table** | §4 Smart-Diff | AST-level diff facts not persisted |
| **Drift Cause Explainer** | §6 Timeline | No causal attribution on path nodes |
| **Path Viewer UI** | §UX | No Angular component for call path visualization |
| **Cross-scan Function-level Drift** | §6 | State drift exists, function-level doesn't |
| **Entrypoint Discovery (per-framework)** | §3 | Limited beyond package.json/manifest parsing |
| Component | Advisory Ref | Gap Description | **Post-Implementation Status** |
|-----------|-------------|-----------------|-------------------------------|
| **Call Graph Extractor (.NET)** | §7 C# Roslyn | No MSBuildWorkspace/Roslyn analysis exists | **DONE** - `DotNetCallGraphExtractor` |
| **Call Graph Extractor (Go)** | §7 Go SSA | No golang.org/x/tools/go/ssa integration | **DONE** - `GoCallGraphExtractor` |
| **Call Graph Extractor (Java)** | §7 | No Soot/WALA integration | **DONE** - `JavaCallGraphExtractor` (ASM) |
| **Call Graph Extractor (Node)** | §7 | No @babel/traverse integration | **PARTIAL** - Skeleton exists |
| **`scanner.code_changes` table** | §4 Smart-Diff | AST-level diff facts not persisted | **DONE** - Migration 010 |
| **Drift Cause Explainer** | §6 Timeline | No causal attribution on path nodes | **DONE** - `DriftCauseExplainer` |
| **Path Viewer UI** | §UX | No Angular component for call path visualization | **DONE** - `path-viewer.component.ts` |
| **Cross-scan Function-level Drift** | §6 | State drift exists, function-level doesn't | **DONE** - `ReachabilityDriftDetector` |
| **Entrypoint Discovery (per-framework)** | §3 | Limited beyond package.json/manifest parsing | **DONE** - 9 entrypoint types |
### 2.3 Terminology Mapping
@@ -378,11 +389,13 @@ smart_diff:
### 7.2 Sub-Sprints
| ID | Topic | Priority | Effort | Dependencies |
|----|-------|----------|--------|--------------|
| SPRINT_3600_0002_0001 | Call Graph Infrastructure | P0 | Large | Master |
| SPRINT_3600_0003_0001 | Drift Detection Engine | P0 | Medium | 3600.2 |
| SPRINT_3600_0004_0001 | UI and Evidence Chain | P1 | Medium | 3600.3 |
| ID | Topic | Priority | Effort | Dependencies | **Status** |
|----|-------|----------|--------|--------------|------------|
| SPRINT_3600_0002_0001 | Call Graph Infrastructure | P0 | Large | Master | **DONE** |
| SPRINT_3600_0003_0001 | Drift Detection Engine | P0 | Medium | 3600.2 | **DONE** |
| SPRINT_3600_0004_0001 | Node.js Babel Integration | P1 | Medium | 3600.3 | TODO |
| SPRINT_3600_0005_0001 | Policy CI Gate Integration | P1 | Small | 3600.3 | TODO |
| SPRINT_3600_0006_0001 | Documentation Finalization | P0 | Medium | 3600.3 | TODO |
---

View File

@@ -0,0 +1,231 @@
# ARCHIVED
> **Archived:** 2025-12-22
> **Reason:** Gap analysis complete; implementation planned via SPRINT_4300 series
> **Analysis:** `docs/implplan/analysis/4300_explainable_triage_gap_analysis.md`
> **Sprints:** `docs/implplan/SPRINT_4300_*.md`
> **Coverage:** ~85% already implemented via prior sprints (3800, 3801, 4100, 4200)
> **Remaining Gaps:** 6 sprints created to close gaps (CLI verify, Evidence API, Privacy, TTL, Schemas, Metrics)
---
Here's a practical, first-time-friendly blueprint for making your security workflow both **explainable** and **provable**-from triage to approval.
# Explainable triage UX (what & why)
Show every risk score with the minimum evidence a responder needs to trust it:
* **Reachable path:** the concrete call-chain (or network path) proving the vuln is actually hit.
* **Entrypoint boundary:** the external surface (HTTP route, CLI verb, cron, message topic) that leads to that path.
* **VEX status:** the exploitability decision (Affected/Not Affected/Under Investigation/Fixed) with rationale.
* **Last-seen timestamp:** when this evidence was last observed/generated.
## UI pattern (compact, 1-click expand)
* **Row (collapsed):** `Score 72 - CVE-2024-12345 - service: api-gateway - package: x.y.z`
* **Expand panel (evidence):**
* **Path:** `POST /billing/charge -> BillingController.Pay() -> StripeClient.Create()`
* **Boundary:** `Ingress: /billing/charge (JWT: required, scope: payments:write)`
* **VEX:** `Not Affected (runtime guard strips untrusted input before sink)`
* **Last seen:** `2025-12-18T09:22Z` (scan: sbomer#c1a2, policy run: lattice#9f0d)
* **Actions:** "Open proof bundle", "Re-run check", "Create exception (time-boxed)"
## Data contract (what the panel needs)
```json
{
"finding_id": "f-7b3c",
"cve": "CVE-2024-12345",
"component": {"name": "stripe-sdk", "version": "6.1.2"},
"reachable_path": [
"HTTP POST /billing/charge",
"BillingController.Pay",
"StripeClient.Create"
],
"entrypoint": {"type":"http","route":"/billing/charge","auth":"jwt:payments:write"},
"vex": {"status":"not_affected","justification":"runtime_sanitizer_blocks_sink","timestamp":"2025-12-18T09:22:00Z"},
"last_seen":"2025-12-18T09:22:00Z",
"attestation_refs": ["sha256:...sbom", "sha256:...vex", "sha256:...policy"]
}
```
# Evidence-linked approvals (what & why)
Make "Approve to ship" contingent on **verifiable proof**, not screenshots:
* **Chain** must exist and be machine-verifiable: **SBOM -> VEX -> policy decision**.
* Use **in-toto/DSSE** attestations or **SLSA provenance** so each link has a signature, subject digest, and predicate.
* **Gate** merges/deploys only when the chain validates.
## Pipeline gate (simple policy)
* Require:
1. **SBOM attestation** referencing the exact image digest
2. **VEX attestation** covering all listed components (or explicit allow-gaps)
3. **Policy decision attestation** (e.g., "risk <= threshold AND all reachable vulns = Not Affected/Fixed")
### Minimal decision attestation (DSSE envelope -> JSON payload)
```json
{
"predicateType": "stella/policy-decision@v1",
"subject": [{"name":"registry/org/app","digest":{"sha256":"<image-digest>"}}],
"predicate": {
"policy": "risk_threshold<=75 && reachable_vulns.all(v => v.vex in ['not_affected','fixed'])",
"inputs": {
"sbom_ref": "sha256:<sbom>",
"vex_ref": "sha256:<vex>"
},
"result": {"allowed": true, "score": 61, "exemptions":[]},
"evidence_refs": ["sha256:<reachability-proof-bundle>"],
"run_at": "2025-12-18T09:23:11Z"
}
}
```
# How this lands in your product (concrete moves)
* **Backend:** add `/findings/:id/evidence` (returns the contract above) + `/approvals/:artifact/attestations`.
* **Storage:** keep **proof bundles** (graphs, call stacks, logs) as content-addressed blobs; store DSSE envelopes alongside.
* **UI:** one list -> expandable rows; chips for VEX status; "Open proof" shows the call graph and boundary in 1 view.
* **CLI/API:** `stella verify image:<digest> --require sbom,vex,decision` returns a signed summary; pipelines fail on non-zero.
* **Metrics:**
* **% changes with complete attestations** (target >=95%)
* **TTFE (time-to-first-evidence)** from alert -> panel open (target <=30s)
* **Post-deploy reversions** due to missing proof (trend to zero)
# Starter acceptance checklist
* [x] Every risk row expands to path, boundary, VEX, last-seen in <300 ms. *(SPRINT_4200_0001_0001)*
* [x] "Approve" button disabled until SBOM+VEX+Decision attestations validate for the **exact artifact digest**. *(SPRINT_4100_0005_0001)*
* [x] One-click "Show DSSE chain" renders the three envelopes with subject digests and signers. *(SPRINT_4200_0001_0001)*
* [x] Audit log captures who approved, which digests, and which evidence hashes. *(SPRINT_3801_0001_0004)*
---
## Implementation Coverage Summary
| Section | Coverage | Sprint(s) |
|---------|----------|-----------|
| Explainable Triage UX | 85% | 3800.*, 4200.0001.0001 |
| Evidence-Linked Approvals | 100% | 3801.*, 4100.* |
| Backend APIs | 85% | 3800.0003.0001, **4300.0001.0002** |
| CLI/API | 50% | 3500.0004.*, **4300.0001.0001** |
| Invariants | 90% | 4100.0003.*, **4300.0002.0002** |
| Data Model | 100% | Scanner.Triage |
| Evidence Types | 100% | 3800.0002.*, Evidence.Bundle |
| Predicate Types | 80% | 3801.*, **4300.0003.0001** |
| Policy Gate | 100% | Policy.Engine |
| Approve Button | 100% | 4100.0005.0001 |
| Privacy | 0% | **4300.0002.0001** |
| TTL Strategy | 50% | **4300.0002.0002** |
| Metrics | 30% | **4300.0003.0002** |
---
*Original advisory content preserved below for reference.*
---
## 1) Start with the invariants (the rules your system must never violate)
If you implement nothing else, implement these invariants-they're what make the UX trustworthy and the approvals auditable.
### Artifact anchoring invariant
Every finding, every piece of evidence, and every approval must be anchored to an immutable **subject digest** (e.g., container image digest `sha256:...`, binary SHA, or SBOM digest).
* No "latest tag" approvals.
* No "approve commit" without mapping to the built artifact digest.
### Evidence closure invariant
A policy decision is only valid if it references **exactly** the evidence it used:
* `inputs.sbom_ref`
* `inputs.vex_ref`
* `inputs.reachability_ref` (optional but recommended)
* `inputs.scan_ref` (optional)
* and any config/IaC refs used for boundary/exposure.
### Signature chain invariant
Evidence is only admissible if it is:
1. structured (machine readable),
2. signed (DSSE/in-toto),
3. verifiable (trusted identity/keys),
4. retrievable by digest.
### Staleness invariant
Evidence must have:
* `last_seen` and `expires_at` (or TTL),
* a "stale evidence" behavior in policy (deny or degrade score).
---
## 2) Choose the canonical formats and where you'll store "proof"
### Attestation envelope: DSSE + in-toto Statement
Use:
* **in-toto Attestation Framework** "Statement" as the payload model ("subject + predicateType + predicate").
* Wrap it in **DSSE** for signing.
### SBOM format: CycloneDX or SPDX
* SPDX is an ISO/IEC standard and has v3.0 and v2.3 lines in the ecosystem.
* CycloneDX is an ECMA standard (ECMA-424) and widely used for application security contexts.
Pick one as **your canonical** (internally), but ingest both.
### VEX format: OpenVEX (practical) + map to "classic" VEX statuses
VEX's value is triage noise reduction: vendors can assert whether a product is affected, fixed, under investigation, or not affected.
OpenVEX is a minimal, embeddable implementation of VEX intended for interoperability.
### Where to store proof: OCI registry referrers
Use OCI "subject/referrers" so proofs travel with the artifact:
* OCI 1.1 introduces an explicit `subject` field and referrers graph for signatures/attestations/SBOMs.
---
## 3-13) [Additional Sections]
*See original advisory for full content on:*
- System architecture
- Data model
- Explainable triage computation
- UI components
- Proof-linked evidence generation
- Predicate types
- Policy gate
- Approve button behavior
- Implementation details
- MVP path
- Quick checklist
---
## References
[1]: https://github.com/secure-systems-lab/dsse "DSSE: Dead Simple Signing Envelope"
[2]: https://github.com/in-toto/attestation "in-toto Attestation Framework"
[3]: https://docs.sigstore.dev/about/bundle/ "Sigstore Bundle Format"
[4]: https://spdx.dev/use/specifications/ "Specifications"
[5]: https://github.com/CycloneDX/specification "CycloneDX/specification"
[6]: https://www.ntia.gov/sites/default/files/publications/vex_one-page_summary_0.pdf "VEX one-page summary"
[7]: https://github.com/openvex/spec "OpenVEX Specification"
[8]: https://opencontainers.org/posts/blog/2024-03-13-image-and-distribution-1-1/ "OCI Image and Distribution Specs v1.1 Releases"
[9]: https://oras.land/docs/concepts/reftypes/ "Attached Artifacts | OCI Registry As Storage"
[10]: https://learn.microsoft.com/en-us/azure/container-registry/container-registry-manage-artifact "Manage OCI Artifacts and Supply Chain Artifacts with ORAS"
[11]: https://openpolicyagent.org/docs/policy-language "Policy Language"
[12]: https://docs.sigstore.dev/cosign/verifying/attestation/ "In-Toto Attestations"

View File

@@ -0,0 +1,93 @@
# ARCHIVED
> **Archived:** 2025-12-22
> **Reason:** Gap analysis complete. Recommendations incorporated into sprints and documentation.
>
> **Implementation Artifacts:**
> - SPRINT_2000_0003_0001: Alpine connector and APK comparator
> - SPRINT_2000_0003_0002: Comprehensive distro version tests (50-100 per distro)
> - SPRINT_4000_0002_0001: Backport UX explainability ("Compared with" badge, "Why Fixed" popover)
> - SPRINT_6000_SUMMARY.md: Updated to reference existing Concelier comparators
> - `src/Concelier/AGENTS.md`: Added distro backport version handling section
>
> **Existing Implementations Validated:**
> - `src/Concelier/__Libraries/StellaOps.Concelier.Merge/Comparers/Nevra.cs` (RPM)
> - `src/Concelier/__Libraries/StellaOps.Concelier.Merge/Comparers/DebianEvr.cs` (Debian/Ubuntu)
> - Distro connectors: Debian, Ubuntu, RedHat, SUSE
---
Here's a quick, practical heads-up on **patch-aware backport handling** so your vulnerability verdicts don't go sideways.
![Package versions concept diagram](attachment\:image)
### Why this matters
Distros often **backport fixes** without bumping the upstream version. If you compare versions with a generic SemVer library, you can mislabel **fixed** builds as **vulnerable** (or the reverse).
### Use distro-native comparators (not SemVer)
* **RPM (RHEL/CentOS/Fedora/openSUSE):** compare using **EVR** (`epoch:version-release`) via `rpmvercmp`. Tilde `~` sorts **before** anything; releases matter (e.g., `1.2-3.el9_2` > `1.2-3`).
* **Debian/Ubuntu:** compare **epoch >> upstream_version >> debian_revision** using `dpkg --compare-versions` rules. Tilde `~` sorts **lower** than empty, so `2.0~rc1` < `2.0`.
* **Alpine (APK):** follows its own comparator; treat `-r` (pkgrel) as part of ordering, similar in spirit to RPM release.
### Practical rules for your scanner (Stella Ops / Feedser -> Vexer)
1. **Normalize the package coordinate**
* RPM: `name:evr.arch` (epoch default 0 if missing).
* DEB: `name:epoch:upstream_version-debian_revision arch`.
* Keep the **distro release**/revision; it encodes backports.
2. **Compare with native engines**
* On Linux hosts/containers, call the system tool when possible:
* RPM: `rpm --qf '%{EPOCH}:%{VERSION}-%{RELEASE}\n' -q <pkg>` then use `rpmdev-vercmp`/`rpmvercmp`.
* DEB/Ubuntu: `dpkg-query -W -f='${Version}\n' <pkg>` and `dpkg --compare-versions`.
* In offline analysis, embed battle-tested comparators (ports of `rpmvercmp` and `dpkg` logic) in your evaluator.
3. **Model advisories with distro ranges**
* Store **per-ecosystem fixed ranges**:
* RPM example: `fixed >= 2:1.4.3-5.el9_3`
* DEB example: `fixed >= 1:1.4.3-5+deb12u2`
* Do **not** rewrite to SemVer; keep native forms.
4. **VEX/decisioning**
* When upstream says "fixed in 1.4.4" but **distro claims fixed in 1.4.3-5~deb12u2**, prefer distro channel **if source is trusted**.
* Record **evidence**: source (DSA/RHSA/USN), comparator used, installed EVR/DEB version, fixed threshold, and result. Attach to the verdict.
5. **Edge cases to test**
* Epoch jumps: `1:1.2-1` > `0:9.9-9`.
* Tilde pre-releases: `2.0~rc1` < `2.0`.
* Release qualifiers: `1.2-3.el9_2` < `1.2-3.el9_3`.
* Rebuilds/backports: `1.2-3ubuntu0.1` vs `1.2-3`.
### Minimal implementation sketch (C#)
* **Strategy pattern**: `IVersionComparator` with implementations `RpmComparator`, `DpkgComparator`, `ApkComparator`.
* **Selector** by package source (`rpmdb`, `dpkg-status`, `apk info`).
* **Evidence struct**:
```csharp
record VersionVerdict(
string Pkg, string Distro, string Installed, string Fixed,
string Comparator, bool IsFixed, string EvidenceSource, string[] ProofLines);
```
* **Fallback**: If native comparator unavailable, use embedded ports of `rpmvercmp` and Debian's algorithm; never SemVer.
### CI tests you should pin
* A table-driven test set with 50-100 cases covering epochs, tildes, and distro revisions.
* Golden files per distro to prevent regressions.
* Cross-check installed values from real images (e.g., `ubi9`, `debian:12`, `ubuntu:22.04`, `alpine:3.20`).
### UX nudge
* In the UI, show **"Compared with: RPM EVR / dpkg rules"** and link the **exact fixed threshold** that matched. Provide a "why fixed" popover showing the string compare steps.
If you like, I can drop in ready-to-use C# comparators (rpmvercmp/dpkg) and a test corpus so you can wire this straight into Feedser/Vexer.

View File

@@ -0,0 +1,184 @@
Here's a compact, practical way to make VEX trust decisions explainable and replayable across vendor, distro, and internal sources—without adding human-in-the-loop friction.
---
# VEX Trust Lattice (compact)
**Goal:** turn messy/contradictory VEX claims into a single, signed, reproducible verdict with a numeric confidence and an audit trail.
## 1) Trust vector per source
Each VEX source S gets a 3component trust vector scored in [0..1]:
* **Provenance (P):** cryptographic & process integrity
* 1.00 = DSSEsigned, timestamped, Rekor/Git tag anchored, org DKIM/Sigstore OIDC, key in allowlist, rotation policy OK
* 0.75 = DSSEsigned + public key known, but no transparency log
* 0.40 = unsigned but retrieved via authenticated, immutable artifact repo
* 0.10 = opaque/CSV/email/manual import
* **Coverage (C):** how well the statement's scope maps to your asset
* 1.00 = exact package + version/build digest + feature/flag context matched
* 0.75 = exact pkg + version range matched; partial feature context
* 0.50 = productlevel only; maps via CPE/PURL family
* 0.25 = familylevel heuristics; no version proof
* **Replayability (R):** can we deterministically rederive the claim?
* 1.00 = all inputs pinned (feeds, SBOM hash, ruleset hash, lattice version); replays byteidentical
* 0.60 = inputs mostly pinned; nondeterministic ordering tolerated but stable outcome
* 0.20 = ephemeral APIs; no snapshot
**BaseTrust(S) = wP·P + wC·C + wR·R** (defaults: wP=0.45, wC=0.35, wR=0.20; tunable per policy).
## 2) Claim strength & freshness
Every individual VEX claim `S asserts X` carries multipliers:
* **Strength (M):** *not*affectedbecause{reason}
* Exploitability analysis + reachability proof subgraph provided → 1.00
* Config/featureflag reason with evidence → 0.80
* Vendor blanket statement → 0.60
* "Under investigation" → 0.40
* **Freshness (F):** timedecay curve; default halflife 90 days
* `F = exp(- ln(2) · age_days / 90)`; floor at 0.35 unless revoked
**ClaimScore = BaseTrust(S) · M · F.**
## 3) Lattice ordering & merge
Define a **partial order** on claims by (scope specificity, ClaimScore). More specific scope wins ties.
For a given CVE×Asset, gather all claims `{Ci}`:
* If any **revocation/contradiction** exists, keep both and trigger **conflict mode**: require replay proof; otherwise downweight older/weaker by Δ=0.25.
* Final **verdict** chosen by **argmax(ClaimScore)** after conflict adjustments.
Return tuple:
```
Verdict = {
status: {affected|not_affected|under_investigation|fixed},
confidence: ClaimScore*,
expl: list of (source, reason, P/C/R, M, F),
evidence_refs: [attestations, SBOM hash, reachability subgraph id],
policy_hash, lattice_version
}
```
## 4) Policy hooks (explainable gates)
* **Minimum confidence by environment:** e.g., prod requires ≥0.75 to accept "not_affected".
* **Unknowns budget:** fail if (#unknown deps > N) OR (Σ(1ClaimScore) over unknowns > T).
* **Source quotas:** cap influence from any single vendor at 60% unless a second independent source supports within Δ=0.1.
* **Reason allowlist:** forbid blanket vendor claims for criticals unless reachability proof exists.
## 5) Deterministic replay
To guarantee "same inputs → same verdict":
* Pin: SBOM digest(s), vuln feed snapshot ids, VEX document digests, reachability graph ids, policy file, lattice version, clock cutoff.
* Sort: stable topological order on inputs (by `(issuer_did, statement_digest)`).
* Serialize verdict + inputs into a **Verdict Manifest** (JSON/CBOR) and sign (DSSE).
* Store in **Authority** with index: `(asset_digest, CVE, policy_hash, lattice_version)`.
## 6) Minimal data model (for Vexer/Policy Engine)
```json
{
"source": {
"id": "did:web:vendor.example",
"provenance": {"sig_type":"dsse","rekor_log_id":"...","key_alias":"vendor_k1"},
"provenance_score": 0.90,
"coverage_score": 0.75,
"replay_score": 0.60,
"weights": {"wP":0.45,"wC":0.35,"wR":0.20}
},
"claim": {
"scope": {"purl":"pkg:rpm/openssl@3.0.12-5","digest":"sha256:...","features":{"fips":true}},
"cve": "CVE-2025-12345",
"status": "not_affected",
"reason": "feature_flag_off",
"strength": 0.80,
"issued_at": "2025-11-28T10:12:00Z",
"evidence": {"reach_subgraph_id":"reg:subg/abcd","attestation":"sha256:..."}
},
"policy": {"min_confidence_prod":0.75,"unknown_budget":5,"require_reachability_for_criticals":true},
"lattice_version": "1.2.0"
}
```
## 7) Deterministic evaluation (C# sketch)
```csharp
public record TrustWeights(double wP=0.45, double wC=0.35, double wR=0.20);
double BaseTrust(double P, double C, double R, TrustWeights W)
=> W.wP*P + W.wC*C + W.wR*R;
double Freshness(DateTime issuedAt, DateTime cutoff, double halfLifeDays=90, double floor=0.35)
{
var age = (cutoff - issuedAt).TotalDays;
var f = Math.Exp(-Math.Log(2) * age / halfLifeDays);
return Math.Max(f, floor);
}
double ClaimScore(Source s, Claim c, TrustWeights W, DateTime cutoffUtc)
{
var baseTrust = BaseTrust(s.P, s.C, s.R, W);
var freshness = Freshness(c.IssuedAt, cutoffUtc);
return baseTrust * c.Strength * freshness;
}
// Merge: pick best score; apply conflict penalty if contradictory present
Verdict Merge(IEnumerable<(Source S, Claim C)> claims, Policy policy, DateTime cutoffUtc)
{
var scored = claims.Select(t => new {
t.S, t.C, Score = ClaimScore(t.S, t.C, t.S.Weights, cutoffUtc)
}).ToList();
bool contradictory = scored.Select(x=>x.C.Status).Distinct().Count() > 1;
if (contradictory) {
scored = scored.Select(x => new {
x.S, x.C, Score = x.Score * 0.75 // conflict penalty
}).ToList();
}
var winner = scored.OrderByDescending(x => (x.C.Scope.Specificity, x.Score)).First();
if (policy.RequireReachForCriticals && winner.C.IsCritical && !winner.C.HasReachabilityProof)
return Verdict.FailGate("No reachability proof for critical");
if (policy.MinConfidenceProd.HasValue && winner.Score < policy.MinConfidenceProd)
return Verdict.FailGate("Below minimum confidence");
return Verdict.Accept(winner.C.Status, winner.Score, AuditTrail.From(scored));
}
```
## 8) UI: "Trust Algebra" panel (1 screen, no new page)
* **Header:** CVE × Asset digest → final status + confidence meter.
* **Stacked bars:** P/C/R contributions for the winning claim.
* **Claim table:** source, status, reason, P/C/R, strength, freshness, ClaimScore; toggle "show conflicts".
* **Policy chips:** which gates applied; click to open policy YAML/JSON (readonly if in replay).
* **Replay button:** "Reproduce verdict" → emits a signed **Verdict Manifest** and logs proof ids.
## 9) Defaults for source classes
* **Vendor:** P=0.9, C=0.7 (often coarse), R=0.6
* **Distro:** P=0.8, C=0.85 (buildaware), R=0.6
* **Internal:** P=0.85 (orgsigned), C=0.95 (exact SBOM+reach), R=0.9
Tune per issuer using rolling calibration: compare past ClaimScores vs. postmortem truth; adjust via small learning rate (±0.02/epoch) under a signed **calibration manifest** (also replayable).
---
If you want, I can drop this into your Stella Ops modules today as:
* **Vexer:** trustvector store + claim normalizer
* **Policy Engine:** lattice evaluator + gates
* **Authority:** verdict manifest signer/indexer
* **UI:** single "Trust Algebra" panel wired to evidence ids
Say the word and I'll generate the concrete JSON schemas, C# interfaces, and a seed policy file.