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

410 lines
11 KiB
Markdown

# 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
```csharp
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
```python
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:
```json
{
"algorithmVersion": "1.0"
}
```
### Backward Compatibility
- v1.0: Initial release
- Future versions will maintain semantic versioning
- Breaking changes will increment major version
---
## References
- [Change-Trace Schema Contract](./change-trace-schema.md)
- [Architecture Document](../modules/scanner/design/change-trace-architecture.md)
- [VexLens Trust Scoring](../modules/vexlens/architecture.md)
- [ReachGraph Analysis](../modules/reachgraph/architecture.md)
---
*Document Version: 1.0.0*
*Last Updated: 2026-01-12*