Files
git.stella-ops.org/docs/modules/excititor/trust-lattice.md
StellaOps Bot 634233dfed 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.
2025-12-22 09:50:12 +02:00

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

  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

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 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:

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, OR
  • sum(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_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