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:
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)
|
||||
Reference in New Issue
Block a user