release orchestrator v1 draft and build fixes
This commit is contained in:
447
docs/contracts/change-trace-schema.md
Normal file
447
docs/contracts/change-trace-schema.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# Change-Trace JSON Schema Contract
|
||||
|
||||
> **Version:** 1.0.0
|
||||
> **Status:** Draft
|
||||
> **Last Updated:** 2026-01-12
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the JSON schema for Change-Trace artifacts (`trace.cdxchange.json`). All implementations must conform to this schema for interoperability.
|
||||
|
||||
---
|
||||
|
||||
## Schema Identifier
|
||||
|
||||
```
|
||||
Schema URI: stella.change-trace/1.0
|
||||
File Extension: .cdxchange.json
|
||||
MIME Type: application/vnd.stella.change-trace+json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Root Object
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://stella-ops.org/schemas/change-trace/1.0/schema.json",
|
||||
"schema": "stella.change-trace/1.0",
|
||||
"subject": { ... },
|
||||
"deltas": [ ... ],
|
||||
"summary": { ... },
|
||||
"analyzedAt": "2026-01-12T14:30:00.000Z",
|
||||
"algorithmVersion": "1.0"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| `schema` | string | Yes | Schema version identifier. Must be `stella.change-trace/1.0` |
|
||||
| `subject` | object | Yes | Subject of the comparison |
|
||||
| `deltas` | array | Yes | Array of package deltas (may be empty) |
|
||||
| `summary` | object | Yes | Aggregated summary metrics |
|
||||
| `analyzedAt` | string | Yes | ISO 8601 timestamp (UTC) |
|
||||
| `algorithmVersion` | string | No | Algorithm version used (default: "1.0") |
|
||||
|
||||
---
|
||||
|
||||
## Subject Object
|
||||
|
||||
```json
|
||||
{
|
||||
"imageRef": "docker.io/library/nginx:1.25.3",
|
||||
"fromDigest": "sha256:abc123...",
|
||||
"toDigest": "sha256:def456...",
|
||||
"fromScanId": "scan-12345",
|
||||
"toScanId": "scan-67890"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| `imageRef` | string | Yes | Container image reference |
|
||||
| `fromDigest` | string | Yes | SHA-256 digest of source image |
|
||||
| `toDigest` | string | Yes | SHA-256 digest of target image |
|
||||
| `fromScanId` | string | No | Source scan identifier |
|
||||
| `toScanId` | string | No | Target scan identifier |
|
||||
|
||||
---
|
||||
|
||||
## PackageDelta Object
|
||||
|
||||
```json
|
||||
{
|
||||
"purl": "pkg:deb/debian/libssl3@3.0.9-1",
|
||||
"fromVersion": "3.0.9-1",
|
||||
"toVersion": "3.0.9-1+deb12u3",
|
||||
"changeType": "patched",
|
||||
"symbols": [ ... ],
|
||||
"bytes": [ ... ],
|
||||
"trustDelta": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| `purl` | string | Yes | Package URL (RFC 3986 compliant) |
|
||||
| `fromVersion` | string | No | Source version (null if added) |
|
||||
| `toVersion` | string | No | Target version (null if removed) |
|
||||
| `changeType` | enum | Yes | Type of change |
|
||||
| `symbols` | array | No | Symbol-level deltas (if available) |
|
||||
| `bytes` | array | No | Byte-level deltas (if enabled) |
|
||||
| `trustDelta` | object | Yes | Trust impact calculation |
|
||||
|
||||
### ChangeType Enum
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `unchanged` | No change detected |
|
||||
| `added` | Package added in target |
|
||||
| `removed` | Package removed in target |
|
||||
| `upgraded` | Version increased |
|
||||
| `downgraded` | Version decreased |
|
||||
| `patched` | Security patch applied (same base version) |
|
||||
| `rebuilt` | Same version, different build |
|
||||
|
||||
---
|
||||
|
||||
## SymbolDelta Object
|
||||
|
||||
```json
|
||||
{
|
||||
"symbolName": "ssl3_get_record",
|
||||
"changeType": "patched",
|
||||
"sizeDelta": -24,
|
||||
"cfgBlockDelta": 2,
|
||||
"confidence": 0.97,
|
||||
"matchMethod": "CFGHash+ChunkMatch",
|
||||
"explanation": "Function patched: 2 basic blocks changed"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| `symbolName` | string | Yes | Symbol/function name |
|
||||
| `changeType` | enum | Yes | Type of symbol change |
|
||||
| `sizeDelta` | integer | No | Size change in bytes |
|
||||
| `cfgBlockDelta` | integer | No | CFG basic block count change |
|
||||
| `confidence` | number | No | Match confidence (0.0-1.0) |
|
||||
| `matchMethod` | string | No | Method used for matching |
|
||||
| `explanation` | string | No | Human-readable explanation |
|
||||
|
||||
### SymbolChangeType Enum
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `unchanged` | No change detected |
|
||||
| `added` | Symbol added in target |
|
||||
| `removed` | Symbol removed in target |
|
||||
| `modified` | Symbol modified (general) |
|
||||
| `patched` | Security patch detected |
|
||||
|
||||
---
|
||||
|
||||
## ByteDelta Object
|
||||
|
||||
```json
|
||||
{
|
||||
"offset": 4096,
|
||||
"size": 2048,
|
||||
"fromHash": "sha256:abc123...",
|
||||
"toHash": "sha256:def456...",
|
||||
"section": ".text",
|
||||
"context": "Function: ssl3_get_record"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| `offset` | integer | Yes | Byte offset in binary |
|
||||
| `size` | integer | Yes | Size of changed region |
|
||||
| `fromHash` | string | Yes | SHA-256 hash of source bytes |
|
||||
| `toHash` | string | Yes | SHA-256 hash of target bytes |
|
||||
| `section` | string | No | Binary section name |
|
||||
| `context` | string | No | Context hint (e.g., function name) |
|
||||
|
||||
**Privacy Note:** Raw byte content is never included; only hashes are stored.
|
||||
|
||||
---
|
||||
|
||||
## TrustDelta Object
|
||||
|
||||
```json
|
||||
{
|
||||
"score": -0.27,
|
||||
"beforeScore": 0.65,
|
||||
"afterScore": 0.92,
|
||||
"reachabilityImpact": "reduced",
|
||||
"exploitabilityImpact": "down",
|
||||
"proofSteps": [
|
||||
"CVE-2026-12345 affects ssl3_get_record",
|
||||
"Version changed: 3.0.9-1 -> 3.0.9-1+deb12u3",
|
||||
"Patch verified via CFG match: 97% confidence",
|
||||
"Reachable call paths: 3 -> 0 after patch",
|
||||
"DSSE attestation present",
|
||||
"Verdict: risk_down (-0.27)"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| `score` | number | Yes | Trust delta value (-1.0 to +1.0) |
|
||||
| `beforeScore` | number | Yes | Trust score before change |
|
||||
| `afterScore` | number | Yes | Trust score after change |
|
||||
| `reachabilityImpact` | enum | Yes | Impact on code reachability |
|
||||
| `exploitabilityImpact` | enum | Yes | Impact on exploitability |
|
||||
| `proofSteps` | array | Yes | Human-readable proof steps |
|
||||
|
||||
### ReachabilityImpact Enum
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `unchanged` | No change in reachability |
|
||||
| `introduced` | Path now reachable |
|
||||
| `eliminated` | Path no longer reachable |
|
||||
| `reduced` | Fewer reachable paths |
|
||||
| `increased` | More reachable paths |
|
||||
|
||||
### ExploitabilityImpact Enum
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `unchanged` | No change in exploitability |
|
||||
| `introduced` | New exploitability concern |
|
||||
| `eliminated` | Exploitability eliminated |
|
||||
| `up` | Increased exploitability |
|
||||
| `down` | Decreased exploitability |
|
||||
|
||||
---
|
||||
|
||||
## Summary Object
|
||||
|
||||
```json
|
||||
{
|
||||
"packagesChanged": 5,
|
||||
"packagesAdded": 1,
|
||||
"packagesRemoved": 0,
|
||||
"symbolsChanged": 23,
|
||||
"bytesChanged": 8192,
|
||||
"trustDelta": -0.15,
|
||||
"overallVerdict": "risk_down"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| `packagesChanged` | integer | Yes | Total packages with changes |
|
||||
| `packagesAdded` | integer | Yes | Packages added |
|
||||
| `packagesRemoved` | integer | Yes | Packages removed |
|
||||
| `symbolsChanged` | integer | Yes | Total symbols with changes |
|
||||
| `bytesChanged` | integer | Yes | Total bytes changed |
|
||||
| `trustDelta` | number | Yes | Aggregate trust delta |
|
||||
| `overallVerdict` | string | Yes | Overall verdict |
|
||||
|
||||
### Verdict Values
|
||||
|
||||
| Value | Trust Delta Range | Description |
|
||||
|-------|-------------------|-------------|
|
||||
| `risk_down` | < -0.3 | Risk decreased significantly |
|
||||
| `neutral` | -0.3 to +0.3 | No significant risk change |
|
||||
| `risk_up` | > +0.3 | Risk increased significantly |
|
||||
| `inconclusive` | N/A | Unable to determine |
|
||||
|
||||
---
|
||||
|
||||
## Full Example
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://stella-ops.org/schemas/change-trace/1.0/schema.json",
|
||||
"schema": "stella.change-trace/1.0",
|
||||
"subject": {
|
||||
"imageRef": "docker.io/library/nginx:1.25.3",
|
||||
"fromDigest": "sha256:abc123def456...",
|
||||
"toDigest": "sha256:789ghi012jkl...",
|
||||
"fromScanId": "scan-20260112-001",
|
||||
"toScanId": "scan-20260112-002"
|
||||
},
|
||||
"deltas": [
|
||||
{
|
||||
"purl": "pkg:deb/debian/libssl3@3.0.9-1",
|
||||
"fromVersion": "3.0.9-1",
|
||||
"toVersion": "3.0.9-1+deb12u3",
|
||||
"changeType": "patched",
|
||||
"symbols": [
|
||||
{
|
||||
"symbolName": "ssl3_get_record",
|
||||
"changeType": "patched",
|
||||
"sizeDelta": -24,
|
||||
"cfgBlockDelta": 2,
|
||||
"confidence": 0.97,
|
||||
"matchMethod": "CFGHash+ChunkMatch",
|
||||
"explanation": "Function patched: 2 basic blocks changed"
|
||||
}
|
||||
],
|
||||
"bytes": [
|
||||
{
|
||||
"offset": 4096,
|
||||
"size": 2048,
|
||||
"fromHash": "sha256:abc...",
|
||||
"toHash": "sha256:def...",
|
||||
"section": ".text"
|
||||
}
|
||||
],
|
||||
"trustDelta": {
|
||||
"score": -0.27,
|
||||
"beforeScore": 0.65,
|
||||
"afterScore": 0.92,
|
||||
"reachabilityImpact": "reduced",
|
||||
"exploitabilityImpact": "down",
|
||||
"proofSteps": [
|
||||
"CVE-2026-12345 affects ssl3_get_record",
|
||||
"Version changed: 3.0.9-1 -> 3.0.9-1+deb12u3",
|
||||
"Patch verified via CFG match: 97% confidence",
|
||||
"Reachable call paths: 3 -> 0 after patch",
|
||||
"DSSE attestation present",
|
||||
"Verdict: risk_down (-0.27)"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"packagesChanged": 1,
|
||||
"packagesAdded": 0,
|
||||
"packagesRemoved": 0,
|
||||
"symbolsChanged": 1,
|
||||
"bytesChanged": 2048,
|
||||
"trustDelta": -0.27,
|
||||
"overallVerdict": "risk_down"
|
||||
},
|
||||
"analyzedAt": "2026-01-12T14:30:00.000Z",
|
||||
"algorithmVersion": "1.0"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Serialization Rules
|
||||
|
||||
### Determinism Requirements
|
||||
|
||||
1. **Key ordering**: Object keys must be sorted alphabetically
|
||||
2. **Array ordering**:
|
||||
- `deltas`: Sorted by `purl` (lexicographic)
|
||||
- `symbols`: Sorted by `symbolName` (lexicographic)
|
||||
- `bytes`: Sorted by `offset` (numeric ascending)
|
||||
- `proofSteps`: Preserve generation order
|
||||
3. **Number formatting**: No trailing zeros, no exponent notation
|
||||
4. **String escaping**: Minimal escaping per RFC 8785
|
||||
5. **Timestamps**: ISO 8601 with milliseconds, UTC (`Z` suffix)
|
||||
|
||||
### RFC 8785 Compliance
|
||||
|
||||
All JSON output must conform to [RFC 8785](https://datatracker.ietf.org/doc/html/rfc8785) (JSON Canonicalization Scheme) for digest computation.
|
||||
|
||||
---
|
||||
|
||||
## Validation
|
||||
|
||||
### JSON Schema
|
||||
|
||||
The formal JSON Schema is available at:
|
||||
```
|
||||
https://stella-ops.org/schemas/change-trace/1.0/schema.json
|
||||
```
|
||||
|
||||
### Validation Command
|
||||
|
||||
```bash
|
||||
stella change-trace verify trace.cdxchange.json
|
||||
```
|
||||
|
||||
### Required Fields
|
||||
|
||||
The following fields are required for a valid trace:
|
||||
- `schema`
|
||||
- `subject.imageRef`
|
||||
- `subject.fromDigest`
|
||||
- `subject.toDigest`
|
||||
- `deltas` (may be empty array)
|
||||
- `summary.*` (all summary fields)
|
||||
- `analyzedAt`
|
||||
|
||||
---
|
||||
|
||||
## CycloneDX Integration
|
||||
|
||||
### Embedded Mode
|
||||
|
||||
When embedded in CycloneDX, the change trace is attached as a `component-evidence` extension:
|
||||
|
||||
```json
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.7",
|
||||
"components": [
|
||||
{
|
||||
"purl": "pkg:deb/debian/libssl3@3.0.9-1+deb12u3",
|
||||
"evidence": {
|
||||
"extensions": [
|
||||
{
|
||||
"extensionType": "stella-change-trace",
|
||||
"extension": {
|
||||
"schema": "stella.change-trace/1.0",
|
||||
"changeType": "patched",
|
||||
"trustDelta": { ... },
|
||||
"symbols": [ ... ]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Standalone Mode
|
||||
|
||||
Export as separate file: `<basename>.cdxchange.json`
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.0.0 | 2026-01-12 | Initial release |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Architecture Document](../modules/scanner/design/change-trace-architecture.md)
|
||||
- [Trust-Delta Contract](./change-trace-trust-delta.md)
|
||||
- [RFC 8785 - JSON Canonicalization Scheme](https://datatracker.ietf.org/doc/html/rfc8785)
|
||||
- [Package URL Specification](https://github.com/package-url/purl-spec)
|
||||
|
||||
---
|
||||
|
||||
*Document Version: 1.0.0*
|
||||
*Last Updated: 2026-01-12*
|
||||
409
docs/contracts/change-trace-trust-delta.md
Normal file
409
docs/contracts/change-trace-trust-delta.md
Normal file
@@ -0,0 +1,409 @@
|
||||
# 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*
|
||||
Reference in New Issue
Block a user