release orchestrator v1 draft and build fixes

This commit is contained in:
master
2026-01-12 12:24:17 +02:00
parent f3de858c59
commit 9873f80830
1598 changed files with 240385 additions and 5944 deletions

View 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*

View 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*