- Introduced `ReachabilityState`, `RuntimeHit`, `ExploitabilitySignal`, `ReachabilitySignal`, `SignalEnvelope`, `SignalType`, `TrustSignal`, and `UnknownSymbolSignal` records to define various signal types and their properties. - Implemented JSON serialization attributes for proper data interchange. - Created project files for the new signal contracts library and corresponding test projects. - Added deterministic test fixtures for micro-interaction testing. - Included cryptographic keys for secure operations with cosign.
7.3 KiB
7.3 KiB
SBOM/VEX Deterministic Diff Rules (SP5)
Status: Draft · Date: 2025-12-04 Scope: Define deterministic diff rules and fixtures for SBOM/VEX deltas, ensuring reproducible comparison results and stable hash expectations.
Objectives
- Enable deterministic diffing between SBOM/VEX versions.
- Define canonical ordering for diff output.
- Provide fixtures for validating diff implementations.
- Ensure diff results are hash-stable.
Diff Operations
Supported Operations
| Operation | Description | Output Format |
|---|---|---|
component-diff |
Compare component lists between SBOMs | JSON Patch |
vulnerability-diff |
Compare vulnerability lists | JSON Patch |
vex-diff |
Compare VEX statements | JSON Patch |
full-diff |
Complete SBOM/VEX comparison | Combined JSON Patch |
JSON Patch Format
Diff output uses RFC 6902 JSON Patch format:
{
"patch": [
{
"op": "add",
"path": "/components/2",
"value": {
"type": "library",
"name": "new-lib",
"version": "1.0.0",
"purl": "pkg:npm/new-lib@1.0.0"
}
},
{
"op": "remove",
"path": "/components/0"
},
{
"op": "replace",
"path": "/components/1/version",
"value": "2.0.0"
}
],
"meta": {
"source": "sbom-v1.json",
"target": "sbom-v2.json",
"sourceHash": "b3:...",
"targetHash": "b3:...",
"patchHash": "b3:...",
"timestamp": "2025-12-04T00:00:00Z"
}
}
Determinism Rules
Ordering
- Operations:
removefirst (descending path order), thenreplace, thenadd - Paths: Lexicographic sort within operation type
- Array indices: Stable indices based on sort keys (purl for components, id for vulns)
Canonical Comparison
When comparing elements for diff:
| Element Type | Sort Keys | Tie Breakers |
|---|---|---|
| Component | purl |
name, version |
| Vulnerability | id |
source.name, ratings[0].score |
| VEX Statement | vulnerability |
products[0].purl, timestamp |
| Service | name |
version |
| Property | name |
- |
Hash Computation
Diff output hash computed as:
- Serialize patch array to canonical JSON (sorted keys, no whitespace)
- Compute BLAKE3-256 over UTF-8 bytes
- Record in
meta.patchHash
Component Diff
Input
// sbom-v1.json
{
"components": [
{"name": "lib-a", "version": "1.0.0", "purl": "pkg:npm/lib-a@1.0.0"},
{"name": "lib-b", "version": "1.0.0", "purl": "pkg:npm/lib-b@1.0.0"}
]
}
// sbom-v2.json
{
"components": [
{"name": "lib-a", "version": "1.0.1", "purl": "pkg:npm/lib-a@1.0.1"},
{"name": "lib-c", "version": "1.0.0", "purl": "pkg:npm/lib-c@1.0.0"}
]
}
Output
{
"patch": [
{
"op": "remove",
"path": "/components/1",
"comment": "removed pkg:npm/lib-b@1.0.0"
},
{
"op": "replace",
"path": "/components/0/version",
"value": "1.0.1",
"comment": "upgraded pkg:npm/lib-a@1.0.0 -> 1.0.1"
},
{
"op": "replace",
"path": "/components/0/purl",
"value": "pkg:npm/lib-a@1.0.1"
},
{
"op": "add",
"path": "/components/1",
"value": {"name": "lib-c", "version": "1.0.0", "purl": "pkg:npm/lib-c@1.0.0"},
"comment": "added pkg:npm/lib-c@1.0.0"
}
]
}
Vulnerability Diff
Added/Removed Vulnerabilities
{
"patch": [
{
"op": "add",
"path": "/vulnerabilities/-",
"value": {
"id": "CVE-2025-0002",
"ratings": [{"method": "CVSSv4", "score": 5.3}]
},
"comment": "new vulnerability CVE-2025-0002"
},
{
"op": "remove",
"path": "/vulnerabilities/0",
"comment": "resolved CVE-2025-0001"
}
]
}
Rating Changes
{
"patch": [
{
"op": "replace",
"path": "/vulnerabilities/0/ratings/0/score",
"value": 9.0,
"comment": "CVE-2025-0001 score updated 8.5 -> 9.0"
}
]
}
VEX Diff
Statement Status Changes
{
"patch": [
{
"op": "replace",
"path": "/statements/0/status",
"value": "not_affected",
"comment": "CVE-2025-0001 status changed affected -> not_affected"
},
{
"op": "add",
"path": "/statements/0/justification",
"value": {
"category": "vulnerable_code_not_present",
"details": "Function patched in v2.0.1"
}
}
]
}
Fixtures
Directory Structure
docs/modules/policy/fixtures/diff-rules/
├── component-diff/
│ ├── input-v1.json
│ ├── input-v2.json
│ ├── expected-diff.json
│ └── hashes.txt
├── vulnerability-diff/
│ ├── input-v1.json
│ ├── input-v2.json
│ ├── expected-diff.json
│ └── hashes.txt
├── vex-diff/
│ ├── input-v1.json
│ ├── input-v2.json
│ ├── expected-diff.json
│ └── hashes.txt
└── full-diff/
├── sbom-v1.json
├── sbom-v2.json
├── expected-diff.json
└── hashes.txt
Sample Fixture (Component Diff)
// docs/modules/policy/fixtures/diff-rules/component-diff/expected-diff.json
{
"patch": [
{"op": "remove", "path": "/components/1"},
{"op": "replace", "path": "/components/0/version", "value": "1.0.1"},
{"op": "replace", "path": "/components/0/purl", "value": "pkg:npm/lib-a@1.0.1"},
{"op": "add", "path": "/components/1", "value": {"name": "lib-c", "version": "1.0.0", "purl": "pkg:npm/lib-c@1.0.0"}}
],
"meta": {
"sourceHash": "b3:...",
"targetHash": "b3:...",
"patchHash": "b3:..."
}
}
CI Validation
#!/bin/bash
# scripts/policy/validate-diff-fixtures.sh
FIXTURE_DIR="docs/modules/policy/fixtures/diff-rules"
for category in component-diff vulnerability-diff vex-diff full-diff; do
echo "Validating ${category}..."
# Run diff
stellaops-diff \
--source "${FIXTURE_DIR}/${category}/input-v1.json" \
--target "${FIXTURE_DIR}/${category}/input-v2.json" \
--output /tmp/actual-diff.json
# Compare with expected
expected_hash=$(grep "expected-diff.json" "${FIXTURE_DIR}/${category}/hashes.txt" | awk '{print $2}')
actual_hash=$(b3sum /tmp/actual-diff.json | cut -d' ' -f1)
if [[ "${actual_hash}" != "${expected_hash}" ]]; then
echo "FAIL: ${category} diff hash mismatch"
diff <(jq -S . "${FIXTURE_DIR}/${category}/expected-diff.json") <(jq -S . /tmp/actual-diff.json)
exit 1
fi
echo "PASS: ${category}"
done
API Integration
Diff Endpoint
POST /api/v1/sbom/diff
Content-Type: application/json
{
"source": "<base64-encoded-sbom-v1>",
"target": "<base64-encoded-sbom-v2>",
"options": {
"includeComments": true,
"format": "json-patch"
}
}
Response
{
"diff": {
"patch": [...],
"meta": {
"sourceHash": "b3:...",
"targetHash": "b3:...",
"patchHash": "b3:...",
"componentChanges": {
"added": 1,
"removed": 1,
"modified": 1
},
"vulnerabilityChanges": {
"added": 0,
"removed": 1,
"modified": 0
}
}
}
}
Links
- Sprint:
docs/implplan/SPRINT_0186_0001_0001_record_deterministic_execution.md(SP5) - Spine Versioning:
docs/modules/policy/contracts/spine-versioning-plan.md(SP1)