feat: Implement distro-native version comparison for RPM, Debian, and Alpine packages
- Add RpmVersionComparer for RPM version comparison with epoch, version, and release handling. - Introduce DebianVersion for parsing Debian EVR (Epoch:Version-Release) strings. - Create ApkVersion for parsing Alpine APK version strings with suffix support. - Define IVersionComparator interface for version comparison with proof-line generation. - Implement VersionComparisonResult struct to encapsulate comparison results and proof lines. - Add tests for Debian and RPM version comparers to ensure correct functionality and edge case handling. - Create project files for the version comparison library and its tests.
This commit is contained in:
296
docs/reachability/cve-symbol-mapping.md
Normal file
296
docs/reachability/cve-symbol-mapping.md
Normal file
@@ -0,0 +1,296 @@
|
||||
# CVE → Symbol Mapping
|
||||
|
||||
_Last updated: 2025-12-22. Owner: Scanner Guild + Concelier Guild._
|
||||
|
||||
This document describes how Stella Ops maps CVE identifiers to specific binary symbols/functions for precise reachability analysis.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
To determine if a vulnerability is reachable, we need to know which specific functions are affected. The **CVE→Symbol Mapping** service bridges:
|
||||
|
||||
- **CVE identifiers** (e.g., `CVE-2024-1234`)
|
||||
- **Package coordinates** (e.g., `pkg:npm/lodash@4.17.21`)
|
||||
- **Affected symbols** (e.g., `lodash.template`, `openssl:EVP_PKEY_decrypt`)
|
||||
|
||||
---
|
||||
|
||||
## 2. Data Sources
|
||||
|
||||
### 2.1 Patch Diff Analysis
|
||||
|
||||
The highest-fidelity source: analyze git commits that fix vulnerabilities.
|
||||
|
||||
```
|
||||
CVE-2024-1234 fixed in commit abc123
|
||||
→ Diff shows changes to:
|
||||
- src/crypto.c: EVP_PKEY_decrypt() [modified]
|
||||
- src/crypto.c: decrypt_internal() [added guard]
|
||||
→ Affected symbols: EVP_PKEY_decrypt, decrypt_internal
|
||||
```
|
||||
|
||||
**Implementation**: `StellaOps.Scanner.VulnSurfaces.PatchDiffAnalyzer`
|
||||
|
||||
### 2.2 Advisory Metadata
|
||||
|
||||
Structured advisories with function-level detail:
|
||||
|
||||
- **OSV** (`affected[].ranges[].events[].introduced/fixed`)
|
||||
- **NVD CPE** with CWE → typical affected patterns
|
||||
- **Vendor advisories** (GitHub, npm, PyPI security advisories)
|
||||
|
||||
**Implementation**: `StellaOps.Concelier.Connectors.*`
|
||||
|
||||
### 2.3 Heuristic Inference
|
||||
|
||||
When precise mappings unavailable:
|
||||
|
||||
1. **All public exports** of affected package version
|
||||
2. **CWE-based patterns** (e.g., CWE-79 XSS → output functions)
|
||||
3. **Function name patterns** (e.g., `*_decrypt*`, `*_parse*`)
|
||||
|
||||
**Implementation**: `StellaOps.Scanner.VulnSurfaces.HeuristicMapper`
|
||||
|
||||
---
|
||||
|
||||
## 3. Mapping Confidence Tiers
|
||||
|
||||
| Tier | Source | Confidence | Example |
|
||||
|------|--------|------------|---------|
|
||||
| **Confirmed** | Patch diff analysis | 0.95–1.0 | Exact function from git diff |
|
||||
| **Likely** | Advisory with function names | 0.7–0.9 | OSV with `affected.functions[]` |
|
||||
| **Inferred** | CWE/pattern heuristics | 0.4–0.6 | All exports of vulnerable version |
|
||||
| **Unknown** | No data available | 0.0–0.3 | Package-level only |
|
||||
|
||||
---
|
||||
|
||||
## 4. Query Interface
|
||||
|
||||
### 4.1 Service Contract
|
||||
|
||||
```csharp
|
||||
public interface IVulnSurfaceService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get symbols affected by a CVE for a specific package.
|
||||
/// </summary>
|
||||
Task<VulnSurfaceResult> GetAffectedSymbolsAsync(
|
||||
string cveId,
|
||||
string purl,
|
||||
VulnSurfaceOptions? options = null,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Batch query for multiple CVE+PURL pairs.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<VulnSurfaceResult>> GetAffectedSymbolsBatchAsync(
|
||||
IEnumerable<(string CveId, string Purl)> queries,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Result Model
|
||||
|
||||
```csharp
|
||||
public sealed record VulnSurfaceResult
|
||||
{
|
||||
public required string CveId { get; init; }
|
||||
public required string Purl { get; init; }
|
||||
public required ImmutableArray<AffectedSymbol> Symbols { get; init; }
|
||||
public required VulnSurfaceSource Source { get; init; }
|
||||
public required double Confidence { get; init; }
|
||||
public DateTimeOffset? CachedAt { get; init; }
|
||||
}
|
||||
|
||||
public sealed record AffectedSymbol
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string SymbolId { get; init; }
|
||||
public string? File { get; init; }
|
||||
public int? Line { get; init; }
|
||||
public string? Signature { get; init; }
|
||||
public SymbolChangeType ChangeType { get; init; }
|
||||
}
|
||||
|
||||
public enum VulnSurfaceSource
|
||||
{
|
||||
PatchDiff,
|
||||
Advisory,
|
||||
Heuristic,
|
||||
Unknown
|
||||
}
|
||||
|
||||
public enum SymbolChangeType
|
||||
{
|
||||
Modified, // Function code changed
|
||||
Added, // New guard/check added
|
||||
Removed, // Vulnerable code removed
|
||||
Renamed // Function renamed
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Integration with Concelier
|
||||
|
||||
The CVE→Symbol mapping service integrates with Concelier's advisory feed:
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌───────────────────┐
|
||||
│ Scanner │────►│ VulnSurface │────►│ Concelier │
|
||||
│ (Query) │ │ Service │ │ Advisory API │
|
||||
└─────────────────┘ └──────────────────┘ └───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Patch Diff │
|
||||
│ Analyzer │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
### 5.1 Advisory Client
|
||||
|
||||
```csharp
|
||||
public interface IAdvisoryClient
|
||||
{
|
||||
Task<Advisory?> GetAdvisoryAsync(string cveId, CancellationToken ct);
|
||||
Task<IReadOnlyList<AffectedPackage>> GetAffectedPackagesAsync(
|
||||
string cveId,
|
||||
CancellationToken ct);
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Caching Strategy
|
||||
|
||||
| Data | TTL | Invalidation |
|
||||
|------|-----|--------------|
|
||||
| Advisory metadata | 1 hour | On feed update |
|
||||
| Patch diff results | 24 hours | On new CVE revision |
|
||||
| Heuristic mappings | 15 minutes | On query |
|
||||
|
||||
---
|
||||
|
||||
## 6. Offline Support
|
||||
|
||||
For air-gapped environments:
|
||||
|
||||
### 6.1 Pre-computed Bundles
|
||||
|
||||
```
|
||||
offline-bundles/
|
||||
vuln-surfaces/
|
||||
cve-2024-*.json # Pre-computed mappings
|
||||
ecosystem-npm.json # NPM ecosystem mappings
|
||||
ecosystem-pypi.json # PyPI ecosystem mappings
|
||||
```
|
||||
|
||||
### 6.2 Bundle Format
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"generatedAt": "2025-12-22T00:00:00Z",
|
||||
"mappings": {
|
||||
"CVE-2024-1234": {
|
||||
"pkg:npm/lodash@4.17.21": {
|
||||
"symbols": ["template", "templateSettings"],
|
||||
"source": "patch_diff",
|
||||
"confidence": 0.95
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Fallback Behavior
|
||||
|
||||
When no mapping is available:
|
||||
|
||||
1. **Ecosystem-specific defaults**:
|
||||
- npm: All `exports` from package.json
|
||||
- PyPI: All public functions (`__all__`)
|
||||
- Native: All exported symbols (`.dynsym`)
|
||||
|
||||
2. **Conservative approach**:
|
||||
- Mark all public APIs as potentially affected
|
||||
- Set confidence = 0.3 (Inferred tier)
|
||||
- Include explanation in verdict reasons
|
||||
|
||||
3. **Manual override**:
|
||||
- Allow user-provided symbol lists via policy
|
||||
- Support suppression rules for known false positives
|
||||
|
||||
---
|
||||
|
||||
## 8. Performance Considerations
|
||||
|
||||
| Metric | Target | Notes |
|
||||
|--------|--------|-------|
|
||||
| Cache hit rate | >90% | Most queries hit cache |
|
||||
| Cold query latency | <500ms | Concelier API call |
|
||||
| Batch throughput | >100 queries/sec | Parallel execution |
|
||||
|
||||
---
|
||||
|
||||
## 9. Example Queries
|
||||
|
||||
### Simple Query
|
||||
|
||||
```http
|
||||
POST /api/vuln-surfaces/query
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"cveId": "CVE-2024-1234",
|
||||
"purl": "pkg:npm/lodash@4.17.21"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"cveId": "CVE-2024-1234",
|
||||
"purl": "pkg:npm/lodash@4.17.21",
|
||||
"symbols": [
|
||||
{
|
||||
"name": "template",
|
||||
"symbolId": "js:lodash/template",
|
||||
"file": "lodash.js",
|
||||
"line": 14850,
|
||||
"changeType": "modified"
|
||||
}
|
||||
],
|
||||
"source": "patch_diff",
|
||||
"confidence": 0.95
|
||||
}
|
||||
```
|
||||
|
||||
### Batch Query
|
||||
|
||||
```http
|
||||
POST /api/vuln-surfaces/batch
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"queries": [
|
||||
{"cveId": "CVE-2024-1234", "purl": "pkg:npm/lodash@4.17.21"},
|
||||
{"cveId": "CVE-2024-5678", "purl": "pkg:pypi/requests@2.28.0"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Related Documentation
|
||||
|
||||
- [Slice Schema](./slice-schema.md)
|
||||
- [Patch Oracles](./patch-oracles.md)
|
||||
- [Concelier Architecture](../modules/concelier/architecture.md)
|
||||
- [Vulnerability Surfaces](../modules/scanner/vuln-surfaces.md)
|
||||
|
||||
---
|
||||
|
||||
_Created: 2025-12-22. See Sprint 3810 for implementation details._
|
||||
332
docs/reachability/replay-verification.md
Normal file
332
docs/reachability/replay-verification.md
Normal file
@@ -0,0 +1,332 @@
|
||||
# Replay Verification
|
||||
|
||||
_Last updated: 2025-12-22. Owner: Scanner Guild._
|
||||
|
||||
This document describes the **replay verification** workflow that ensures reachability slices are reproducible and tamper-evident.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
Replay verification answers: *"Given the same inputs, do we get the exact same slice?"*
|
||||
|
||||
This is critical for:
|
||||
- **Audit trails**: Prove analysis results are genuine
|
||||
- **Tamper detection**: Detect modified inputs or results
|
||||
- **Debugging**: Identify sources of non-determinism
|
||||
- **Compliance**: Demonstrate reproducible security analysis
|
||||
|
||||
---
|
||||
|
||||
## 2. Replay Workflow
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌───────────────────┐
|
||||
│ Original │ │ Rehydrate │ │ Recompute │
|
||||
│ Slice │────►│ Inputs │────►│ Slice │
|
||||
│ (with digest) │ │ from CAS │ │ (fresh) │
|
||||
└─────────────────┘ └──────────────────┘ └───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ Compare │
|
||||
│ byte-for-byte │
|
||||
└───────────────────┘
|
||||
│
|
||||
┌─────────────┴─────────────┐
|
||||
▼ ▼
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ MATCH │ │ MISMATCH │
|
||||
│ ✓ │ │ + diff │
|
||||
└──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. API Reference
|
||||
|
||||
### 3.1 Replay Endpoint
|
||||
|
||||
```http
|
||||
POST /api/slices/replay
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"sliceDigest": "blake3:a1b2c3d4..."
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Response Format
|
||||
|
||||
**Match Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"match": true,
|
||||
"originalDigest": "blake3:a1b2c3d4...",
|
||||
"recomputedDigest": "blake3:a1b2c3d4...",
|
||||
"replayedAt": "2025-12-22T10:00:00Z",
|
||||
"inputsVerified": true
|
||||
}
|
||||
```
|
||||
|
||||
**Mismatch Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"match": false,
|
||||
"originalDigest": "blake3:a1b2c3d4...",
|
||||
"recomputedDigest": "blake3:e5f6g7h8...",
|
||||
"replayedAt": "2025-12-22T10:00:00Z",
|
||||
"diff": {
|
||||
"missingNodes": ["node:5"],
|
||||
"extraNodes": ["node:6"],
|
||||
"missingEdges": [{"from": "node:1", "to": "node:5"}],
|
||||
"extraEdges": [{"from": "node:1", "to": "node:6"}],
|
||||
"verdictDiff": {
|
||||
"original": "unreachable",
|
||||
"recomputed": "reachable"
|
||||
},
|
||||
"confidenceDiff": {
|
||||
"original": 0.95,
|
||||
"recomputed": 0.72
|
||||
}
|
||||
},
|
||||
"possibleCauses": [
|
||||
"Input graph may have been modified",
|
||||
"Analyzer version mismatch: 1.2.0 vs 1.2.1",
|
||||
"Feed version changed: nvd-2025-12-20 vs nvd-2025-12-22"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (404 Not Found)**:
|
||||
```json
|
||||
{
|
||||
"error": "slice_not_found",
|
||||
"message": "Slice with digest blake3:a1b2c3d4... not found in CAS",
|
||||
"sliceDigest": "blake3:a1b2c3d4..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Input Rehydration
|
||||
|
||||
All inputs must be CAS-addressed for replay:
|
||||
|
||||
### 4.1 Required Inputs
|
||||
|
||||
| Input | CAS Key | Description |
|
||||
|-------|---------|-------------|
|
||||
| Graph | `cas://graphs/{digest}` | Full RichGraph JSON |
|
||||
| Binaries | `cas://binaries/{digest}` | Binary file hashes |
|
||||
| SBOM | `cas://sboms/{digest}` | CycloneDX/SPDX document |
|
||||
| Policy | `cas://policies/{digest}` | Policy DSL |
|
||||
| Feeds | `cas://feeds/{version}` | Advisory feed snapshot |
|
||||
|
||||
### 4.2 Manifest Contents
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest": {
|
||||
"analyzerVersion": "scanner.native:1.2.0",
|
||||
"rulesetHash": "sha256:abc123...",
|
||||
"feedVersions": {
|
||||
"nvd": "2025-12-20",
|
||||
"osv": "2025-12-20",
|
||||
"ghsa": "2025-12-20"
|
||||
},
|
||||
"createdAt": "2025-12-22T10:00:00Z",
|
||||
"toolchain": "iced-x86:1.21.0",
|
||||
"environment": {
|
||||
"os": "linux",
|
||||
"arch": "x86_64"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Determinism Requirements
|
||||
|
||||
For byte-for-byte reproducibility:
|
||||
|
||||
### 5.1 JSON Canonicalization
|
||||
|
||||
```
|
||||
1. Keys sorted alphabetically at all levels
|
||||
2. No whitespace (compact JSON)
|
||||
3. UTF-8 encoding
|
||||
4. Lowercase hex for all hashes
|
||||
5. Numbers: no trailing zeros, scientific notation for large values
|
||||
```
|
||||
|
||||
### 5.2 Graph Ordering
|
||||
|
||||
```
|
||||
Nodes: sorted by symbolId (lexicographic)
|
||||
Edges: sorted by (from, to) tuple (lexicographic)
|
||||
Paths: sorted by first node, then path length
|
||||
```
|
||||
|
||||
### 5.3 Timestamp Handling
|
||||
|
||||
```
|
||||
All timestamps: UTC, ISO-8601, with 'Z' suffix
|
||||
Example: "2025-12-22T10:00:00Z"
|
||||
No milliseconds unless significant
|
||||
```
|
||||
|
||||
### 5.4 Floating Point
|
||||
|
||||
```
|
||||
Confidence values: round to 6 decimal places
|
||||
Example: 0.950000, not 0.95 or 0.9500001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Diff Computation
|
||||
|
||||
When slices don't match:
|
||||
|
||||
### 6.1 Diff Algorithm
|
||||
|
||||
```python
|
||||
def compute_diff(original, recomputed):
|
||||
diff = SliceDiff()
|
||||
|
||||
# Node diff
|
||||
orig_nodes = set(n.id for n in original.subgraph.nodes)
|
||||
new_nodes = set(n.id for n in recomputed.subgraph.nodes)
|
||||
diff.missing_nodes = list(orig_nodes - new_nodes)
|
||||
diff.extra_nodes = list(new_nodes - orig_nodes)
|
||||
|
||||
# Edge diff
|
||||
orig_edges = set((e.from, e.to) for e in original.subgraph.edges)
|
||||
new_edges = set((e.from, e.to) for e in recomputed.subgraph.edges)
|
||||
diff.missing_edges = list(orig_edges - new_edges)
|
||||
diff.extra_edges = list(new_edges - orig_edges)
|
||||
|
||||
# Verdict diff
|
||||
if original.verdict.status != recomputed.verdict.status:
|
||||
diff.verdict_diff = {
|
||||
"original": original.verdict.status,
|
||||
"recomputed": recomputed.verdict.status
|
||||
}
|
||||
|
||||
return diff
|
||||
```
|
||||
|
||||
### 6.2 Cause Analysis
|
||||
|
||||
```python
|
||||
def analyze_causes(original, recomputed, manifest):
|
||||
causes = []
|
||||
|
||||
if manifest.analyzerVersion != current_version():
|
||||
causes.append(f"Analyzer version mismatch")
|
||||
|
||||
if manifest.feedVersions != current_feed_versions():
|
||||
causes.append(f"Feed version changed")
|
||||
|
||||
if original.inputs.graphDigest != fetch_graph_digest():
|
||||
causes.append(f"Input graph may have been modified")
|
||||
|
||||
return causes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. CLI Usage
|
||||
|
||||
### 7.1 Replay Command
|
||||
|
||||
```bash
|
||||
# Replay and verify a slice
|
||||
stella slice replay --digest blake3:a1b2c3d4...
|
||||
|
||||
# Output:
|
||||
# ✓ Slice verified: digest matches
|
||||
# Original: blake3:a1b2c3d4...
|
||||
# Recomputed: blake3:a1b2c3d4...
|
||||
```
|
||||
|
||||
### 7.2 Verbose Mode
|
||||
|
||||
```bash
|
||||
stella slice replay --digest blake3:a1b2c3d4... --verbose
|
||||
|
||||
# Output:
|
||||
# Fetching slice from CAS...
|
||||
# Rehydrating inputs:
|
||||
# - Graph: cas://graphs/blake3:xyz... ✓
|
||||
# - SBOM: cas://sboms/sha256:abc... ✓
|
||||
# - Policy: cas://policies/sha256:def... ✓
|
||||
# Recomputing slice...
|
||||
# Comparing results...
|
||||
# ✓ Match confirmed
|
||||
```
|
||||
|
||||
### 7.3 Mismatch Handling
|
||||
|
||||
```bash
|
||||
stella slice replay --digest blake3:a1b2c3d4...
|
||||
|
||||
# Output:
|
||||
# ✗ Slice mismatch detected!
|
||||
#
|
||||
# Differences:
|
||||
# Nodes: 1 missing, 0 extra
|
||||
# Edges: 1 missing, 1 extra
|
||||
# Verdict: unreachable → reachable
|
||||
#
|
||||
# Possible causes:
|
||||
# - Input graph may have been modified
|
||||
# - Analyzer version: 1.2.0 → 1.2.1
|
||||
#
|
||||
# Run with --diff-file to export detailed diff
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Error Handling
|
||||
|
||||
| Error | Cause | Resolution |
|
||||
|-------|-------|------------|
|
||||
| `slice_not_found` | Slice not in CAS | Check digest, verify upload |
|
||||
| `input_not_found` | Referenced input missing | Reupload inputs |
|
||||
| `version_mismatch` | Analyzer version differs | Pin version or accept drift |
|
||||
| `feed_stale` | Feed snapshot unavailable | Use latest or pin version |
|
||||
|
||||
---
|
||||
|
||||
## 9. Security Considerations
|
||||
|
||||
1. **Input integrity**: Verify CAS digests before replay
|
||||
2. **Audit logging**: Log all replay attempts
|
||||
3. **Rate limiting**: Prevent replay DoS
|
||||
4. **Access control**: Same permissions as slice access
|
||||
|
||||
---
|
||||
|
||||
## 10. Performance Targets
|
||||
|
||||
| Metric | Target |
|
||||
|--------|--------|
|
||||
| Replay latency | <5s for typical slice |
|
||||
| Input fetch | <2s (parallel CAS fetches) |
|
||||
| Comparison | <100ms |
|
||||
|
||||
---
|
||||
|
||||
## 11. Related Documentation
|
||||
|
||||
- [Slice Schema](./slice-schema.md)
|
||||
- [Binary Reachability Schema](./binary-reachability-schema.md)
|
||||
- [Determinism Requirements](../contracts/determinism.md)
|
||||
- [CAS Architecture](../modules/platform/cas.md)
|
||||
|
||||
---
|
||||
|
||||
_Created: 2025-12-22. See Sprint 3820 for implementation details._
|
||||
287
docs/reachability/slice-schema.md
Normal file
287
docs/reachability/slice-schema.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Reachability Slice Schema
|
||||
|
||||
_Last updated: 2025-12-22. Owner: Scanner Guild._
|
||||
|
||||
This document defines the **Reachability Slice** schema—a minimal, attestable proof unit that answers whether a vulnerable symbol is reachable from application entrypoints.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
A **slice** is a focused subgraph extracted from a full reachability graph, containing only the nodes and edges relevant to answering a specific reachability query (e.g., "Is CVE-2024-1234's vulnerable function reachable?").
|
||||
|
||||
### Key Properties
|
||||
|
||||
| Property | Description |
|
||||
|----------|-------------|
|
||||
| **Minimal** | Contains only nodes/edges on paths between entrypoints and targets |
|
||||
| **Attestable** | DSSE-signed with in-toto predicate format |
|
||||
| **Reproducible** | Same inputs → same bytes (deterministic) |
|
||||
| **Content-addressed** | Retrieved by BLAKE3 digest |
|
||||
|
||||
---
|
||||
|
||||
## 2. Schema Definition
|
||||
|
||||
### 2.1 DSSE Predicate Type
|
||||
|
||||
```
|
||||
https://stellaops.dev/predicates/reachability-slice/v1
|
||||
```
|
||||
|
||||
### 2.2 Full Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://stellaops.dev/schemas/reachability-slice.v1.schema.json",
|
||||
"title": "Reachability Slice",
|
||||
"type": "object",
|
||||
"required": ["_type", "inputs", "query", "subgraph", "verdict", "manifest"],
|
||||
"properties": {
|
||||
"_type": {
|
||||
"const": "https://stellaops.dev/predicates/reachability-slice/v1"
|
||||
},
|
||||
"inputs": { "$ref": "#/$defs/SliceInputs" },
|
||||
"query": { "$ref": "#/$defs/SliceQuery" },
|
||||
"subgraph": { "$ref": "#/$defs/SliceSubgraph" },
|
||||
"verdict": { "$ref": "#/$defs/SliceVerdict" },
|
||||
"manifest": { "$ref": "#/$defs/ScanManifest" }
|
||||
},
|
||||
"$defs": {
|
||||
"SliceInputs": {
|
||||
"type": "object",
|
||||
"required": ["graphDigest", "binaryDigests"],
|
||||
"properties": {
|
||||
"graphDigest": { "type": "string", "pattern": "^blake3:[a-f0-9]{64}$" },
|
||||
"binaryDigests": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }
|
||||
},
|
||||
"sbomDigest": { "type": "string" },
|
||||
"layerDigests": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
"SliceQuery": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cveId": { "type": "string", "pattern": "^CVE-\\d{4}-\\d+$" },
|
||||
"targetSymbols": { "type": "array", "items": { "type": "string" } },
|
||||
"entrypoints": { "type": "array", "items": { "type": "string" } },
|
||||
"policyHash": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"SliceSubgraph": {
|
||||
"type": "object",
|
||||
"required": ["nodes", "edges"],
|
||||
"properties": {
|
||||
"nodes": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/SliceNode" }
|
||||
},
|
||||
"edges": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/SliceEdge" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"SliceNode": {
|
||||
"type": "object",
|
||||
"required": ["id", "symbol", "kind"],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"symbol": { "type": "string" },
|
||||
"kind": { "enum": ["entrypoint", "intermediate", "target", "unknown"] },
|
||||
"file": { "type": "string" },
|
||||
"line": { "type": "integer" },
|
||||
"purl": { "type": "string" },
|
||||
"attributes": { "type": "object" }
|
||||
}
|
||||
},
|
||||
"SliceEdge": {
|
||||
"type": "object",
|
||||
"required": ["from", "to", "confidence"],
|
||||
"properties": {
|
||||
"from": { "type": "string" },
|
||||
"to": { "type": "string" },
|
||||
"kind": { "enum": ["direct", "plt", "iat", "dynamic", "unknown"] },
|
||||
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
||||
"evidence": { "type": "string" },
|
||||
"gate": { "$ref": "#/$defs/GateInfo" },
|
||||
"observed": { "$ref": "#/$defs/ObservedInfo" }
|
||||
}
|
||||
},
|
||||
"GateInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": { "enum": ["feature_flag", "auth", "config", "admin_only"] },
|
||||
"condition": { "type": "string" },
|
||||
"satisfied": { "type": "boolean" }
|
||||
}
|
||||
},
|
||||
"ObservedInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"firstObserved": { "type": "string", "format": "date-time" },
|
||||
"lastObserved": { "type": "string", "format": "date-time" },
|
||||
"count": { "type": "integer" }
|
||||
}
|
||||
},
|
||||
"SliceVerdict": {
|
||||
"type": "object",
|
||||
"required": ["status", "confidence"],
|
||||
"properties": {
|
||||
"status": { "enum": ["reachable", "unreachable", "unknown", "gated"] },
|
||||
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
||||
"reasons": { "type": "array", "items": { "type": "string" } },
|
||||
"pathWitnesses": { "type": "array", "items": { "type": "string" } },
|
||||
"unknownCount": { "type": "integer" },
|
||||
"gatedPaths": { "type": "array", "items": { "$ref": "#/$defs/GateInfo" } }
|
||||
}
|
||||
},
|
||||
"ScanManifest": {
|
||||
"type": "object",
|
||||
"required": ["analyzerVersion", "createdAt"],
|
||||
"properties": {
|
||||
"analyzerVersion": { "type": "string" },
|
||||
"rulesetHash": { "type": "string" },
|
||||
"feedVersions": { "type": "object" },
|
||||
"createdAt": { "type": "string", "format": "date-time" },
|
||||
"toolchain": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Verdict Status Definitions
|
||||
|
||||
| Status | Meaning | Confidence Range |
|
||||
|--------|---------|------------------|
|
||||
| `reachable` | Path exists from entrypoint to target | ≥0.7 |
|
||||
| `unreachable` | No path found, no unknowns | ≥0.9 |
|
||||
| `unknown` | Unknowns present on potential paths | 0.3–0.7 |
|
||||
| `gated` | Path exists but gated by feature flag/auth | 0.5–0.8 |
|
||||
|
||||
### Verdict Computation Rules
|
||||
|
||||
```
|
||||
reachable := path_exists AND min(path_confidence) ≥ 0.7 AND unknown_edges = 0
|
||||
unreachable := NOT path_exists AND unknown_edges = 0
|
||||
gated := path_exists AND all_paths_gated AND gates_not_satisfied
|
||||
unknown := unknown_edges > 0 OR min(path_confidence) < 0.5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Example Slice
|
||||
|
||||
```json
|
||||
{
|
||||
"_type": "https://stellaops.dev/predicates/reachability-slice/v1",
|
||||
"inputs": {
|
||||
"graphDigest": "blake3:a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
|
||||
"binaryDigests": ["sha256:deadbeef..."],
|
||||
"sbomDigest": "sha256:cafebabe..."
|
||||
},
|
||||
"query": {
|
||||
"cveId": "CVE-2024-1234",
|
||||
"targetSymbols": ["openssl:EVP_PKEY_decrypt"],
|
||||
"entrypoints": ["main", "http_handler"]
|
||||
},
|
||||
"subgraph": {
|
||||
"nodes": [
|
||||
{"id": "node:1", "symbol": "main", "kind": "entrypoint", "file": "/app/main.c", "line": 42},
|
||||
{"id": "node:2", "symbol": "process_request", "kind": "intermediate", "file": "/app/handler.c", "line": 100},
|
||||
{"id": "node:3", "symbol": "decrypt_data", "kind": "intermediate", "file": "/app/crypto.c", "line": 55},
|
||||
{"id": "node:4", "symbol": "EVP_PKEY_decrypt", "kind": "target", "purl": "pkg:generic/openssl@3.0.0"}
|
||||
],
|
||||
"edges": [
|
||||
{"from": "node:1", "to": "node:2", "kind": "direct", "confidence": 1.0},
|
||||
{"from": "node:2", "to": "node:3", "kind": "direct", "confidence": 0.95},
|
||||
{"from": "node:3", "to": "node:4", "kind": "plt", "confidence": 0.9}
|
||||
]
|
||||
},
|
||||
"verdict": {
|
||||
"status": "reachable",
|
||||
"confidence": 0.9,
|
||||
"reasons": ["Direct call path from main() to EVP_PKEY_decrypt()"],
|
||||
"pathWitnesses": ["main → process_request → decrypt_data → EVP_PKEY_decrypt"]
|
||||
},
|
||||
"manifest": {
|
||||
"analyzerVersion": "scanner.native:1.2.0",
|
||||
"rulesetHash": "sha256:...",
|
||||
"createdAt": "2025-12-22T10:00:00Z",
|
||||
"toolchain": "iced-x86:1.21.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. DSSE Envelope Format
|
||||
|
||||
Slices are wrapped in DSSE envelopes for attestation:
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": "<base64-encoded slice JSON>",
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "sha256:abc123...",
|
||||
"sig": "<base64-encoded signature>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Storage & Retrieval
|
||||
|
||||
### CAS URI Format
|
||||
|
||||
```
|
||||
cas://slices/blake3:<digest>
|
||||
```
|
||||
|
||||
### OCI Artifact Format
|
||||
|
||||
```json
|
||||
{
|
||||
"mediaType": "application/vnd.stellaops.slice.v1+json",
|
||||
"digest": "sha256:...",
|
||||
"annotations": {
|
||||
"org.stellaops.slice.cve": "CVE-2024-1234",
|
||||
"org.stellaops.slice.verdict": "reachable"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Determinism Requirements
|
||||
|
||||
For reproducible slices:
|
||||
|
||||
1. **Node ordering**: Sort by `id` lexicographically
|
||||
2. **Edge ordering**: Sort by `(from, to)` tuple
|
||||
3. **Timestamps**: Use UTC ISO-8601 with Z suffix
|
||||
4. **Floating point**: Round to 6 decimal places
|
||||
5. **JSON serialization**: No whitespace, sorted keys
|
||||
|
||||
---
|
||||
|
||||
## 8. Related Documentation
|
||||
|
||||
- [Binary Reachability Schema](./binary-reachability-schema.md)
|
||||
- [RichGraph Contract](../contracts/richgraph-v1.md)
|
||||
- [Function-Level Evidence](./function-level-evidence.md)
|
||||
- [Replay Verification](./replay-verification.md)
|
||||
|
||||
---
|
||||
|
||||
_Created: 2025-12-22. See Sprint 3810 for implementation details._
|
||||
Reference in New Issue
Block a user