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:
462
docs/modules/authority/verdict-manifest.md
Normal file
462
docs/modules/authority/verdict-manifest.md
Normal 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)
|
||||
444
docs/modules/benchmark/architecture.md
Normal file
444
docs/modules/benchmark/architecture.md
Normal 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
|
||||
460
docs/modules/excititor/trust-lattice.md
Normal file
460
docs/modules/excititor/trust-lattice.md
Normal 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)
|
||||
212
docs/modules/platform/explainable-triage-implementation-plan.md
Normal file
212
docs/modules/platform/explainable-triage-implementation-plan.md
Normal 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 |
|
||||
276
docs/modules/platform/moat-gap-analysis.md
Normal file
276
docs/modules/platform/moat-gap-analysis.md
Normal 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`
|
||||
371
docs/modules/scanner/reachability-drift.md
Normal file
371
docs/modules/scanner/reachability-drift.md
Normal 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/`
|
||||
358
docs/modules/web/smart-diff-ui-architecture.md
Normal file
358
docs/modules/web/smart-diff-ui-architecture.md
Normal 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)
|
||||
Reference in New Issue
Block a user