410 lines
11 KiB
Markdown
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*
|