- 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.
10 KiB
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
- Explainability: Every verdict includes a full breakdown of how it was computed
- Reproducibility: Same inputs always produce identical verdicts (deterministic)
- Auditability: Signed verdict manifests with pinned inputs for regulatory compliance
- 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
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:
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 daysfloor = 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:
- Exact digest match (highest)
- Exact version match
- Version range match
- Product family match
- Platform match (lowest)
4.2 Conflict Detection
Conflicts occur when claims for the same (CVE, Asset) have different statuses:
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:
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:
var winner = scored
.OrderByDescending(x => (x.Claim.ScopeSpecificity, x.AdjustedScore))
.First();
4.5 Audit Trail Generation
Every merge produces:
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.
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.
gates:
unknownsBudget:
enabled: true
maxUnknownCount: 5
maxCumulativeUncertainty: 2.0
Behavior: Fails if:
#unknown_deps > maxUnknownCount, ORsum(1 - ClaimScore) > maxCumulativeUncertainty
5.3 SourceQuotaGate
Prevents single-source dominance without corroboration.
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.
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
{
"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
# 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_affectedwithcomponent_not_present - Ubuntu VEX:
not_affectedwithcomponent_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