finish off sprint advisories and sprints
This commit is contained in:
253
docs/modules/signals/unified-score.md
Normal file
253
docs/modules/signals/unified-score.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Unified Trust Score
|
||||
|
||||
> **Ownership:** Signals Guild / Platform Guild
|
||||
> **Services:** `StellaOps.Signals.UnifiedScore`
|
||||
> **API:** `POST /api/v1/score/evaluate`, `GET /api/v1/score/{id}/replay`
|
||||
> **CLI:** `stella score compute|explain|replay|verify`, `stella gate score evaluate`
|
||||
|
||||
## Overview
|
||||
|
||||
The Unified Trust Score is a facade over existing EWS (Evidence-Weighted Score) and Determinization systems. It provides a single API for computing risk scores, uncertainty metrics, and score replay proofs without replacing any underlying scoring logic.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Input** — Caller provides signal values (reachability, runtime, exploit, etc.) and optional context (CVE ID, PURL, SBOM ref)
|
||||
2. **EWS computation** — The facade delegates to `IEvidenceWeightedScoreCalculator` using weights from a versioned manifest
|
||||
3. **Entropy calculation** — `IUncertaintyScoreCalculator` computes the unknowns fraction (U) from signal presence/absence
|
||||
4. **Conflict detection** — `IConflictDetector` identifies contradictory signals
|
||||
5. **Delta calculation** — For missing signals, computes potential score impact ranges
|
||||
6. **Result assembly** — Returns `UnifiedScoreResult` combining all outputs
|
||||
|
||||
---
|
||||
|
||||
## The Unknowns Fraction (U)
|
||||
|
||||
The `UnknownsFraction` exposes how much of the score depends on absent data:
|
||||
|
||||
```
|
||||
U = 1 - (weighted_present_signals / total_weight)
|
||||
```
|
||||
|
||||
### Unknowns Bands
|
||||
|
||||
| U Range | Band | Meaning | Recommended Action |
|
||||
|---------|------|---------|-------------------|
|
||||
| 0.0 – 0.2 | **Complete** | All signals present | Automated decisions safe |
|
||||
| 0.2 – 0.4 | **Adequate** | Sufficient signal coverage | Automated decisions safe |
|
||||
| 0.4 – 0.6 | **Sparse** | Signal gaps exist | Manual review recommended |
|
||||
| 0.6 – 1.0 | **Insufficient** | Critical data missing | Block until more signals arrive |
|
||||
|
||||
Band thresholds align with Determinization configuration:
|
||||
- `RefreshEntropyThreshold: 0.40` — triggers signal refresh attempt
|
||||
- `ManualReviewEntropyThreshold: 0.60` — requires human review
|
||||
|
||||
---
|
||||
|
||||
## Delta-If-Present
|
||||
|
||||
When signals are absent, the facade calculates how the score would change if each missing signal were provided:
|
||||
|
||||
```json
|
||||
{
|
||||
"signal": "reachability",
|
||||
"min_impact": -15,
|
||||
"max_impact": 8,
|
||||
"weight": 0.30,
|
||||
"description": "If reachability confirmed as not-reachable, score decreases by up to 15"
|
||||
}
|
||||
```
|
||||
|
||||
This helps operators prioritize which signals to gather first.
|
||||
|
||||
---
|
||||
|
||||
## Weight Manifests
|
||||
|
||||
EWS weights are stored in versioned JSON files under `etc/weights/`:
|
||||
|
||||
```
|
||||
etc/weights/v2026-01-22.weights.json
|
||||
```
|
||||
|
||||
Manifests are:
|
||||
- **Immutable** once published
|
||||
- **Content-addressed** via SHA-256 hash
|
||||
- **Pinnable** by policy rules via `weights_ref`
|
||||
- **Auditable** — the manifest version and hash are included in every score result
|
||||
|
||||
See [Scoring Algebra §4](../../technical/scoring-algebra.md) for the manifest schema.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `POST` | `/api/v1/score/evaluate` | Compute unified score |
|
||||
| `GET` | `/api/v1/score/{scoreId}` | Retrieve previously computed score |
|
||||
| `GET` | `/api/v1/score/weights` | List weight manifest versions |
|
||||
| `GET` | `/api/v1/score/weights/{version}` | Get specific manifest |
|
||||
| `GET` | `/api/v1/score/weights/effective` | Get effective manifest for a date |
|
||||
| `GET` | `/api/v1/score/{scoreId}/replay` | Fetch signed replay proof |
|
||||
| `POST` | `/api/v1/score/verify` | Verify a replay log |
|
||||
|
||||
### Evaluate Request
|
||||
|
||||
```json
|
||||
{
|
||||
"cve_id": "CVE-2024-1234",
|
||||
"purl": "pkg:npm/lodash@4.17.0",
|
||||
"signals": {
|
||||
"reachability": 0.9,
|
||||
"runtime": 0.7,
|
||||
"exploit": 0.3,
|
||||
"backport": 0.0,
|
||||
"source": 0.5,
|
||||
"mitigation": 0.0
|
||||
},
|
||||
"options": {
|
||||
"include_breakdown": true,
|
||||
"include_delta": true,
|
||||
"weight_set_id": "v2026-01-22"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Evaluate Response (key fields)
|
||||
|
||||
```json
|
||||
{
|
||||
"score_id": "score_a1b2c3d4e5f67890",
|
||||
"score_value": 72,
|
||||
"bucket": "ScheduleNext",
|
||||
"unknowns_fraction": 0.15,
|
||||
"unknowns_band": "Complete",
|
||||
"weight_manifest": {
|
||||
"version": "v2026-01-22",
|
||||
"content_hash": "sha256:..."
|
||||
},
|
||||
"ews_digest": "sha256:...",
|
||||
"determinization_fingerprint": "sha256:...",
|
||||
"computed_at": "2026-01-23T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### `stella score compute`
|
||||
|
||||
Compute a unified score from signal values:
|
||||
|
||||
```bash
|
||||
stella score compute \
|
||||
--finding-id CVE-2024-1234@pkg:npm/lodash@4.17.0 \
|
||||
--cvss 7.5 --epss 0.15 \
|
||||
--reachability 0.9 --runtime 0.7 \
|
||||
--format table
|
||||
```
|
||||
|
||||
### `stella score explain`
|
||||
|
||||
Show a detailed breakdown of a score:
|
||||
|
||||
```bash
|
||||
stella score explain CVE-2024-1234@pkg:npm/lodash@4.17.0
|
||||
```
|
||||
|
||||
### `stella score replay`
|
||||
|
||||
Fetch the signed replay proof for a previously computed score:
|
||||
|
||||
```bash
|
||||
stella score replay score_a1b2c3d4e5f67890
|
||||
```
|
||||
|
||||
### `stella score verify`
|
||||
|
||||
Re-execute the computation and verify it matches the original:
|
||||
|
||||
```bash
|
||||
stella score verify score_a1b2c3d4e5f67890
|
||||
```
|
||||
|
||||
### `stella gate score evaluate` (enhanced)
|
||||
|
||||
Existing gate command with new flags:
|
||||
|
||||
```bash
|
||||
stella gate score evaluate \
|
||||
--finding-id CVE-2024-1234@pkg:npm/lodash \
|
||||
--cvss 7.5 --epss 0.15 \
|
||||
--show-unknowns --show-deltas \
|
||||
--weights-version v2026-01-22
|
||||
```
|
||||
|
||||
### `stella gate score weights`
|
||||
|
||||
Manage weight manifests:
|
||||
|
||||
```bash
|
||||
stella gate score weights list
|
||||
stella gate score weights show v2026-01-22
|
||||
stella gate score weights diff v2026-01-22 v2026-02-01
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Score Replay and Verification
|
||||
|
||||
Every computed score can produce a **replay proof** — a DSSE-signed attestation (payload type `application/vnd.stella.score+json`) that records:
|
||||
|
||||
1. Canonical input hashes (SBOM, VEX, etc.)
|
||||
2. Transform versions applied (canonicalization, normalization, decay)
|
||||
3. Step-by-step algebra decisions (signal × weight = contribution)
|
||||
4. Final score and metadata
|
||||
|
||||
Replay proofs enable:
|
||||
- **Independent verification** — auditors re-execute the computation
|
||||
- **Transparency logging** — optional anchoring to Rekor for non-repudiation
|
||||
- **OCI storage** — proofs stored as OCI referrers ("StellaBundle" pattern)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### High Unknowns Fraction (U > 0.6)
|
||||
|
||||
**Symptom:** Score shows "Insufficient" band, decisions are blocked.
|
||||
|
||||
**Causes:**
|
||||
- Missing reachability analysis (run `stella scan` with `--reachability`)
|
||||
- No VEX data available (check VEX feed configuration)
|
||||
- Runtime observations not collected (configure runtime agent)
|
||||
|
||||
**Resolution:**
|
||||
1. Run `stella score explain <finding-id>` to see which signals are missing
|
||||
2. Use `--show-deltas` to understand which signals would have the most impact
|
||||
3. Prioritize gathering signals with the highest weight × delta
|
||||
|
||||
### Score Disagrees with CVSS
|
||||
|
||||
**Symptom:** EWS score is much lower than expected from CVSS alone.
|
||||
|
||||
**Explanation:** EWS incorporates reachability, runtime, backport, and mitigation signals that CVSS does not. A high-CVSS vulnerability that is not reachable or already mitigated will have a lower EWS score.
|
||||
|
||||
**Resolution:** Run `stella score explain` to see the per-dimension breakdown and understand which signals are reducing the score.
|
||||
|
||||
### Replay Verification Fails
|
||||
|
||||
**Symptom:** `stella score verify` reports `score_matches: false`.
|
||||
|
||||
**Causes:**
|
||||
- Weight manifest version changed between compute and verify
|
||||
- Signal inputs were modified after scoring
|
||||
- Non-determinism in signal providers (check for time-dependent signals)
|
||||
|
||||
**Resolution:**
|
||||
1. Pin the weight manifest version in the verify request
|
||||
2. Ensure canonical inputs match (compare SHA-256 hashes)
|
||||
3. Check the `differences` field in the verify response for specific mismatches
|
||||
Reference in New Issue
Block a user