Files
git.stella-ops.org/docs/contracts/change-trace-trust-delta.md
2026-01-12 12:24:17 +02:00

11 KiB

Change-Trace Trust-Delta Formula Contract

Version: 1.0.0 Status: Draft Last Updated: 2026-01-12


Overview

This document specifies the mathematical formula and algorithm for computing trust-delta scores in Change-Trace. The formula integrates VEX consensus scores, reachability analysis, and patch verification to produce a deterministic risk assessment.


Core Formula

Trust Delta Calculation

TrustDelta = (AfterTrust - BeforeTrust) / max(BeforeTrust, 0.01)

Where:

  • TrustDelta is bounded to [-1.0, +1.0]
  • BeforeTrust and AfterTrust are computed per the sections below

Before Trust Score

BeforeTrust = VexConsensus(purl, fromVersion) * ReachabilityFactor(fromReachablePaths)

After Trust Score

AfterTrust = VexConsensus(purl, toVersion) * ReachabilityFactor(toReachablePaths) + PatchBonus

Component Definitions

VEX Consensus Score

The VEX consensus score is obtained from VexLens, which aggregates trust assessments across multiple issuers.

VexConsensus(purl, version) -> [0.0, 1.0]
Score Range Interpretation
0.9 - 1.0 High trust (no known vulnerabilities)
0.7 - 0.9 Moderate trust (minor issues)
0.5 - 0.7 Low trust (some concerns)
0.0 - 0.5 Very low trust (significant vulnerabilities)

Reachability Factor

The reachability factor adjusts trust based on whether vulnerable code is actually reachable.

ReachabilityFactor(callPaths) =
  - 1.0   if callPaths > 0      (reachable)
  - 0.7   if callPaths == 0     (unreachable, 30% reduction in impact)
  - 1.0   if callPaths is null  (unknown, assume reachable)

Rationale: Unreachable vulnerable code poses less risk, so we reduce the weight of the trust score. However, we don't eliminate it entirely because reachability analysis may have gaps.

Patch Verification Bonus

The patch bonus rewards verified security patches with additional trust.

PatchBonus =
    FunctionMatchWeight * PatchVerificationConfidence
  + SectionMatchWeight * SymbolSimilarity
  + AttestationWeight * IssuerAuthorityScore (if DSSE present)

Default Weights

Weight Value Description
FunctionMatchWeight 0.25 Weight for function-level match confidence
SectionMatchWeight 0.15 Weight for section-level similarity
AttestationWeight 0.10 Weight for DSSE attestation presence

Configurable via TrustDeltaOptions

public sealed record TrustDeltaOptions
{
    public double FunctionMatchWeight { get; init; } = 0.25;
    public double SectionMatchWeight { get; init; } = 0.15;
    public double AttestationWeight { get; init; } = 0.10;
    public double RuntimeConfirmWeight { get; init; } = 0.10;
    public double SignificantDeltaThreshold { get; init; } = 0.3;
}

Verdict Mapping

Trust Delta to Verdict

Delta Range Verdict Description
delta <= -0.3 risk_down Significant risk reduction
-0.3 < delta < 0.3 neutral No significant change
delta >= 0.3 risk_up Significant risk increase

Reachability Impact

Before Paths After Paths Impact
null any unchanged
any null unchanged
0 > 0 introduced
> 0 0 eliminated
N < N reduced
N > N increased
N N unchanged

Exploitability Impact

Delta Range Impact
delta <= -0.5 eliminated
-0.5 < delta < -0.1 down
-0.1 <= delta <= 0.1 unchanged
0.1 < delta < 0.5 up
delta >= 0.5 introduced

Worked Examples

Example 1: Security Patch Applied

Scenario: libssl3 updated from 3.0.9-1 to 3.0.9-1+deb12u3 (Debian security patch)

Inputs:

  • VexConsensus(3.0.9-1) = 0.45 (CVE-2026-12345 present)
  • VexConsensus(3.0.9-1+deb12u3) = 0.95 (CVE patched)
  • ReachablePaths(before) = 3
  • ReachablePaths(after) = 0
  • PatchVerificationConfidence = 0.97
  • SymbolSimilarity = 0.85
  • HasDsseAttestation = true
  • IssuerAuthorityScore = 0.90

Calculation:

BeforeTrust = 0.45 * 1.0 = 0.45

PatchBonus = (0.25 * 0.97) + (0.15 * 0.85) + (0.10 * 0.90)
           = 0.2425 + 0.1275 + 0.09
           = 0.46

AfterTrust = 0.95 * 0.7 + 0.46 = 0.665 + 0.46 = 1.125
           -> Capped at 1.0 for display, but used as-is for delta

TrustDelta = (1.125 - 0.45) / max(0.45, 0.01)
           = 0.675 / 0.45
           = 1.5
           -> Clamped to 1.0

Final: TrustDelta = 1.0 (clamped)
Verdict: risk_down (delta < -0.3 equivalent in impact)

Note: The formula produces a positive delta when risk decreases because AfterTrust > BeforeTrust. The display inverts this for user comprehension (risk_down means good).

Example 2: Version Upgrade with New Vulnerability

Scenario: openssl upgraded from 3.0.8 to 3.1.0 (new version introduces regression)

Inputs:

  • VexConsensus(3.0.8) = 0.85
  • VexConsensus(3.1.0) = 0.55 (regression CVE)
  • ReachablePaths(before) = 0
  • ReachablePaths(after) = 5
  • No patch verification (upgrade, not patch)

Calculation:

BeforeTrust = 0.85 * 0.7 = 0.595 (unreachable before)

AfterTrust = 0.55 * 1.0 + 0 = 0.55 (reachable after)

TrustDelta = (0.55 - 0.595) / max(0.595, 0.01)
           = -0.045 / 0.595
           = -0.076

Final: TrustDelta = -0.08
Verdict: neutral (within -0.3 to +0.3 range)

Analysis: Despite the new CVE, the overall delta is neutral because the before version also had some risk (though unreachable). The reachability change from 0 to 5 is captured in reachabilityImpact: introduced.

Example 3: Rebuild Without Changes

Scenario: Same source, different build timestamp

Inputs:

  • VexConsensus(1.0.0) = 0.90 (both versions)
  • ReachablePaths = 2 (both versions)
  • SymbolSimilarity = 1.0 (identical code)

Calculation:

BeforeTrust = 0.90 * 1.0 = 0.90

AfterTrust = 0.90 * 1.0 + 0 = 0.90 (no patch bonus for rebuild)

TrustDelta = (0.90 - 0.90) / max(0.90, 0.01)
           = 0 / 0.90
           = 0

Final: TrustDelta = 0.00
Verdict: neutral

Proof Step Generation

Step Categories

  1. CVE Context: List CVEs affecting the package
  2. Version Change: Document version transition
  3. Patch Verification: Report verification method and confidence
  4. Symbol Similarity: Report code similarity metrics
  5. Reachability: Report call path changes
  6. Attestation: Note DSSE attestation presence
  7. Verdict: Final determination

Step Format

1. {CVE-ID} affects {function_name}
2. Version changed: {from} -> {to}
3. Patch verified via {method}: {confidence}% confidence
4. Symbol similarity: {percentage}%
5. Reachable call paths: {before} -> {after}
6. DSSE attestation present
7. Verdict: {verdict} ({delta:+0.00})

Example Output

1. CVE-2026-12345 affects ssl3_get_record
2. Version changed: 3.0.9-1 -> 3.0.9-1+deb12u3
3. Patch verified via CFG match: 97% confidence
4. Symbol similarity: 85%
5. Reachable call paths: 3 -> 0 after patch
6. DSSE attestation present
7. Verdict: risk_down (-0.27)

Algorithm Pseudocode

def calculate_trust_delta(context: TrustDeltaContext, options: TrustDeltaOptions) -> TrustDelta:
    # Get VEX consensus scores
    before_consensus = vexlens.get_consensus(context.purl, context.from_version)
    after_consensus = vexlens.get_consensus(context.purl, context.to_version)

    # Compute reachability factors
    before_reach = compute_reachability_factor(context.reachable_paths_before)
    after_reach = compute_reachability_factor(context.reachable_paths_after)

    # Compute base trust scores
    before_trust = before_consensus.trust_score * before_reach
    after_trust = after_consensus.trust_score * after_reach

    # Add patch verification bonus
    patch_bonus = 0.0
    if context.patch_verification_confidence:
        patch_bonus += options.function_match_weight * context.patch_verification_confidence
    if context.symbol_similarity:
        patch_bonus += options.section_match_weight * context.symbol_similarity
    if context.has_dsse_attestation and context.issuer_authority_score:
        patch_bonus += options.attestation_weight * context.issuer_authority_score

    after_trust += patch_bonus

    # Compute delta with division protection
    delta = (after_trust - before_trust) / max(before_trust, 0.01)
    delta = clamp(delta, -1.0, 1.0)

    # Determine impacts
    reachability_impact = determine_reachability_impact(
        context.reachable_paths_before,
        context.reachable_paths_after
    )
    exploitability_impact = determine_exploitability_impact(delta)

    # Generate proof steps
    proof_steps = generate_proof_steps(context, delta)

    return TrustDelta(
        score=round(delta, 2),
        before_score=round(before_trust, 2),
        after_score=round(after_trust, 2),
        reachability_impact=reachability_impact,
        exploitability_impact=exploitability_impact,
        proof_steps=proof_steps
    )


def compute_reachability_factor(call_paths: int | None) -> float:
    if call_paths is None:
        return 1.0  # Unknown, assume reachable
    if call_paths == 0:
        return 0.7  # Unreachable, 30% reduction
    return 1.0  # Reachable


def determine_reachability_impact(before: int | None, after: int | None) -> ReachabilityImpact:
    if before is None or after is None:
        return ReachabilityImpact.UNCHANGED
    if before == 0 and after > 0:
        return ReachabilityImpact.INTRODUCED
    if before > 0 and after == 0:
        return ReachabilityImpact.ELIMINATED
    if after < before:
        return ReachabilityImpact.REDUCED
    if after > before:
        return ReachabilityImpact.INCREASED
    return ReachabilityImpact.UNCHANGED


def determine_exploitability_impact(delta: float) -> ExploitabilityImpact:
    if delta <= -0.5:
        return ExploitabilityImpact.ELIMINATED
    if delta < -0.1:
        return ExploitabilityImpact.DOWN
    if delta > 0.5:
        return ExploitabilityImpact.INTRODUCED
    if delta > 0.1:
        return ExploitabilityImpact.UP
    return ExploitabilityImpact.UNCHANGED

Determinism Requirements

Reproducibility

Given identical inputs, the formula must produce identical outputs:

  • Same VEX consensus data
  • Same reachability data
  • Same patch verification results
  • Same options

Floating Point Handling

  • All intermediate calculations use double precision
  • Final scores are rounded to 2 decimal places
  • Comparisons use exact equality after rounding

Timestamp Independence

The formula does not depend on current time. All inputs are point-in-time snapshots.


Versioning

Algorithm Version

The algorithm version is tracked in the output:

{
  "algorithmVersion": "1.0"
}

Backward Compatibility

  • v1.0: Initial release
  • Future versions will maintain semantic versioning
  • Breaking changes will increment major version

References


Document Version: 1.0.0 Last Updated: 2026-01-12