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 634233dfed
112 changed files with 31925 additions and 1813 deletions

View File

@@ -0,0 +1,462 @@
# Verdict Manifest Specification
> **Status**: Draft (Sprint 7100)
> **Last Updated**: 2025-12-22
> **Source Advisory**: `docs/product-advisories/archived/22-Dec-2026 - Building a Trust Lattice for VEX Sources.md`
## 1. Overview
A Verdict Manifest is a signed, immutable record of a VEX decisioning outcome. It captures all inputs used to produce a verdict, enabling deterministic replay and audit compliance.
### Purpose
1. **Auditability**: Prove exactly how a verdict was reached
2. **Reproducibility**: Replay the decision with identical results
3. **Compliance**: Meet regulatory requirements for security decisions
4. **Debugging**: Investigate unexpected verdict changes
---
## 2. Manifest Schema
### 2.1 Complete Schema
```typescript
interface VerdictManifest {
// Identity
manifestId: string; // Unique identifier
tenant: string; // Tenant scope
// Scope
assetDigest: string; // SHA256 of asset/SBOM
vulnerabilityId: string; // CVE/GHSA/vendor ID
// Inputs (pinned for replay)
inputs: VerdictInputs;
// Result
result: VerdictResult;
// Policy context
policyHash: string; // SHA256 of policy file
latticeVersion: string; // Trust lattice version
// Metadata
evaluatedAt: string; // ISO 8601 UTC timestamp
manifestDigest: string; // SHA256 of canonical manifest
}
interface VerdictInputs {
sbomDigests: string[]; // SBOM document digests
vulnFeedSnapshotIds: string[]; // Feed snapshot identifiers
vexDocumentDigests: string[]; // VEX document digests
reachabilityGraphIds: string[]; // Call graph identifiers
clockCutoff: string; // Evaluation timestamp
}
interface VerdictResult {
status: "affected" | "not_affected" | "fixed" | "under_investigation";
confidence: number; // 0.0 to 1.0
explanations: VerdictExplanation[];
evidenceRefs: string[]; // Attestation/proof references
}
interface VerdictExplanation {
sourceId: string;
reason: string;
provenanceScore: number;
coverageScore: number;
replayabilityScore: number;
strengthMultiplier: number;
freshnessMultiplier: number;
claimScore: number;
}
```
### 2.2 Identity Fields
| Field | Type | Description |
|-------|------|-------------|
| `manifestId` | string | Format: `verd:{tenant}:{asset_short}:{vuln_id}:{timestamp}` |
| `tenant` | string | Tenant identifier for multi-tenancy |
### 2.3 Input Pinning
All inputs that affect the verdict must be pinned for deterministic replay:
| Field | Description |
|-------|-------------|
| `sbomDigests` | SHA256 digests of SBOM documents used |
| `vulnFeedSnapshotIds` | Identifiers for vulnerability feed snapshots |
| `vexDocumentDigests` | SHA256 digests of VEX documents considered |
| `reachabilityGraphIds` | Identifiers for call graph snapshots |
| `clockCutoff` | Timestamp used for freshness calculations |
### 2.4 Verdict Result
The result section contains the actual verdict and full explanation:
| Field | Description |
|-------|-------------|
| `status` | Final verdict: affected, not_affected, fixed, under_investigation |
| `confidence` | Numeric confidence score (0.0 to 1.0) |
| `explanations` | Per-source breakdown of scoring |
| `evidenceRefs` | Links to attestations and proof bundles |
---
## 3. Deterministic Serialization
### 3.1 Canonical JSON
Manifests are serialized using canonical JSON rules:
1. Keys sorted alphabetically (ASCII order)
2. No insignificant whitespace
3. UTF-8 encoding without BOM
4. Timestamps in ISO 8601 format with 'Z' suffix
5. Arrays sorted by natural key (sourceId, then score)
6. Numbers without trailing zeros
### 3.2 Digest Computation
The manifest digest is computed over the canonical JSON:
```csharp
public static string ComputeDigest(VerdictManifest manifest)
{
var json = CanonicalJsonSerializer.Serialize(manifest with { ManifestDigest = "" });
var bytes = Encoding.UTF8.GetBytes(json);
var hash = SHA256.HashData(bytes);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
```
---
## 4. Signing
### 4.1 DSSE Envelope
Verdict manifests are signed using [DSSE (Dead Simple Signing Envelope)](https://github.com/secure-systems-lab/dsse):
```json
{
"payloadType": "application/vnd.stellaops.verdict+json",
"payload": "<base64-encoded-manifest>",
"signatures": [
{
"keyid": "projects/stellaops/keys/verdict-signer-2025",
"sig": "<base64-encoded-signature>"
}
]
}
```
### 4.2 Predicate Type
For in-toto attestations:
```
https://stella-ops.org/attestations/vex-verdict/1
```
### 4.3 Rekor Integration
Optionally, verdicts can be logged to Sigstore Rekor for transparency:
```json
{
"rekorLogId": "rekor.sigstore.dev",
"rekorLogIndex": 12345678,
"rekorEntryUrl": "https://rekor.sigstore.dev/api/v1/log/entries/..."
}
```
---
## 5. Storage
### 5.1 PostgreSQL Schema
```sql
CREATE TABLE authority.verdict_manifests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
manifest_id TEXT NOT NULL UNIQUE,
tenant TEXT NOT NULL,
-- Scope
asset_digest TEXT NOT NULL,
vulnerability_id TEXT NOT NULL,
-- Inputs (JSONB)
inputs_json JSONB NOT NULL,
-- Result
status TEXT NOT NULL,
confidence DOUBLE PRECISION NOT NULL,
result_json JSONB NOT NULL,
-- Policy context
policy_hash TEXT NOT NULL,
lattice_version TEXT NOT NULL,
-- Metadata
evaluated_at TIMESTAMPTZ NOT NULL,
manifest_digest TEXT NOT NULL,
-- Signature
signature_json JSONB,
rekor_log_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 5.2 Indexing Strategy
| Index | Purpose |
|-------|---------|
| `(tenant, asset_digest, vulnerability_id)` | Primary lookup |
| `(tenant, policy_hash, lattice_version)` | Policy version queries |
| `(evaluated_at)` BRIN | Time-based queries |
| Unique: `(tenant, asset_digest, vulnerability_id, policy_hash, lattice_version)` | Ensure one verdict per configuration |
---
## 6. Replay Verification
### 6.1 Verification Protocol
1. Retrieve stored manifest by ID
2. Fetch pinned inputs (SBOM, VEX docs, feeds) by digest
3. Re-execute trust lattice evaluation with identical inputs
4. Compare result with stored verdict
5. Verify signature if present
### 6.2 Verification Request
```http
POST /api/v1/authority/verdicts/{manifestId}/replay
Authorization: Bearer <token>
```
### 6.3 Verification Response
```json
{
"success": true,
"originalManifest": { ... },
"replayedManifest": { ... },
"differences": [],
"signatureValid": true,
"verifiedAt": "2025-12-22T15:30:00Z"
}
```
### 6.4 Failure Handling
When replay produces different results:
```json
{
"success": false,
"originalManifest": { ... },
"replayedManifest": { ... },
"differences": [
{
"field": "result.confidence",
"original": 0.82,
"replayed": 0.79,
"reason": "VEX document digest mismatch"
}
],
"signatureValid": true,
"error": "Verdict replay produced different confidence score"
}
```
---
## 7. API Reference
### Get Verdict Manifest
```http
GET /api/v1/authority/verdicts/{manifestId}
Authorization: Bearer <token>
Response: 200 OK
{
"manifest": { ... },
"signature": { ... },
"rekorEntry": { ... }
}
```
### List Verdicts by Scope
```http
GET /api/v1/authority/verdicts?assetDigest={digest}&vulnerabilityId={cve}
Authorization: Bearer <token>
Response: 200 OK
{
"verdicts": [ ... ],
"pageToken": "..."
}
```
### Replay Verdict
```http
POST /api/v1/authority/verdicts/{manifestId}/replay
Authorization: Bearer <token>
Response: 200 OK
{
"success": true,
...
}
```
### Download Signed Manifest
```http
GET /api/v1/authority/verdicts/{manifestId}/download
Authorization: Bearer <token>
Response: 200 OK
Content-Type: application/vnd.stellaops.verdict+json
Content-Disposition: attachment; filename="verdict-{manifestId}.json"
```
---
## 8. JSON Schema
### verdict-manifest.schema.json
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/schemas/verdict-manifest/1.0.0",
"type": "object",
"required": [
"manifestId",
"tenant",
"assetDigest",
"vulnerabilityId",
"inputs",
"result",
"policyHash",
"latticeVersion",
"evaluatedAt",
"manifestDigest"
],
"properties": {
"manifestId": {
"type": "string",
"pattern": "^verd:[a-z0-9-]+:[a-f0-9]+:[A-Z0-9-]+:[0-9]+$"
},
"tenant": { "type": "string", "minLength": 1 },
"assetDigest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"vulnerabilityId": { "type": "string", "minLength": 1 },
"inputs": { "$ref": "#/$defs/VerdictInputs" },
"result": { "$ref": "#/$defs/VerdictResult" },
"policyHash": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
},
"latticeVersion": {
"type": "string",
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
},
"evaluatedAt": {
"type": "string",
"format": "date-time"
},
"manifestDigest": {
"type": "string",
"pattern": "^sha256:[a-f0-9]{64}$"
}
},
"$defs": {
"VerdictInputs": {
"type": "object",
"required": ["sbomDigests", "vulnFeedSnapshotIds", "vexDocumentDigests", "clockCutoff"],
"properties": {
"sbomDigests": {
"type": "array",
"items": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }
},
"vulnFeedSnapshotIds": {
"type": "array",
"items": { "type": "string" }
},
"vexDocumentDigests": {
"type": "array",
"items": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }
},
"reachabilityGraphIds": {
"type": "array",
"items": { "type": "string" }
},
"clockCutoff": {
"type": "string",
"format": "date-time"
}
}
},
"VerdictResult": {
"type": "object",
"required": ["status", "confidence", "explanations"],
"properties": {
"status": {
"type": "string",
"enum": ["affected", "not_affected", "fixed", "under_investigation"]
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"explanations": {
"type": "array",
"items": { "$ref": "#/$defs/VerdictExplanation" }
},
"evidenceRefs": {
"type": "array",
"items": { "type": "string" }
}
}
},
"VerdictExplanation": {
"type": "object",
"required": ["sourceId", "reason", "claimScore"],
"properties": {
"sourceId": { "type": "string" },
"reason": { "type": "string" },
"provenanceScore": { "type": "number", "minimum": 0, "maximum": 1 },
"coverageScore": { "type": "number", "minimum": 0, "maximum": 1 },
"replayabilityScore": { "type": "number", "minimum": 0, "maximum": 1 },
"strengthMultiplier": { "type": "number", "minimum": 0, "maximum": 1 },
"freshnessMultiplier": { "type": "number", "minimum": 0, "maximum": 1 },
"claimScore": { "type": "number", "minimum": 0, "maximum": 1 }
}
}
}
}
```
---
## Related Documentation
- [Trust Lattice Specification](../excititor/trust-lattice.md)
- [Authority Architecture](./architecture.md)
- [DSSE Signing](../../dev/dsse-signing.md)
- [API Reference](../../09_API_CLI_REFERENCE.md)

View File

@@ -0,0 +1,444 @@
# Benchmark Module Architecture
## Overview
The Benchmark module provides infrastructure for validating and demonstrating Stella Ops' competitive advantages through automated comparison against other container security scanners (Trivy, Grype, Syft, etc.).
**Module Path**: `src/Scanner/__Libraries/StellaOps.Scanner.Benchmark/`
**Status**: PLANNED (Sprint 7000.0001.0001)
---
## Mission
Establish verifiable, reproducible benchmarks that:
1. Validate competitive claims with evidence
2. Detect regressions in accuracy or performance
3. Generate marketing-ready comparison materials
4. Provide ground-truth corpus for testing
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Benchmark Module │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Corpus │ │ Harness │ │ Metrics │ │
│ │ Manager │───▶│ Runner │───▶│ Calculator │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Ground Truth │ │ Competitor │ │ Claims │ │
│ │ Manifest │ │ Adapters │ │ Index │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## Components
### 1. Corpus Manager
**Namespace**: `StellaOps.Scanner.Benchmark.Corpus`
Manages the ground-truth corpus of container images with known vulnerabilities.
```csharp
public interface ICorpusManager
{
Task<Corpus> LoadCorpusAsync(string corpusPath, CancellationToken ct);
Task<CorpusImage> GetImageAsync(string digest, CancellationToken ct);
Task<GroundTruth> GetGroundTruthAsync(string digest, CancellationToken ct);
}
public record Corpus(
string Version,
DateTimeOffset CreatedAt,
ImmutableArray<CorpusImage> Images
);
public record CorpusImage(
string Digest,
string Name,
string Tag,
CorpusCategory Category,
GroundTruth GroundTruth
);
public record GroundTruth(
ImmutableArray<string> TruePositives,
ImmutableArray<string> KnownFalsePositives,
ImmutableArray<string> Notes
);
public enum CorpusCategory
{
BaseOS, // Alpine, Debian, Ubuntu, RHEL
ApplicationNode, // Node.js applications
ApplicationPython,// Python applications
ApplicationJava, // Java applications
ApplicationDotNet,// .NET applications
BackportScenario, // Known backported fixes
Unreachable // Known unreachable vulns
}
```
### 2. Harness Runner
**Namespace**: `StellaOps.Scanner.Benchmark.Harness`
Executes scans using Stella Ops and competitor tools.
```csharp
public interface IHarnessRunner
{
Task<BenchmarkRun> RunAsync(
Corpus corpus,
ImmutableArray<ITool> tools,
BenchmarkOptions options,
CancellationToken ct
);
}
public interface ITool
{
string Name { get; }
string Version { get; }
Task<ToolResult> ScanAsync(string imageRef, CancellationToken ct);
}
public record BenchmarkRun(
string RunId,
DateTimeOffset StartedAt,
DateTimeOffset CompletedAt,
ImmutableArray<ToolResult> Results
);
public record ToolResult(
string ToolName,
string ToolVersion,
string ImageDigest,
ImmutableArray<NormalizedFinding> Findings,
TimeSpan Duration
);
```
### 3. Competitor Adapters
**Namespace**: `StellaOps.Scanner.Benchmark.Adapters`
Normalize output from competitor tools.
```csharp
public interface ICompetitorAdapter : ITool
{
Task<ImmutableArray<NormalizedFinding>> ParseOutputAsync(
string output,
CancellationToken ct
);
}
// Implementations
public class TrivyAdapter : ICompetitorAdapter { }
public class GrypeAdapter : ICompetitorAdapter { }
public class SyftAdapter : ICompetitorAdapter { }
public class StellaOpsAdapter : ICompetitorAdapter { }
```
### 4. Metrics Calculator
**Namespace**: `StellaOps.Scanner.Benchmark.Metrics`
Calculate precision, recall, F1, and other metrics.
```csharp
public interface IMetricsCalculator
{
BenchmarkMetrics Calculate(
ToolResult result,
GroundTruth groundTruth
);
ComparativeMetrics Compare(
BenchmarkMetrics baseline,
BenchmarkMetrics comparison
);
}
public record BenchmarkMetrics(
int TruePositives,
int FalsePositives,
int TrueNegatives,
int FalseNegatives,
double Precision,
double Recall,
double F1Score,
ImmutableDictionary<string, BenchmarkMetrics> ByCategory
);
public record ComparativeMetrics(
string BaselineTool,
string ComparisonTool,
double PrecisionDelta,
double RecallDelta,
double F1Delta,
ImmutableArray<string> UniqueFindings,
ImmutableArray<string> MissedFindings
);
```
### 5. Claims Index
**Namespace**: `StellaOps.Scanner.Benchmark.Claims`
Manage verifiable claims with evidence links.
```csharp
public interface IClaimsIndex
{
Task<ImmutableArray<Claim>> GetAllClaimsAsync(CancellationToken ct);
Task<ClaimVerification> VerifyClaimAsync(string claimId, CancellationToken ct);
Task UpdateClaimsAsync(BenchmarkRun run, CancellationToken ct);
}
public record Claim(
string Id,
ClaimCategory Category,
string Statement,
string EvidencePath,
ClaimStatus Status,
DateTimeOffset LastVerified
);
public enum ClaimStatus { Pending, Verified, Published, Disputed, Resolved }
public record ClaimVerification(
string ClaimId,
bool IsValid,
string? Evidence,
string? FailureReason
);
```
---
## Data Flow
```
┌────────────────┐
│ Corpus Images │
│ (50+ images) │
└───────┬────────┘
┌────────────────┐ ┌────────────────┐
│ Stella Ops Scan│ │ Trivy/Grype │
│ │ │ Scan │
└───────┬────────┘ └───────┬────────┘
│ │
▼ ▼
┌────────────────┐ ┌────────────────┐
│ Normalized │ │ Normalized │
│ Findings │ │ Findings │
└───────┬────────┘ └───────┬────────┘
│ │
└──────────┬───────────┘
┌──────────────┐
│ Ground Truth │
│ Comparison │
└──────┬───────┘
┌──────────────┐
│ Metrics │
│ (P/R/F1) │
└──────┬───────┘
┌──────────────┐
│ Claims Index │
│ Update │
└──────────────┘
```
---
## Corpus Structure
```
bench/competitors/
├── corpus/
│ ├── manifest.json # Corpus metadata
│ ├── ground-truth/
│ │ ├── alpine-3.18.json # Per-image ground truth
│ │ ├── debian-bookworm.json
│ │ └── ...
│ └── images/
│ ├── base-os/
│ ├── applications/
│ └── edge-cases/
├── results/
│ ├── 2025-12-22/
│ │ ├── stellaops.json
│ │ ├── trivy.json
│ │ ├── grype.json
│ │ └── comparison.json
│ └── latest -> 2025-12-22/
└── fixtures/
└── adapters/ # Test fixtures for adapters
```
---
## Ground Truth Format
```json
{
"imageDigest": "sha256:abc123...",
"imageName": "alpine:3.18",
"category": "BaseOS",
"groundTruth": {
"truePositives": [
{
"cveId": "CVE-2024-1234",
"package": "openssl",
"version": "3.0.8",
"notes": "Fixed in 3.0.9"
}
],
"knownFalsePositives": [
{
"cveId": "CVE-2024-9999",
"package": "zlib",
"version": "1.2.13",
"reason": "Backported in alpine:3.18"
}
],
"expectedUnreachable": [
{
"cveId": "CVE-2024-5678",
"package": "curl",
"reason": "Vulnerable function not linked"
}
]
},
"lastVerified": "2025-12-01T00:00:00Z",
"verifiedBy": "security-team"
}
```
---
## CI Integration
### Workflow: `benchmark-vs-competitors.yml`
```yaml
name: Competitive Benchmark
on:
schedule:
- cron: '0 2 * * 0' # Weekly Sunday 2 AM
workflow_dispatch:
push:
paths:
- 'src/Scanner/__Libraries/StellaOps.Scanner.Benchmark/**'
- 'bench/competitors/**'
jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install competitor tools
run: |
# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# Install Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh
- name: Run benchmark
run: stella benchmark run --corpus bench/competitors/corpus --output bench/competitors/results/$(date +%Y-%m-%d)
- name: Update claims index
run: stella benchmark claims --output docs/claims-index.md
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: bench/competitors/results/
```
---
## CLI Commands
```bash
# Run full benchmark
stella benchmark run --corpus <path> --competitors trivy,grype,syft
# Verify a specific claim
stella benchmark verify <CLAIM_ID>
# Generate claims index
stella benchmark claims --output docs/claims-index.md
# Generate marketing battlecard
stella benchmark battlecard --output docs/marketing/battlecard.md
# Show comparison summary
stella benchmark summary --format table|json|markdown
```
---
## Testing
| Test Type | Location | Purpose |
|-----------|----------|---------|
| Unit | `StellaOps.Scanner.Benchmark.Tests/` | Adapter parsing, metrics calculation |
| Integration | `StellaOps.Scanner.Benchmark.Integration.Tests/` | Full benchmark flow |
| Golden | `bench/competitors/fixtures/` | Deterministic output verification |
---
## Security Considerations
1. **Competitor binaries**: Run in isolated containers, no network access during scan
2. **Corpus images**: Verified digests, no external pulls during benchmark
3. **Results**: Signed with DSSE before publishing
4. **Claims**: Require PR review before status change
---
## Dependencies
- `StellaOps.Scanner.Core` - Normalized finding models
- `StellaOps.Attestor.Dsse` - Result signing
- Docker - Competitor tool execution
- Ground-truth corpus (maintained separately)
---
## Related Documentation
- [Claims Index](../../claims-index.md)
- [Sprint 7000.0001.0001](../../implplan/SPRINT_7000_0001_0001_competitive_benchmarking.md)
- [Testing Strategy](../../implplan/SPRINT_5100_SUMMARY.md)
---
*Document Version*: 1.0.0
*Created*: 2025-12-22

View File

@@ -0,0 +1,460 @@
# VEX Trust Lattice Specification
> **Status**: Draft (Sprint 7100)
> **Last Updated**: 2025-12-22
> **Source Advisory**: `docs/product-advisories/archived/22-Dec-2026 - Building a Trust Lattice for VEX Sources.md`
## 1. Overview
The VEX Trust Lattice provides a mathematically rigorous framework for converting heterogeneous VEX claims from multiple sources into a single, signed, reproducible verdict with a numeric confidence and a complete audit trail.
### Goals
1. **Explainability**: Every verdict includes a full breakdown of how it was computed
2. **Reproducibility**: Same inputs always produce identical verdicts (deterministic)
3. **Auditability**: Signed verdict manifests with pinned inputs for regulatory compliance
4. **Tunability**: Per-tenant, per-source trust configuration without code changes
### Non-Goals
- Real-time vulnerability detection (handled by Scanner)
- VEX document ingestion (handled by Excititor core)
- Policy enforcement (handled by Policy Engine)
---
## 2. Trust Vector Model
Each VEX source is assigned a 3-component trust vector scored in the range [0..1].
### 2.1 Provenance (P)
Measures cryptographic and process integrity of the source.
| Score | Description |
|-------|-------------|
| 1.00 | DSSE-signed, timestamped, Rekor/Git anchored, key in allow-list, rotation policy OK |
| 0.75 | DSSE-signed + public key known, but no transparency log |
| 0.40 | Unsigned but retrieved via authenticated, immutable artifact repo |
| 0.10 | Opaque/CSV/email/manual import |
### 2.2 Coverage (C)
Measures how well the statement's scope maps to the target asset.
| Score | Description |
|-------|-------------|
| 1.00 | Exact package + version/build digest + feature/flag context matched |
| 0.75 | Exact package + version range matched; partial feature context |
| 0.50 | Product-level only; maps via CPE/PURL family |
| 0.25 | Family-level heuristics; no version proof |
### 2.3 Replayability (R)
Measures whether the claim can be deterministically re-derived.
| Score | Description |
|-------|-------------|
| 1.00 | All inputs pinned (feeds, SBOM hash, ruleset hash, lattice version); replays byte-identical |
| 0.60 | Inputs mostly pinned; non-deterministic ordering tolerated but stable outcome |
| 0.20 | Ephemeral APIs; no snapshot |
### 2.4 Weight Configuration
The base trust score is computed as:
```
BaseTrust(S) = wP * P + wC * C + wR * R
```
**Default weights:**
- `wP = 0.45` (Provenance)
- `wC = 0.35` (Coverage)
- `wR = 0.20` (Replayability)
Weights are tunable per policy and sum to 1.0.
---
## 3. Claim Scoring
### 3.1 Base Trust Calculation
```csharp
double BaseTrust(double P, double C, double R, TrustWeights W)
=> W.wP * P + W.wC * C + W.wR * R;
```
### 3.2 Claim Strength Multipliers (M)
Each VEX claim carries a strength multiplier based on evidence quality:
| Strength | Value | Description |
|----------|-------|-------------|
| ExploitabilityWithReachability | 1.00 | Exploitability analysis + reachability proof subgraph provided |
| ConfigWithEvidence | 0.80 | Config/feature-flag reason with evidence |
| VendorBlanket | 0.60 | Vendor blanket statement |
| UnderInvestigation | 0.40 | "Under investigation" |
### 3.3 Freshness Decay (F)
Time-decay curve with configurable half-life:
```csharp
double Freshness(DateTime issuedAt, DateTime cutoff, double halfLifeDays = 90, double floor = 0.35)
{
var ageDays = (cutoff - issuedAt).TotalDays;
var decay = Math.Exp(-Math.Log(2) * ageDays / halfLifeDays);
return Math.Max(decay, floor);
}
```
**Parameters:**
- `halfLifeDays = 90` (default): Score halves every 90 days
- `floor = 0.35` (default): Minimum freshness unless revoked
### 3.4 ClaimScore Formula
```
ClaimScore = BaseTrust(S) * M * F
```
**Example calculation:**
```
Source: Red Hat (Vendor)
P = 0.90, C = 0.75, R = 0.60
BaseTrust = 0.45*0.90 + 0.35*0.75 + 0.20*0.60 = 0.405 + 0.2625 + 0.12 = 0.7875
Claim: ConfigWithEvidence (M = 0.80)
Freshness: 30 days old (F = 0.79)
ClaimScore = 0.7875 * 0.80 * 0.79 = 0.498
```
---
## 4. Lattice Merge Algorithm
### 4.1 Partial Ordering
Claims are ordered by a tuple: `(scope_specificity, ClaimScore)`.
Scope specificity levels:
1. Exact digest match (highest)
2. Exact version match
3. Version range match
4. Product family match
5. Platform match (lowest)
### 4.2 Conflict Detection
Conflicts occur when claims for the same (CVE, Asset) have different statuses:
```csharp
bool HasConflict(IEnumerable<Claim> claims)
=> claims.Select(c => c.Status).Distinct().Count() > 1;
```
### 4.3 Conflict Penalty
When conflicts exist, apply a penalty to weaker/older claims:
```csharp
const double ConflictPenalty = 0.25;
if (contradictory)
{
var strongest = claims.OrderByDescending(c => c.Score).First();
foreach (var claim in claims.Where(c => c.Status != strongest.Status))
{
claim.AdjustedScore = claim.Score * (1 - ConflictPenalty);
}
}
```
### 4.4 Winner Selection
Final verdict is selected by:
```csharp
var winner = scored
.OrderByDescending(x => (x.Claim.ScopeSpecificity, x.AdjustedScore))
.First();
```
### 4.5 Audit Trail Generation
Every merge produces:
```csharp
public sealed record MergeResult
{
public VexStatus Status { get; init; }
public double Confidence { get; init; }
public ImmutableArray<VerdictExplanation> Explanations { get; init; }
public ImmutableArray<string> EvidenceRefs { get; init; }
public string PolicyHash { get; init; }
public string LatticeVersion { get; init; }
}
```
---
## 5. Policy Gates
Gates are evaluated after merge to enforce policy requirements.
### 5.1 MinimumConfidenceGate
Requires minimum confidence by environment for certain statuses.
```yaml
gates:
minimumConfidence:
enabled: true
thresholds:
production: 0.75
staging: 0.60
development: 0.40
applyToStatuses:
- not_affected
- fixed
```
**Behavior**: Fails if confidence < threshold for specified statuses.
### 5.2 UnknownsBudgetGate
Limits exposure to unknown/unscored dependencies.
```yaml
gates:
unknownsBudget:
enabled: true
maxUnknownCount: 5
maxCumulativeUncertainty: 2.0
```
**Behavior**: Fails if:
- `#unknown_deps > maxUnknownCount`, OR
- `sum(1 - ClaimScore) > maxCumulativeUncertainty`
### 5.3 SourceQuotaGate
Prevents single-source dominance without corroboration.
```yaml
gates:
sourceQuota:
enabled: true
maxInfluencePercent: 60
corroborationDelta: 0.10
```
**Behavior**: Fails if single source influence > 60% AND no second source within delta=0.10.
### 5.4 ReachabilityRequirementGate
Requires reachability proof for critical vulnerabilities.
```yaml
gates:
reachabilityRequirement:
enabled: true
severityThreshold: CRITICAL
requiredForStatuses:
- not_affected
bypassReasons:
- component_not_present
```
**Behavior**: Fails if `not_affected` on CRITICAL CVE without reachability proof (unless bypass reason applies).
---
## 6. Deterministic Replay
### 6.1 Input Pinning
To guarantee "same inputs → same verdict", pin:
- SBOM digest(s)
- Vuln feed snapshot IDs
- VEX document digests
- Reachability graph IDs
- Policy file hash
- Lattice version
- Clock cutoff (evaluation timestamp)
### 6.2 Verdict Manifest
```json
{
"manifestId": "verd:tenant:asset:cve:1234567890",
"tenant": "acme-corp",
"assetDigest": "sha256:abc123...",
"vulnerabilityId": "CVE-2025-12345",
"inputs": {
"sbomDigests": ["sha256:..."],
"vulnFeedSnapshotIds": ["nvd:2025-12-22"],
"vexDocumentDigests": ["sha256:..."],
"reachabilityGraphIds": ["graph:..."],
"clockCutoff": "2025-12-22T12:00:00Z"
},
"result": {
"status": "not_affected",
"confidence": 0.82,
"explanations": [...]
},
"policyHash": "sha256:...",
"latticeVersion": "1.2.0",
"evaluatedAt": "2025-12-22T12:00:01Z",
"manifestDigest": "sha256:..."
}
```
### 6.3 Signing
Verdict manifests are signed using DSSE with predicate type:
```
https://stella-ops.org/attestations/vex-verdict/1
```
### 6.4 Replay Verification
```
POST /api/v1/authority/verdicts/{manifestId}/replay
Response:
{
"success": true,
"originalManifest": {...},
"replayedManifest": {...},
"differences": [],
"signatureValid": true
}
```
---
## 7. Configuration Reference
### Full Configuration Example
```yaml
# etc/trust-lattice.yaml
version: "1.0"
trustLattice:
weights:
provenance: 0.45
coverage: 0.35
replayability: 0.20
freshness:
halfLifeDays: 90
floor: 0.35
conflictPenalty: 0.25
defaults:
vendor:
provenance: 0.90
coverage: 0.70
replayability: 0.60
distro:
provenance: 0.80
coverage: 0.85
replayability: 0.60
internal:
provenance: 0.85
coverage: 0.95
replayability: 0.90
gates:
minimumConfidence:
enabled: true
thresholds:
production: 0.75
staging: 0.60
development: 0.40
unknownsBudget:
enabled: true
maxUnknownCount: 5
maxCumulativeUncertainty: 2.0
sourceQuota:
enabled: true
maxInfluencePercent: 60
corroborationDelta: 0.10
reachabilityRequirement:
enabled: true
severityThreshold: CRITICAL
```
---
## 8. API Reference
### Endpoints
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/excititor/verdicts/{manifestId}` | Get verdict manifest |
| GET | `/api/v1/excititor/verdicts` | List verdicts (paginated) |
| POST | `/api/v1/authority/verdicts/{manifestId}/replay` | Verify replay |
| GET | `/api/v1/authority/verdicts/{manifestId}/download` | Download signed manifest |
See `docs/09_API_CLI_REFERENCE.md` for complete API documentation.
---
## 9. Examples
### Example 1: High-Confidence Verdict
**Input:**
- Red Hat VEX: `not_affected` with `component_not_present`
- Ubuntu VEX: `not_affected` with `component_not_present`
**Calculation:**
```
Red Hat: BaseTrust=0.78, M=0.80, F=0.95 → ClaimScore=0.59
Ubuntu: BaseTrust=0.72, M=0.80, F=0.90 → ClaimScore=0.52
No conflict (both agree)
Winner: Red Hat (higher score)
Confidence: 0.59
Gates: All pass (> 0.40 threshold)
```
### Example 2: Conflict Resolution
**Input:**
- Vendor VEX: `not_affected`
- Internal scan: `affected`
**Calculation:**
```
Vendor: ClaimScore=0.65
Internal: ClaimScore=0.55
Conflict detected → penalty applied
Internal adjusted: 0.55 * 0.75 = 0.41
Winner: Vendor
Confidence: 0.65
Note: Conflict recorded in audit trail
```
---
## Related Documentation
- [Excititor Architecture](./architecture.md)
- [Verdict Manifest Specification](../authority/verdict-manifest.md)
- [Policy Gates Configuration](../policy/architecture.md)
- [API Reference](../../09_API_CLI_REFERENCE.md)

View File

@@ -0,0 +1,212 @@
# Explainable Triage Workflows - Implementation Plan
## Executive Summary
This document outlines the implementation plan for delivering **Explainable Triage Workflows** as defined in the product advisory dated 21-Dec-2025. The capability set enables vulnerability-first, policy-backed, reachability-informed verdicts with full explainability and auditability.
## Vision
> Every vulnerability finding must resolve to a **policy-backed, reachability-informed, runtime-corroborated verdict** that is **exportable as one signed attestation attached to the built artifact**.
## Current State Analysis
### Already Implemented (75%)
| Capability | Implementation | Completeness |
|------------|----------------|--------------|
| Reachability analysis | 10 language analyzers, binary, runtime | 95% |
| VEX processing | OpenVEX, CSAF, CycloneDX with lattice | 90% |
| Explainability | ExplainTrace with rule steps | 95% |
| Evidence generation | Path witnesses, rich graphs | 90% |
| Audit trails | Immutable ledger with chain integrity | 85% |
| Policy gates | 4-stage gate system | 95% |
| Attestations | 7 predicate types with DSSE | 90% |
| Runtime capture | eBPF, dyld, ETW | 85% |
### Already Planned (15%)
| Capability | Sprint | Status |
|------------|--------|--------|
| Risk Verdict Attestation | SPRINT_4100_0003_0001 | TODO |
| OCI Attachment | SPRINT_4100_0003_0002 | TODO |
| Counterfactuals | SPRINT_4200_0002_0005 | TODO |
| Replay Engine | SPRINT_4100_0002_0002 | TODO |
| Knowledge Snapshot | SPRINT_4100_0002_0001 | TODO |
| Audit Pack Export | SPRINT_5100_0006_0001 | TODO |
| Unknown Budgets | SPRINT_4100_0001_0002 | TODO |
### Net New Gaps (10%)
| Gap | Sprint | Story Points |
|-----|--------|--------------|
| Unified Confidence Model | 7000.0002.0001 | 13 |
| Vulnerability-First UX API | 7000.0002.0002 | 13 |
| Evidence Graph API | 7000.0003.0001 | 8 |
| Reachability Mini-Map | 7000.0003.0002 | 5 |
| Runtime Timeline | 7000.0003.0003 | 5 |
| Progressive Fidelity | 7000.0004.0001 | 13 |
| Evidence Size Budgets | 7000.0004.0002 | 8 |
| Quality KPIs | 7000.0005.0001 | 8 |
## Implementation Roadmap
### Phase 1: Foundation (Existing + New)
**Objective**: Establish core verdict and confidence infrastructure.
**Sprints**:
- SPRINT_4100_0003_0001: Risk Verdict Attestation
- SPRINT_4100_0002_0001: Knowledge Snapshot Manifest
- SPRINT_7000_0002_0001: Unified Confidence Model
- SPRINT_7000_0004_0001: Progressive Fidelity (parallel)
- SPRINT_7000_0004_0002: Evidence Budgets (parallel)
**Key Deliverables**:
- `RiskVerdictAttestation` model with PASS/FAIL/PASS_WITH_EXCEPTIONS/INDETERMINATE
- `ConfidenceScore` with 5-factor breakdown
- `FidelityLevel` enum with Quick/Standard/Deep modes
- `EvidenceBudget` with retention tiers
### Phase 2: UX Layer
**Objective**: Deliver vulnerability-first presentation layer.
**Sprints**:
- SPRINT_7000_0002_0002: Vulnerability-First UX API
**Key Deliverables**:
- `FindingSummaryResponse` with verdict chip, confidence, one-liner
- `ProofBadges` (Reachability, Runtime, Policy, Provenance)
- `GET /api/v1/findings` list endpoint
- `GET /api/v1/findings/{id}/summary` detail endpoint
### Phase 3: Visualization APIs
**Objective**: Enable evidence exploration and click-through.
**Sprints** (parallelizable):
- SPRINT_7000_0003_0001: Evidence Graph API
- SPRINT_7000_0003_0002: Reachability Mini-Map API
- SPRINT_7000_0003_0003: Runtime Timeline API
**Key Deliverables**:
- `GET /api/v1/findings/{id}/evidence-graph`
- `GET /api/v1/findings/{id}/reachability-map`
- `GET /api/v1/findings/{id}/runtime-timeline`
### Phase 4: Metrics & Observability
**Objective**: Track quality KPIs for continuous improvement.
**Sprints**:
- SPRINT_7000_0005_0001: Quality KPIs Tracking
**Key Deliverables**:
- `TriageQualityKpis` model
- `GET /api/v1/metrics/kpis` dashboard endpoint
- Trend tracking over time
## Architecture Changes
### New Libraries
```
src/
├── Policy/
│ └── __Libraries/
│ └── StellaOps.Policy.Confidence/ # NEW: Confidence model
│ ├── Models/
│ ├── Services/
│ └── Configuration/
├── Scanner/
│ └── __Libraries/
│ └── StellaOps.Scanner.Orchestration/ # NEW: Fidelity orchestration
│ └── Fidelity/
├── Findings/
│ └── StellaOps.Findings.WebService/ # EXTEND: UX APIs
│ ├── Contracts/
│ ├── Services/
│ └── Endpoints/
├── Evidence/ # NEW: Evidence management
│ └── StellaOps.Evidence/
│ ├── Budgets/
│ └── Retention/
└── Metrics/ # NEW: KPI tracking
└── StellaOps.Metrics/
└── Kpi/
```
### Database Changes
| Table | Purpose |
|-------|---------|
| `confidence_factors` | Store factor breakdown per verdict |
| `evidence_items` | Track evidence with size and tier |
| `kpi_counters` | Real-time KPI counters |
| `kpi_snapshots` | Daily KPI snapshots |
### API Surface
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/api/v1/findings` | GET | List findings with summaries |
| `/api/v1/findings/{id}/summary` | GET | Detailed finding summary |
| `/api/v1/findings/{id}/evidence-graph` | GET | Evidence graph |
| `/api/v1/findings/{id}/reachability-map` | GET | Reachability mini-map |
| `/api/v1/findings/{id}/runtime-timeline` | GET | Runtime timeline |
| `/api/v1/scan/analyze` | POST | Analyze with fidelity level |
| `/api/v1/scan/findings/{id}/upgrade` | POST | Upgrade fidelity |
| `/api/v1/metrics/kpis` | GET | Quality KPIs |
## Non-Negotiables
From the advisory:
1. **Vulnerability-first UX**: Users start from CVE/finding and immediately see applicability, reachability, runtime corroboration, and policy rationale.
2. **Single canonical verdict artifact**: One built-in, signed verdict attestation per subject (OCI digest), replayable.
3. **Deterministic evidence**: Evidence objects are content-hashed and versioned.
4. **Unknowns are first-class**: "Unknown reachability/runtime/config" is not hidden; it is budgeted and policy-controlled.
## Quality KPIs
| KPI | Target | Measurement |
|-----|--------|-------------|
| % non-UNKNOWN reachability | >80% | Weekly |
| % runtime corroboration | >50% (where sensor deployed) | Weekly |
| Explainability completeness | >95% | Weekly |
| Replay success rate | >99% | Weekly |
| Median time to verdict | <5 min | Daily |
## Risk Management
| Risk | Impact | Mitigation |
|------|--------|------------|
| Confidence model complexity | High | Start simple (3 factors), iterate |
| Deep analysis performance | Medium | Progressive fidelity with timeouts |
| Evidence storage growth | Medium | Budget enforcement + tier pruning |
| API backward compatibility | Low | Versioned endpoints |
## Definition of Done
Per advisory, a release is "done" only if:
- [ ] Build produces OCI artifact with attached **signed verdict attestation**
- [ ] Each verdict is **explainable** (reason steps + proof pointers)
- [ ] Reachability evidence stored as **reproducible subgraph** (or explicitly UNKNOWN with reason)
- [ ] Replay verification reproduces same verdict with pinned inputs
- [ ] UX starts from vulnerabilities and links directly to proofs and audit export
## References
- **Advisory**: `docs/product-advisories/archived/21-Dec-2025 - Designing Explainable Triage Workflows.md`
- **Sprint Summary**: `docs/implplan/SPRINT_7000_SUMMARY.md`
- **Individual Sprints**: `docs/implplan/SPRINT_7000_*.md`
## Revision History
| Date | Change | Author |
|------|--------|--------|
| 2025-12-22 | Initial implementation plan | Claude |

View File

@@ -0,0 +1,276 @@
# Moat Gap Analysis: StellaOps Competitive Position
> **Source Advisory**: 19-Dec-2025 - Stella Ops candidate features mapped to moat strength
> **Analysis Date**: 2025-12-22
> **Status**: Sprints created, implementation pending
---
## Executive Summary
This document captures the gap analysis between the competitive moat advisory and StellaOps' current implementation, along with the sprint plan to address identified gaps.
### Moat Scale Reference
| Rating | Definition |
|--------|------------|
| **5** | Structural moat — new primitives, strong defensibility, durable switching cost |
| **4** | Strong moat — difficult multi-domain engineering; incumbents have partial analogs |
| **3** | Moderate moat — others can build; differentiation is execution + packaging |
| **2** | Weak moat — table-stakes soon; limited defensibility |
| **1** | Commodity — widely available in OSS / easy to replicate |
---
## Feature Implementation Matrix
| Feature | Moat | Current % | Key Gaps | Sprint Coverage |
|---------|------|-----------|----------|-----------------|
| Signed, replayable risk verdicts | 5 | 70% | OCI push, one-command replay | 4300_0001_* |
| VEX decisioning engine | 4 | 85% | Evidence hooks | Minimal |
| Reachability with proof | 4 | 75% | Standalone artifact | 4400_0001_0002 |
| Smart-Diff semantic delta | 4 | 80% | Signed delta verdict | 4400_0001_0001 |
| Unknowns as first-class state | 4 | 75% | Policy budgets, attestations | 4300_0002_* |
| Air-gapped epistemic mode | 4 | 70% | Sealed snapshot workflow | 4300_0003_0001 |
| SBOM ledger + lineage | 3 | 60% | Historical tracking, BYOS | 4600_0001_* |
| Policy engine with proofs | 3 | 85% | Compilation to artifact | Minimal |
| VEX distribution network | 3-4 | 30% | Hub layer entirely | 4500_0001_* |
---
## Detailed Gap Analysis
### 1. Signed, Replayable Risk Verdicts (Moat 5)
**What exists:**
- `VerdictReceiptStatement` with in-toto predicate
- `ProofSpine` and `ProofChainBuilder` infrastructure
- `TrustLatticeEngine.Evaluate()` producing `ProofBundle`
- `ReplayManifest` and `ReplayVerifier`
- Input hashing (sbomDigest, feedsDigest, policyDigest)
**Gaps:**
| Gap | Sprint |
|-----|--------|
| Verdict as OCI-attached attestation | 4300_0001_0001 |
| One-command audit replay CLI | 4300_0001_0002 |
| Formal replay determinism tests | 4300_0001_0002 |
**Moat Thesis**: "We don't output findings; we output an attestable decision that can be replayed."
---
### 2. VEX Decisioning Engine (Moat 4)
**What exists:**
- `VexConsensusEngine` with 5 modes
- `TrustLatticeEngine` with K4 lattice atoms
- `TrustWeightEngine` for issuer weighting
- VEX normalizers for CycloneDX, OpenVEX, CSAF
- `VexLens` module with consensus rationale
**Gaps:**
| Gap | Sprint |
|-----|--------|
| Configurable evidence hooks | Minor enhancement |
**Moat Thesis**: "We treat VEX as a logical claim system, not a suppression file."
---
### 3. Reachability with Proof (Moat 4)
**What exists:**
- `ReachabilityWitnessStatement` attestation type
- `PathWitnessBuilder` for call-path proofs
- `CallPath` models with entrypoint → symbol chain
- `ReachabilityLattice` for state management
- `CompositeGateDetector` for boundary extraction
**Gaps:**
| Gap | Sprint |
|-----|--------|
| Standalone reachability subgraph as OCI artifact | 4400_0001_0002 |
| Binary-level reachability proof | 6000_* (existing) |
**Moat Thesis**: "We provide proof of exploitability in *this* artifact, not just a badge."
---
### 4. Smart-Diff Semantic Risk Delta (Moat 4)
**What exists:**
- `MaterialRiskChangeDetector` with R1-R4 rules
- `RiskStateSnapshot` capturing full finding state
- Detection of all flip types
- Priority scoring algorithm
- SARIF output generation
**Gaps:**
| Gap | Sprint |
|-----|--------|
| Signed delta verdict attestation | 4400_0001_0001 |
| Diff over reachability graphs | Future |
**Moat Thesis**: "We explain what changed in exploitable surface area, not what changed in CVE count."
---
### 5. Unknowns as First-Class State (Moat 4)
**What exists:**
- `UncertaintyTier` (T1-T4) with entropy classification
- `UnknownStateLedger` tracking marker kinds
- Risk modifiers from uncertainty
- `BlocksNotAffected()` gate on T1 tier
**Gaps:**
| Gap | Sprint |
|-----|--------|
| Policy rule: "fail if unknowns > N" | 4300_0002_0001 |
| Unknown budgets with decay | 4100_0001_0002 (existing) |
| Unknowns in attestations | 4300_0002_0002 |
**Moat Thesis**: "We quantify uncertainty and gate on it."
---
### 6. Air-Gapped Epistemic Mode (Moat 4)
**What exists:**
- `AirGap.Controller` with state management
- `ReplayVerifier` with depth levels
- `TrustStore` and `TufMetadataValidator`
- `EgressPolicy` enforcement
- `TimeAnchor` for offline time validation
**Gaps:**
| Gap | Sprint |
|-----|--------|
| Sealed knowledge snapshot export CLI | 4300_0003_0001 |
| One-command import + replay validation | 4300_0003_0001 |
| Feed snapshot versioning with merkle roots | 4300_0003_0001 |
**Moat Thesis**: Air-gapped "runtime" is common; air-gapped **reproducibility** is not.
---
### 7. SBOM Ledger + Lineage (Moat 3)
**What exists:**
- `SbomService` with versioning events
- `CatalogRecord` for storage
- `Graph` module for dependency indexing
- `SbomVersionEvents`
**Gaps:**
| Gap | Sprint |
|-----|--------|
| Historical SBOM tracking with diff lineage | 4600_0001_0001 |
| BYOS ingestion workflow with validation | 4600_0001_0002 |
| SBOM grouping by artifact family | 4600_0001_0001 |
**Moat Strategy**: Make the ledger valuable via **semantic diff, evidence joins, and provenance**.
---
### 8. Policy Engine with Proofs (Moat 3)
**What exists:**
- `PolicyEvaluation` with `PolicyExplanation`
- OPA/Rego integration
- `ProofBundle` generation from TrustLattice
- Evidence pointers in verdict statements
**Gaps:**
| Gap | Sprint |
|-----|--------|
| Policy compilation to standalone decision artifact | Minor enhancement |
**Moat Strategy**: Keep policy language small but rigorous; always emit evidence pointers.
---
### 9. VEX Distribution Network (Moat 3-4)
**What exists:**
- Excititor ingests from 7+ VEX sources
- `VexConnectorMetadata` for source tracking
**Gaps:**
| Gap | Sprint |
|-----|--------|
| VEX Hub aggregation layer | 4500_0001_0001 |
| Trust scoring of VEX sources | 4500_0001_0002 |
| VEX verification + validation pipeline | 4500_0001_0001 |
| API for VEX discovery/subscription | 4500_0001_0001 |
**Moat Strategy**: Differentiate with **verification + trust scoring** of VEX sources.
---
## Sprint Roadmap
### Phase 1: Moat 5 Anchor (P0)
```
4300_0001_0001 → 4300_0001_0002
└── Verdict becomes portable, replayable
```
### Phase 2: Moat 4 Hardening (P1)
```
4300_0002_0001 → 4300_0002_0002
└── Unknowns become actionable
4300_0003_0001
└── Air-gap becomes reproducible
4500_0001_0001 → 4500_0001_0002
└── VEX becomes distributable
```
### Phase 3: Moat 4 Extensions (P2)
```
4400_0001_0001 (Delta Verdict)
4400_0001_0002 (Reachability Artifact)
```
### Phase 4: Moat 3 Foundation (P2)
```
4600_0001_0001 → 4600_0001_0002
└── SBOM becomes historical
```
---
## Competitive Positioning Summary
### Where StellaOps Is Strong
1. **VEX decisioning** — Multi-mode consensus engine is ahead of competitors
2. **Smart-Diff** — R1-R4 rules with priority scoring is unique
3. **Policy engine** — OPA/Rego with proof output is mature
4. **Attestor** — in-toto/DSSE infrastructure is complete
### Where StellaOps Must Improve
1. **Verdict portability** — OCI push makes verdicts first-class artifacts
2. **Audit replay** — One-command replay is essential for compliance
3. **VEX distribution** — Hub layer creates network effects
4. **Unknown governance** — Policy budgets make uncertainty actionable
### Avoid Head-On Fights
- **Snyk**: Don't compete on developer UX; compete on proof-carrying reachability
- **Prisma**: Don't compete on CNAPP breadth; compete on decision integrity
- **Anchore**: Don't compete on SBOM storage; compete on semantic diff + VEX reasoning
---
## References
- **Sprints**: `docs/implplan/SPRINT_4300_*.md`, `SPRINT_4400_*.md`, `SPRINT_4500_*.md`, `SPRINT_4600_*.md`
- **Original Advisory**: `docs/product-advisories/archived/19-Dec-2025 - Stella Ops candidate features mapped to moat strength.md`
- **Architecture**: `docs/07_HIGH_LEVEL_ARCHITECTURE.md`

View File

@@ -0,0 +1,371 @@
# Reachability Drift Detection - Architecture
**Module:** Scanner
**Version:** 1.0
**Status:** Implemented (Sprint 3600.2-3600.3)
**Last Updated:** 2025-12-22
---
## 1. Overview
Reachability Drift Detection tracks function-level reachability changes between scans to identify when code modifications create new paths to vulnerable sinks or mitigate existing risks. This enables security teams to:
- **Detect regressions** when previously unreachable vulnerabilities become exploitable
- **Validate fixes** by confirming vulnerable code paths are removed
- **Prioritize triage** based on actual exploitability rather than theoretical risk
- **Automate VEX** by generating evidence-backed justifications
---
## 2. Key Concepts
### 2.1 Call Graph
A directed graph representing function/method call relationships in source code:
- **Nodes**: Functions, methods, lambdas with metadata (file, line, visibility)
- **Edges**: Call relationships with call kind (direct, virtual, delegate, reflection, dynamic)
- **Entrypoints**: Public-facing functions (HTTP handlers, CLI commands, message consumers)
- **Sinks**: Security-sensitive APIs (command execution, SQL, file I/O, deserialization)
### 2.2 Reachability Analysis
Multi-source BFS traversal from entrypoints to determine which sinks are exploitable:
```
Entrypoints (HTTP handlers, CLI)
▼ BFS traversal
[Application Code]
Sinks (exec, query, writeFile)
Reachable = TRUE if path exists
```
### 2.3 Drift Detection
Compares reachability between two scans (base vs head):
| Transition | Direction | Risk Impact |
|------------|-----------|-------------|
| Unreachable → Reachable | `became_reachable` | **Increased** - New exploit path |
| Reachable → Unreachable | `became_unreachable` | **Decreased** - Mitigation applied |
### 2.4 Cause Attribution
Explains *why* drift occurred by correlating with code changes:
| Cause Kind | Description | Example |
|------------|-------------|---------|
| `guard_removed` | Conditional check removed | `if (!authorized)` deleted |
| `guard_added` | New conditional blocks path | Added null check |
| `new_public_route` | New entrypoint created | Added `/api/admin` endpoint |
| `visibility_escalated` | Internal → Public | Method made public |
| `dependency_upgraded` | Library update changed behavior | lodash 4.x → 5.x |
| `symbol_removed` | Function deleted | Removed vulnerable helper |
| `unknown` | Cannot determine | Multiple simultaneous changes |
---
## 3. Data Flow
```mermaid
flowchart TD
subgraph Scan["Scan Execution"]
A[Source Code] --> B[Call Graph Extractor]
B --> C[CallGraphSnapshot]
end
subgraph Analysis["Drift Analysis"]
C --> D[Reachability Analyzer]
D --> E[ReachabilityResult]
F[Base Scan Graph] --> G[Drift Detector]
E --> G
H[Code Changes] --> G
G --> I[ReachabilityDriftResult]
end
subgraph Output["Output"]
I --> J[Path Compressor]
J --> K[Compressed Paths]
I --> L[Cause Explainer]
L --> M[Drift Causes]
K --> N[Storage/API]
M --> N
end
subgraph Integration["Integration"]
N --> O[Policy Gates]
N --> P[VEX Emission]
N --> Q[Web UI]
end
```
---
## 4. Component Architecture
### 4.1 Call Graph Extractors
Per-language AST analysis producing `CallGraphSnapshot`:
| Language | Extractor | Technology | Status |
|----------|-----------|------------|--------|
| .NET | `DotNetCallGraphExtractor` | Roslyn semantic model | **Done** |
| Java | `JavaCallGraphExtractor` | ASM bytecode analysis | **Done** |
| Go | `GoCallGraphExtractor` | golang.org/x/tools SSA | **Done** |
| Python | `PythonCallGraphExtractor` | Python AST | **Done** |
| Node.js | `NodeCallGraphExtractor` | Babel (planned) | Skeleton |
| PHP | `PhpCallGraphExtractor` | php-parser | **Done** |
| Ruby | `RubyCallGraphExtractor` | parser gem | **Done** |
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.CallGraph/Extraction/`
### 4.2 Reachability Analyzer
Multi-source BFS from entrypoints to sinks:
```csharp
public sealed class ReachabilityAnalyzer
{
public ReachabilityResult Analyze(CallGraphSnapshot graph);
}
public record ReachabilityResult
{
ImmutableHashSet<string> ReachableNodes { get; }
ImmutableArray<string> ReachableSinks { get; }
ImmutableDictionary<string, ImmutableArray<string>> ShortestPaths { get; }
}
```
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.CallGraph/Analysis/`
### 4.3 Drift Detector
Compares base and head graphs:
```csharp
public sealed class ReachabilityDriftDetector
{
public ReachabilityDriftResult Detect(
CallGraphSnapshot baseGraph,
CallGraphSnapshot headGraph,
IReadOnlyList<CodeChangeFact> codeChanges);
}
```
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/Services/`
### 4.4 Path Compressor
Reduces full paths to key nodes for storage/display:
```
Full Path (20 nodes):
entrypoint → A → B → C → ... → X → Y → sink
Compressed Path:
entrypoint → [changed: B] → [changed: X] → sink
(intermediateCount: 17)
```
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/Services/PathCompressor.cs`
### 4.5 Cause Explainer
Correlates drift with code changes:
```csharp
public sealed class DriftCauseExplainer
{
public DriftCause Explain(...);
public DriftCause ExplainUnreachable(...);
}
```
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/Services/DriftCauseExplainer.cs`
---
## 5. Language Support Matrix
| Feature | .NET | Java | Go | Python | Node.js | PHP | Ruby |
|---------|------|------|-------|--------|---------|-----|------|
| Function extraction | Yes | Yes | Yes | Yes | Partial | Yes | Yes |
| Call edge extraction | Yes | Yes | Yes | Yes | Partial | Yes | Yes |
| HTTP entrypoints | ASP.NET | Spring | net/http | Flask/Django | Express* | Laravel | Rails |
| gRPC entrypoints | Yes | Yes | Yes | Yes | No | No | No |
| CLI entrypoints | Yes | Yes | Yes | Yes | Partial | Yes | Yes |
| Sink detection | Yes | Yes | Yes | Yes | Partial | Yes | Yes |
*Requires Sprint 3600.4 completion
---
## 6. Storage Schema
### 6.1 PostgreSQL Tables
**call_graph_snapshots:**
```sql
CREATE TABLE call_graph_snapshots (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
scan_id TEXT NOT NULL,
language TEXT NOT NULL,
graph_digest TEXT NOT NULL,
node_count INT NOT NULL,
edge_count INT NOT NULL,
entrypoint_count INT NOT NULL,
sink_count INT NOT NULL,
extracted_at TIMESTAMPTZ NOT NULL,
snapshot_json JSONB NOT NULL
);
```
**reachability_drift_results:**
```sql
CREATE TABLE reachability_drift_results (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
base_scan_id TEXT NOT NULL,
head_scan_id TEXT NOT NULL,
language TEXT NOT NULL,
newly_reachable_count INT NOT NULL,
newly_unreachable_count INT NOT NULL,
detected_at TIMESTAMPTZ NOT NULL,
result_digest TEXT NOT NULL
);
```
**drifted_sinks:**
```sql
CREATE TABLE drifted_sinks (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
drift_result_id UUID NOT NULL REFERENCES reachability_drift_results(id),
sink_node_id TEXT NOT NULL,
symbol TEXT NOT NULL,
sink_category TEXT NOT NULL,
direction TEXT NOT NULL,
cause_kind TEXT NOT NULL,
cause_description TEXT NOT NULL,
compressed_path JSONB NOT NULL,
associated_vulns JSONB
);
```
**code_changes:**
```sql
CREATE TABLE code_changes (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
scan_id TEXT NOT NULL,
base_scan_id TEXT NOT NULL,
language TEXT NOT NULL,
file TEXT NOT NULL,
symbol TEXT NOT NULL,
change_kind TEXT NOT NULL,
details JSONB,
detected_at TIMESTAMPTZ NOT NULL
);
```
### 6.2 Valkey Caching
```
stella:callgraph:{scan_id}:{lang}:{digest} → Compressed CallGraphSnapshot
stella:callgraph:{scan_id}:{lang}:reachable → Set of reachable sink IDs
stella:callgraph:{scan_id}:{lang}:paths:{sink} → Shortest path to sink
```
TTL: Configurable (default 24h)
Circuit breaker: 5 failures → 30s timeout
---
## 7. API Endpoints
| Method | Path | Description |
|--------|------|-------------|
| GET | `/scans/{scanId}/drift` | Get drift results for a scan |
| GET | `/drift/{driftId}/sinks` | List drifted sinks (paginated) |
| POST | `/scans/{scanId}/compute-reachability` | Trigger reachability computation |
| GET | `/scans/{scanId}/reachability/components` | List components with reachability |
| GET | `/scans/{scanId}/reachability/findings` | Get reachable vulnerable sinks |
| GET | `/scans/{scanId}/reachability/explain` | Explain why a sink is reachable |
See: `docs/api/scanner-drift-api.md`
---
## 8. Integration Points
### 8.1 Policy Module
Drift results feed into policy gates for CI/CD blocking:
```yaml
smart_diff:
gates:
- condition: "delta_reachable > 0 AND is_kev = true"
action: block
```
### 8.2 VEX Emission
Automatic VEX candidate generation on drift:
| Drift Direction | VEX Status | Justification |
|-----------------|------------|---------------|
| became_unreachable | `not_affected` | `vulnerable_code_not_in_execute_path` |
| became_reachable | — | Requires manual review |
### 8.3 Attestation
DSSE-signed drift attestations:
```json
{
"_type": "https://in-toto.io/Statement/v1",
"predicateType": "stellaops.dev/predicates/reachability-drift@v1",
"predicate": {
"baseScanId": "abc123",
"headScanId": "def456",
"newlyReachable": [...],
"newlyUnreachable": [...],
"resultDigest": "sha256:..."
}
}
```
---
## 9. Performance Characteristics
| Metric | Target | Notes |
|--------|--------|-------|
| Graph extraction (100K LOC) | < 60s | Per language |
| Reachability analysis | < 5s | BFS traversal |
| Drift detection | < 10s | Graph comparison |
| Memory usage | < 2GB | Large projects |
| Cache hit improvement | 10x | Valkey lookup vs recompute |
---
## 10. References
- **Implementation Sprints:**
- `docs/implplan/SPRINT_3600_0002_0001_call_graph_infrastructure.md`
- `docs/implplan/SPRINT_3600_0003_0001_drift_detection_engine.md`
- **API Reference:** `docs/api/scanner-drift-api.md`
- **Operations Guide:** `docs/operations/reachability-drift-guide.md`
- **Original Advisory:** `docs/product-advisories/archived/17-Dec-2025 - Reachability Drift Detection.md`
- **Source Code:** `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/`

View File

@@ -0,0 +1,358 @@
# Smart-Diff UI Architecture
**Version:** 1.0
**Status:** Draft
**Last Updated:** 2025-12-22
**Sprint Reference:** SPRINT_4200_0002_0003
## Overview
The Smart-Diff UI provides a dedicated comparison experience for analyzing material risk changes between container image versions. It implements a "diff-first" approach to vulnerability triage, enabling users to focus on what changed rather than reviewing entire vulnerability lists.
## Design Principles
### 1. Diff-First Triage
The primary question in any release is: *"What changed that affects risk?"* The UI defaults to showing delta information rather than full vulnerability lists.
### 2. Proof-Carrying Evidence
Every verdict and comparison includes cryptographic evidence. Users can verify determinism, trace decisions to policy rules, and replay computations.
### 3. Baseline Transparency
Comparisons require explicit baselines with auditor-friendly rationale. The system never uses "magic" to select baselines without explanation.
### 4. Role-Based Defaults
Different personas (Developer, Security, Audit) see different default views while retaining access to all information.
## Component Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ SMART-DIFF UI ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ COMPARE VIEW CONTAINER │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ Baseline │ │ Trust │ │ Export │ │ │
│ │ │ Selector │ │ Indicators │ │ Actions │ │ │
│ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ │
│ │ │ │ │ │ │
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
│ │ │ DELTA SUMMARY STRIP │ │ │
│ │ │ [+N added] [-N removed] [~N changed] [Policy: v1.2] [Feed: 2h]│ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────┐ │ │
│ │ │ THREE-PANE LAYOUT │ │ │
│ │ │ ┌──────────┐ ┌────────────────┐ ┌────────────────────────┐ │ │ │
│ │ │ │Categories│ │ Items │ │ Proof Panel │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ ● SBOM │ │ CVE-2024-1234 │ │ ┌────────────────────┐ │ │ │ │
│ │ │ │ ● Reach │ │ lodash@4.17.20 │ │ │ Witness Path │ │ │ │ │
│ │ │ │ ● VEX │ │ +reachable │ │ │ main() → parse() │ │ │ │ │
│ │ │ │ ● Policy │ │ Priority: 0.85 │ │ │ → vuln_func() │ │ │ │ │
│ │ │ │ ● Unknwn │ │ │ │ └────────────────────┘ │ │ │ │
│ │ │ │ │ │ CVE-2024-5678 │ │ ┌────────────────────┐ │ │ │ │
│ │ │ │ │ │ requests@2.28 │ │ │ VEX Merge │ │ │ │ │
│ │ │ │ │ │ +KEV │ │ │ vendor: affected │ │ │ │ │
│ │ │ │ │ │ Priority: 0.95 │ │ │ distro: not_aff │ │ │ │ │
│ │ │ │ │ │ │ │ │ → Result: affected │ │ │ │ │
│ │ │ │ │ │ │ │ └────────────────────┘ │ │ │ │
│ │ │ └──────────┘ └────────────────┘ └────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────────────────────┐ │ │
│ │ │ ACTIONABLES PANEL │ │ │
│ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ What to do next: │ │ │ │
│ │ │ │ 1. [CRITICAL] Upgrade lodash → 4.17.21 │ │ │ │
│ │ │ │ 2. [HIGH] Add VEX statement for urllib3 (not affected) │ │ │ │
│ │ │ │ 3. [MEDIUM] Resolve unknown: missing SBOM for module A │ │ │ │
│ │ │ └─────────────────────────────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Component Hierarchy
```
CompareViewComponent
├── BaselineSelectorComponent
│ └── BaselineRationaleComponent
├── TrustIndicatorsComponent
│ ├── DeterminismHashDisplay
│ ├── PolicyVersionDisplay
│ ├── FeedSnapshotDisplay
│ ├── SignatureStatusDisplay
│ └── PolicyDriftIndicator
├── DeltaSummaryStripComponent
├── ThreePaneLayoutComponent
│ ├── CategoriesPaneComponent
│ ├── ItemsPaneComponent
│ └── ProofPaneComponent
│ ├── WitnessPathComponent
│ ├── VexMergeExplanationComponent
│ └── EnvelopeHashesComponent
├── ActionablesPanelComponent
└── ExportActionsComponent
```
## State Management
### Signals-Based State
The compare view uses Angular signals for reactive state management:
```typescript
// Core state
currentTarget = signal<CompareTarget | null>(null);
baselineTarget = signal<CompareTarget | null>(null);
delta = signal<DeltaVerdictResponse | null>(null);
// UI state
selectedCategory = signal<string | null>(null);
selectedItem = signal<DeltaItem | null>(null);
viewMode = signal<'side-by-side' | 'unified'>('side-by-side');
userRole = signal<'developer' | 'security' | 'audit'>('developer');
// Computed state
filteredItems = computed(() => {
const cat = this.selectedCategory();
const items = this.delta()?.Items ?? [];
return cat ? items.filter(i => i.category === cat) : items;
});
deltaSummary = computed(() => this.delta()?.Summary);
trustIndicators = computed(() => this.delta()?.TrustIndicators);
```
### Data Flow
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Route │───►│ Component │───►│ Service │
│ Params │ │ Init │ │ Calls │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Signals │◄───│ Backend │
│ Update │ │ Response │
└─────────────┘ └─────────────┘
┌─────────────┐
│ Computed │
│ Values │
└─────────────┘
┌─────────────┐
│ Template │
│ Render │
└─────────────┘
```
## API Integration
### Backend Endpoints
| Endpoint | Purpose |
|----------|---------|
| `GET /api/v1/baselines/recommendations/{digest}` | Get recommended baselines |
| `GET /api/v1/baselines/rationale/{base}/{head}` | Get baseline selection rationale |
| `POST /api/v1/delta/compute` | Compute delta (idempotent) |
| `GET /api/v1/delta/{deltaId}` | Get delta results |
| `GET /api/v1/delta/{deltaId}/trust-indicators` | Get trust indicators |
| `GET /api/v1/actionables/delta/{deltaId}` | Get actionable recommendations |
| `GET /api/v1/evidence/delta/{deltaId}/items/{itemId}` | Get item evidence |
| `GET /api/v1/evidence/delta/{deltaId}/witness-paths` | Get witness paths |
| `GET /api/v1/evidence/delta/{deltaId}/vex-merge/{vulnId}` | Get VEX merge explanation |
### Service Layer
```typescript
@Injectable({ providedIn: 'root' })
export class CompareService {
constructor(private http: HttpClient) {}
getRecommendedBaselines(digest: string): Observable<BaselineRecommendationsResponse> {
return this.http.get<BaselineRecommendationsResponse>(
`/api/v1/baselines/recommendations/${digest}`
);
}
computeDelta(request: DeltaComputeRequest): Observable<DeltaVerdictResponse> {
return this.http.post<DeltaVerdictResponse>('/api/v1/delta/compute', request);
}
getActionables(deltaId: string): Observable<ActionablesResponse> {
return this.http.get<ActionablesResponse>(`/api/v1/actionables/delta/${deltaId}`);
}
getItemEvidence(deltaId: string, itemId: string): Observable<DeltaItemEvidenceResponse> {
return this.http.get<DeltaItemEvidenceResponse>(
`/api/v1/evidence/delta/${deltaId}/items/${itemId}`
);
}
}
```
## Routing
```typescript
// app.routes.ts additions
{
path: 'releases/:releaseId',
children: [
{ path: '', redirectTo: 'detail', pathMatch: 'full' },
{ path: 'detail', component: ReleaseFlowComponent },
{
path: 'compare',
component: CompareViewComponent,
data: { requireBaseline: false }
},
{
path: 'compare/:baselineId',
component: CompareViewComponent,
data: { requireBaseline: true }
}
]
},
{
path: 'compare',
children: [
{
path: ':currentDigest',
component: CompareViewComponent
},
{
path: ':currentDigest/:baselineDigest',
component: CompareViewComponent
}
]
}
```
## Role-Based Views
### Default Tab by Role
| Role | Default Tab | Visible Features |
|------|-------------|------------------|
| Developer | Actionables | Actionables, Witness Paths, Upgrade Suggestions |
| Security | Claims | VEX Merge, Policy Reasoning, Claim Sources, Actionables |
| Audit | Attestations | Signatures, Replay, Evidence Pack, Envelope Hashes |
### Implementation
```typescript
const ROLE_DEFAULTS: Record<UserRole, RoleConfig> = {
developer: {
defaultTab: 'actionables',
showFeatures: ['actionables', 'witness-paths', 'upgrade-suggestions'],
expandedPanels: ['actionables']
},
security: {
defaultTab: 'claims',
showFeatures: ['vex-merge', 'policy-reasoning', 'claim-sources', 'actionables'],
expandedPanels: ['vex-merge', 'policy']
},
audit: {
defaultTab: 'attestations',
showFeatures: ['signatures', 'replay', 'evidence-pack', 'envelope-hashes'],
expandedPanels: ['trust-indicators', 'signatures']
}
};
```
## Trust Indicators
### Determinism Verification
The UI displays and enables verification of:
1. **Determinism Hash** - SHA-256 of normalized delta output
2. **Policy Version/Hash** - Active policy at scan time
3. **Feed Snapshot** - Vulnerability feed timestamp and hash
4. **Signature Status** - DSSE envelope verification result
### Degraded Mode
When signature verification fails, the UI:
- Displays a prominent warning banner
- Disables "Approve" actions
- Shows detailed verification failure reason
- Provides replay command for local verification
## Accessibility
### Keyboard Navigation
- `Tab` / `Shift+Tab`: Navigate between panes
- `Arrow Up/Down`: Navigate items within pane
- `Enter`: Select item / expand detail
- `Escape`: Close expanded detail
- `C`: Copy replay command (when focused on trust indicators)
### Screen Reader Support
- ARIA labels on all interactive elements
- Live regions for delta summary updates
- Semantic heading structure
## Performance Considerations
### Lazy Loading
- Evidence panel loads on-demand when item selected
- Witness paths collapse by default (expand on click)
- VEX merge details in expansion panel
### Caching
- Delta computations cached by (base_hash, head_hash, policy_hash)
- Baseline recommendations cached per session
- Trust indicators cached with delta
### Virtual Scrolling
For large deltas (> 100 items), the items pane uses virtual scrolling:
```html
<cdk-virtual-scroll-viewport itemSize="56" class="items-viewport">
<mat-list-item *cdkVirtualFor="let item of filteredItems()">
<!-- item content -->
</mat-list-item>
</cdk-virtual-scroll-viewport>
```
## Testing Strategy
### Unit Tests
- Component behavior (selection, filtering, expansion)
- Computed signal derivations
- Role-based view switching
### Integration Tests
- API service calls and response handling
- Navigation and routing
- State persistence across route changes
### E2E Tests
- Full comparison workflow
- Baseline selection and rationale display
- Export functionality
- Role-based default verification
## Related Documentation
- [Sprint: Delta Compare View UI](../../implplan/SPRINT_4200_0002_0003_delta_compare_view.md)
- [Sprint: Delta Compare Backend API](../../implplan/SPRINT_4200_0002_0006_delta_compare_api.md)
- [Smart-Diff CLI Reference](../../cli/smart-diff-cli.md)
- [Advisory: Smart Diff - Reproducibility as a Feature](../../product-advisories/archived/22-Dec-2025/21-Dec-2025%20-%20Smart%20Diff%20-%20Reproducibility%20as%20a%20Feature.md)