sprints and audit work

This commit is contained in:
StellaOps Bot
2026-01-07 09:36:16 +02:00
parent 05833e0af2
commit ab364c6032
377 changed files with 64534 additions and 1627 deletions

View File

@@ -118,10 +118,61 @@ Key notes:
| **API** (`Api/`) | Minimal API endpoints, DTO validation, problem responses, idempotency. | Generated clients for CLI/UI. |
| **Observability** (`Telemetry/`) | Metrics (`policy_run_seconds`, `rules_fired_total`), traces, structured logs. | Sampled rule-hit logs with redaction. |
| **Offline Adapter** (`Offline/`) | Bundle export/import (policies, simulations, runs), sealed-mode enforcement. | Uses DSSE signing via Signer service; bundles include IR hash, input cursors, shadow flag, coverage artefacts. |
| **VEX Decision Emitter** (`Vex/Emitter/`) | Build OpenVEX statements, attach reachability evidence hashes, request DSSE signing, and persist artifacts for Export Center / bench repo. | New (Sprint401); integrates with Signer predicate `stella.ops/vexDecision@v1` and Attestor Rekor logging. |
| **VEX Decision Emitter** (`Vex/Emitter/`) | Build OpenVEX statements, attach reachability evidence hashes, request DSSE signing, and persist artifacts for Export Center / bench repo. | New (Sprint401); integrates with Signer predicate `stella.ops/vexDecision@v1` and Attestor Rekor logging. || **Determinization** (`Policy.Determinization/`) | Scores uncertainty/trust based on signal completeness and age; calculates entropy (0.0 = complete, 1.0 = no knowledge), confidence decay (exponential half-life), and aggregated trust scores; emits metrics for uncertainty/decay/trust; supports VEX-trust integration. | Library consumed by Signals and VEX subsystems; configuration via `Determinization` section. |
---
### 3.1 · Determinization Configuration
The Determinization subsystem calculates uncertainty scores based on signal completeness (entropy), confidence decay based on observation age (exponential half-life), and aggregated trust scores. Configuration options in `appsettings.json` under `Determinization`:
```json
{
"Determinization": {
"SignalWeights": {
"VexWeight": 0.35,
"EpssWeight": 0.10,
"ReachabilityWeight": 0.25,
"RuntimeWeight": 0.15,
"BackportWeight": 0.10,
"SbomLineageWeight": 0.05
},
"PriorDistribution": "Conservative",
"ConfidenceHalfLifeDays": 14.0,
"ConfidenceFloor": 0.1,
"ManualReviewEntropyThreshold": 0.60,
"RefreshEntropyThreshold": 0.40,
"StaleObservationDays": 30.0,
"EnableDetailedLogging": false,
"EnableAutoRefresh": true,
"MaxSignalQueryRetries": 3
}
}
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `SignalWeights` | Object | See above | Relative weights for each signal type in entropy calculation. Weights are normalized to sum to 1.0. VEX carries highest weight (0.35), followed by Reachability (0.25), Runtime (0.15), EPSS/Backport (0.10 each), and SBOM lineage (0.05). |
| `PriorDistribution` | Enum | `Conservative` | Prior distribution for missing signals. Options: `Conservative` (pessimistic), `Neutral`, `Optimistic`. Affects uncertainty tier classification when signals are unavailable. |
| `ConfidenceHalfLifeDays` | Double | `14.0` | Half-life period for confidence decay in days. Confidence decays exponentially: `exp(-ln(2) * age_days / half_life_days)`. |
| `ConfidenceFloor` | Double | `0.1` | Minimum confidence value after decay (0.0-1.0). Prevents confidence from decaying to zero, maintaining baseline trust even for very old observations. |
| `ManualReviewEntropyThreshold` | Double | `0.60` | Entropy threshold for triggering manual review (0.0-1.0). Findings with entropy ≥ this value require human intervention due to insufficient signal coverage. |
| `RefreshEntropyThreshold` | Double | `0.40` | Entropy threshold for triggering signal refresh (0.0-1.0). Findings with entropy ≥ this value should attempt to gather more signals before verdict. |
| `StaleObservationDays` | Double | `30.0` | Maximum age before an observation is considered stale (days). Used in conjunction with decay calculations and auto-refresh triggers. |
| `EnableDetailedLogging` | Boolean | `false` | Enable verbose logging for entropy/decay/trust calculations. Useful for debugging but increases log volume significantly. |
| `EnableAutoRefresh` | Boolean | `true` | Automatically trigger signal refresh when entropy exceeds `RefreshEntropyThreshold`. Requires integration with signal providers. |
| `MaxSignalQueryRetries` | Integer | `3` | Maximum retry attempts for failed signal provider queries before marking signal as unavailable. |
**Metrics emitted:**
- `stellaops_determinization_uncertainty_entropy` (histogram, unit: ratio): Uncertainty entropy score per CVE/PURL pair. Tags: `cve`, `purl`.
- `stellaops_determinization_decay_multiplier` (histogram, unit: ratio): Confidence decay multiplier based on observation age. Tags: `half_life_days`, `age_days`.
**Usage in policies:**
Determinization scores are exposed to SPL policies via the `signals.trust.*` and `signals.uncertainty.*` namespaces. Use `signals.uncertainty.entropy` to access entropy values and `signals.trust.score` for aggregated trust scores that combine VEX, reachability, runtime, and other signals with decay/weighting.
---
## 4·Data Model & Persistence
### 4.1 Collections

View File

@@ -0,0 +1,290 @@
# Verdict Rationale Template
> **Status:** Implemented (SPRINT_20260106_001_001_LB)
> **Library:** `StellaOps.Policy.Explainability`
> **API Endpoint:** `GET /api/v1/triage/findings/{findingId}/rationale`
> **CLI Command:** `stella verdict rationale <finding-id>`
---
## Overview
**Verdict Rationales** provide human-readable explanations for policy verdicts using a standardized 4-line template. Each rationale explains:
1. **Evidence:** What vulnerability was found and where
2. **Policy Clause:** Which policy rule triggered the decision
3. **Attestations:** What proofs support the verdict
4. **Decision:** Final verdict with recommendation
Rationales are content-addressed (same inputs produce same rationale ID), enabling caching and deduplication.
---
## 4-Line Template
Every verdict rationale follows this structure:
```
Line 1 - Evidence: CVE-2024-XXXX in `libxyz` 1.2.3; symbol `foo_read` reachable from `/usr/bin/tool`.
Line 2 - Policy: Policy S2.1: reachable+EPSS>=0.2 => triage=P1.
Line 3 - Attestations: Build-ID match to vendor advisory; call-path: `main->parse->foo_read`.
Line 4 - Decision: Affected (score 0.72). Mitigation recommended: upgrade or backport KB-123.
```
### Template Components
| Line | Purpose | Content |
|------|---------|---------|
| **Evidence** | What was found | CVE ID, component PURL, version, reachability info |
| **Policy Clause** | Why decision was made | Policy rule ID, expression, triage priority |
| **Attestations** | Supporting proofs | Build-ID matches, call paths, VEX statements, provenance |
| **Decision** | What to do | Verdict status, risk score, recommendation, mitigation |
---
## API Usage
### Get Rationale (JSON)
```bash
curl -H "Authorization: Bearer $TOKEN" \
"https://scanner.example.com/api/v1/triage/findings/12345/rationale?format=json"
```
**Response:**
```json
{
"finding_id": "12345",
"rationale_id": "rationale:sha256:abc123...",
"schema_version": "1.0",
"evidence": {
"cve": "CVE-2024-1234",
"component_purl": "pkg:npm/lodash@4.17.20",
"component_version": "4.17.20",
"vulnerable_function": "template",
"entry_point": "/app/src/index.js",
"text": "CVE-2024-1234 in `pkg:npm/lodash@4.17.20` 4.17.20; symbol `template` reachable from `/app/src/index.js`."
},
"policy_clause": {
"clause_id": "S2.1",
"rule_description": "High severity with reachability",
"conditions": ["severity>=high", "reachable=true"],
"text": "Policy S2.1: severity>=high AND reachable=true => triage=P1."
},
"attestations": {
"path_witness": {
"id": "witness-789",
"type": "path-witness",
"digest": "sha256:def456...",
"summary": "Path witness from scanner"
},
"vex_statements": [
{
"id": "vex-001",
"type": "vex",
"digest": "sha256:ghi789...",
"summary": "Affected: from vendor.example.com"
}
],
"provenance": null,
"text": "Path witness from scanner; VEX statement: Affected from vendor.example.com."
},
"decision": {
"verdict": "Affected",
"score": 0.72,
"recommendation": "Upgrade to version 4.17.21",
"mitigation": {
"action": "upgrade",
"details": "Upgrade to 4.17.21 or later"
},
"text": "Affected (score 0.72). Mitigation recommended: Upgrade to version 4.17.21."
},
"generated_at": "2026-01-07T12:00:00Z",
"input_digests": {
"verdict_digest": "sha256:abc123...",
"policy_digest": "sha256:def456...",
"evidence_digest": "sha256:ghi789..."
}
}
```
### Get Rationale (Plain Text)
```bash
curl -H "Authorization: Bearer $TOKEN" \
"https://scanner.example.com/api/v1/triage/findings/12345/rationale?format=plaintext"
```
**Response:**
```json
{
"finding_id": "12345",
"rationale_id": "rationale:sha256:abc123...",
"format": "plaintext",
"content": "CVE-2024-1234 in `pkg:npm/lodash@4.17.20` 4.17.20; symbol `template` reachable from `/app/src/index.js`.\nPolicy S2.1: severity>=high AND reachable=true => triage=P1.\nPath witness from scanner; VEX statement: Affected from vendor.example.com.\nAffected (score 0.72). Mitigation recommended: Upgrade to version 4.17.21."
}
```
### Get Rationale (Markdown)
```bash
curl -H "Authorization: Bearer $TOKEN" \
"https://scanner.example.com/api/v1/triage/findings/12345/rationale?format=markdown"
```
**Response:**
```json
{
"finding_id": "12345",
"rationale_id": "rationale:sha256:abc123...",
"format": "markdown",
"content": "**Evidence:** CVE-2024-1234 in `pkg:npm/lodash@4.17.20` 4.17.20; symbol `template` reachable from `/app/src/index.js`.\n\n**Policy:** Policy S2.1: severity>=high AND reachable=true => triage=P1.\n\n**Attestations:** Path witness from scanner; VEX statement: Affected from vendor.example.com.\n\n**Decision:** Affected (score 0.72). Mitigation recommended: Upgrade to version 4.17.21."
}
```
---
## CLI Usage
### Table Output (Default)
```bash
stella verdict rationale 12345
```
```
Finding: 12345
Rationale ID: rationale:sha256:abc123...
Generated: 2026-01-07T12:00:00Z
+--------------------------------------+
| 1. Evidence |
+--------------------------------------+
| CVE-2024-1234 in `pkg:npm/lodash... |
+--------------------------------------+
+--------------------------------------+
| 2. Policy Clause |
+--------------------------------------+
| Policy S2.1: severity>=high AND... |
+--------------------------------------+
+--------------------------------------+
| 3. Attestations |
+--------------------------------------+
| Path witness from scanner; VEX... |
+--------------------------------------+
+--------------------------------------+
| 4. Decision |
+--------------------------------------+
| Affected (score 0.72). Mitigation... |
+--------------------------------------+
```
### JSON Output
```bash
stella verdict rationale 12345 --output json
```
### Markdown Output
```bash
stella verdict rationale 12345 --output markdown
```
### Plain Text Output
```bash
stella verdict rationale 12345 --output text
```
### With Tenant
```bash
stella verdict rationale 12345 --tenant acme-corp
```
---
## Integration
### Service Registration
```csharp
// In Program.cs or service configuration
services.AddVerdictExplainability();
services.AddScoped<IFindingRationaleService, FindingRationaleService>();
```
### Programmatic Usage
```csharp
// Inject IVerdictRationaleRenderer
public class MyService
{
private readonly IVerdictRationaleRenderer _renderer;
public MyService(IVerdictRationaleRenderer renderer)
{
_renderer = renderer;
}
public string GetExplanation(VerdictRationaleInput input)
{
var rationale = _renderer.Render(input);
return _renderer.RenderPlainText(rationale);
}
}
```
---
## Input Requirements
The `VerdictRationaleInput` requires:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `VerdictRef` | `VerdictReference` | Yes | Reference to verdict attestation |
| `Cve` | `string` | Yes | CVE identifier |
| `Component` | `ComponentIdentity` | Yes | Component PURL, name, version |
| `Reachability` | `ReachabilityDetail` | No | Vulnerable function, entry point |
| `PolicyClauseId` | `string` | Yes | Policy clause that triggered verdict |
| `PolicyRuleDescription` | `string` | Yes | Human-readable rule description |
| `PolicyConditions` | `List<string>` | No | Matched conditions |
| `PathWitness` | `AttestationReference` | No | Path witness attestation |
| `VexStatements` | `List<AttestationReference>` | No | VEX statement references |
| `Provenance` | `AttestationReference` | No | Provenance attestation |
| `Verdict` | `string` | Yes | Final verdict status |
| `Score` | `double?` | No | Risk score (0-1) |
| `Recommendation` | `string` | Yes | Recommended action |
| `Mitigation` | `MitigationGuidance` | No | Specific mitigation guidance |
---
## Determinism
Rationales are **content-addressed**: the same inputs always produce the same `rationale_id`. This enables:
- **Caching:** Store and retrieve rationales by ID
- **Deduplication:** Avoid regenerating identical rationales
- **Verification:** Confirm rationale wasn't modified after generation
The rationale ID is computed as:
```
sha256(canonical_json(verdict_id + witness_id + score_factors))
```
---
## Related Documents
- [Verdict Attestations](verdict-attestations.md) - Cryptographic verdict proofs
- [Policy DSL](dsl.md) - Policy rule syntax
- [Scoring Profiles](scoring-profiles.md) - Risk score computation
- [VEX Trust Model](vex-trust-model.md) - VEX statement handling