feat: Implement distro-native version comparison for RPM, Debian, and Alpine packages

- Add RpmVersionComparer for RPM version comparison with epoch, version, and release handling.
- Introduce DebianVersion for parsing Debian EVR (Epoch:Version-Release) strings.
- Create ApkVersion for parsing Alpine APK version strings with suffix support.
- Define IVersionComparator interface for version comparison with proof-line generation.
- Implement VersionComparisonResult struct to encapsulate comparison results and proof lines.
- Add tests for Debian and RPM version comparers to ensure correct functionality and edge case handling.
- Create project files for the version comparison library and its tests.
This commit is contained in:
StellaOps Bot
2025-12-22 09:49:38 +02:00
parent aff0ceb2fe
commit df94136727
111 changed files with 30413 additions and 1813 deletions

View File

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