finish off sprint advisories and sprints
This commit is contained in:
237
docs/contracts/function-map-v1.md
Normal file
237
docs/contracts/function-map-v1.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# Function Map V1 Contract
|
||||
|
||||
> **Predicate Type:** `https://stella.ops/predicates/function-map/v1`
|
||||
> **DSSE Payload Type:** `application/vnd.stellaops.function-map.v1+json`
|
||||
> **Schema Version:** `1.0.0`
|
||||
|
||||
## Overview
|
||||
|
||||
A function map predicate declares the expected call paths for a service component, enabling verification of runtime behavior against static analysis. It follows the [in-toto attestation](https://github.com/in-toto/attestation) framework.
|
||||
|
||||
---
|
||||
|
||||
## Predicate Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "https://stella.ops/predicates/function-map/v1",
|
||||
"subject": {
|
||||
"purl": "pkg:oci/my-service@sha256:abc123...",
|
||||
"digest": { "sha256": "abc123..." }
|
||||
},
|
||||
"predicate": {
|
||||
"service": "my-backend",
|
||||
"build_id": "build-456",
|
||||
"expected_paths": [...],
|
||||
"coverage": {
|
||||
"min_observation_rate": 0.95,
|
||||
"window_seconds": 1800,
|
||||
"fail_on_unexpected": false
|
||||
},
|
||||
"generated_at": "2026-01-23T10:00:00Z",
|
||||
"generated_from": {
|
||||
"sbom_ref": "oci://registry/sbom@sha256:...",
|
||||
"static_analysis_ref": "oci://registry/analysis@sha256:..."
|
||||
},
|
||||
"generator": {
|
||||
"name": "stella-cli",
|
||||
"version": "2.0.0",
|
||||
"commit": "abc123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Subject
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `purl` | string | Yes | Package URL of the subject artifact |
|
||||
| `digest` | object | Yes | Content digest (sha256, sha512, etc.) |
|
||||
|
||||
---
|
||||
|
||||
## Predicate Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `service` | string | Yes | Service name for correlation |
|
||||
| `build_id` | string | No | Build identifier for provenance correlation |
|
||||
| `expected_paths` | array | Yes | List of expected call paths |
|
||||
| `coverage` | object | Yes | Coverage thresholds for verification |
|
||||
| `generated_at` | ISO 8601 | Yes | Generation timestamp |
|
||||
| `generated_from` | object | No | Source references (SBOM, static analysis) |
|
||||
| `generator` | object | No | Tool that generated the predicate |
|
||||
|
||||
---
|
||||
|
||||
## Expected Path
|
||||
|
||||
Each expected path represents a call chain starting from an entrypoint:
|
||||
|
||||
```json
|
||||
{
|
||||
"path_id": "path-001",
|
||||
"entrypoint": {
|
||||
"symbol": "handleRequest",
|
||||
"node_hash": "sha256:..."
|
||||
},
|
||||
"expected_calls": [
|
||||
{
|
||||
"symbol": "crypto_sign",
|
||||
"purl": "pkg:deb/libcrypto3@3.0.0",
|
||||
"node_hash": "sha256:...",
|
||||
"probe_types": ["uprobe"],
|
||||
"optional": false,
|
||||
"function_address": null,
|
||||
"binary_path": "/usr/lib/libcrypto.so.3"
|
||||
}
|
||||
],
|
||||
"path_hash": "sha256:...",
|
||||
"optional": false,
|
||||
"strict_ordering": false,
|
||||
"tags": ["crypto"]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `path_id` | string | Yes | Unique path identifier |
|
||||
| `entrypoint` | object | Yes | Path entry point (symbol + node_hash) |
|
||||
| `expected_calls` | array | Yes | List of expected function calls |
|
||||
| `path_hash` | string | Yes | SHA-256(entrypoint \|\| sorted calls) |
|
||||
| `optional` | boolean | No | Whether this path is optional (default false) |
|
||||
| `strict_ordering` | boolean | No | Ordered sequence vs unordered set (default false) |
|
||||
| `tags` | array | No | Categorization tags (crypto, auth, network, etc.) |
|
||||
|
||||
---
|
||||
|
||||
## Expected Call
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `symbol` | string | Yes | Function name (demangled) |
|
||||
| `purl` | string | Yes | Package URL of the component containing this function |
|
||||
| `node_hash` | string | Yes | SHA-256(PURL + normalized symbol) |
|
||||
| `probe_types` | array | Yes | Acceptable probe types for observation |
|
||||
| `optional` | boolean | No | Whether this call is optional (default false) |
|
||||
| `function_address` | string | No | Address hint for probe attachment |
|
||||
| `binary_path` | string | No | Binary path for uprobe attachment |
|
||||
|
||||
### Probe Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `kprobe` | Kernel function entry |
|
||||
| `kretprobe` | Kernel function return |
|
||||
| `uprobe` | User-space function entry |
|
||||
| `uretprobe` | User-space function return |
|
||||
| `tracepoint` | Kernel tracepoint |
|
||||
| `usdt` | User-space statically defined tracing |
|
||||
|
||||
---
|
||||
|
||||
## Coverage Thresholds
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `min_observation_rate` | double | 0.95 | Minimum fraction of paths that must be observed |
|
||||
| `window_seconds` | integer | 1800 | Observation window duration |
|
||||
| `fail_on_unexpected` | boolean | false | Whether unexpected symbols cause verification failure |
|
||||
|
||||
---
|
||||
|
||||
## Node Hash Recipe
|
||||
|
||||
Node hashes provide content-addressable identifiers for function calls, matching the [Witness V1](witness-v1.md) convention:
|
||||
|
||||
```
|
||||
node_hash = SHA-256(PURL + ":" + normalize(symbol))
|
||||
```
|
||||
|
||||
Where `normalize(symbol)`:
|
||||
1. Demangle C++/Rust symbols
|
||||
2. Strip leading underscores (platform convention)
|
||||
3. Lowercase the result
|
||||
4. Remove whitespace
|
||||
|
||||
### Path Hash Recipe
|
||||
|
||||
```
|
||||
path_hash = SHA-256(entrypoint.node_hash + ":" + sort(calls.map(c => c.node_hash)).join(":"))
|
||||
```
|
||||
|
||||
The path hash is independent of call ordering (sorted) unless `strict_ordering` is true, in which case calls are not sorted before hashing.
|
||||
|
||||
---
|
||||
|
||||
## Coverage Calculation Algorithm
|
||||
|
||||
```
|
||||
total_required = count(paths where optional == false)
|
||||
observed_required = count(paths where optional == false AND has_matching_observation)
|
||||
|
||||
observation_rate = observed_required / total_required
|
||||
= 0.0 if total_required == 0
|
||||
|
||||
verified = observation_rate >= coverage.min_observation_rate
|
||||
```
|
||||
|
||||
For each path, an observation "matches" when:
|
||||
- At least one observation has a `node_hash` matching any call in the path
|
||||
- The observation falls within the time window
|
||||
- The probe type is in the call's `probe_types` list
|
||||
|
||||
---
|
||||
|
||||
## Verification Algorithm
|
||||
|
||||
```
|
||||
VERIFY(predicate, observations, options):
|
||||
1. Filter observations to time window [now - window_seconds, now]
|
||||
2. For each required expected_path:
|
||||
a. For each expected_call in path:
|
||||
- Find observations matching node_hash AND probe_type
|
||||
- Mark call as "observed" if any match found
|
||||
b. Mark path as "covered" if entrypoint OR any call observed
|
||||
3. Compute observation_rate = covered_paths / required_paths
|
||||
4. Collect unexpected = observations not matching any expected call
|
||||
5. Collect missing = required calls with no matching observation
|
||||
6. verified = observation_rate >= min_observation_rate
|
||||
AND (NOT fail_on_unexpected OR unexpected.count == 0)
|
||||
7. Return result with breakdown, unexpected, missing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Media Types
|
||||
|
||||
| Usage | Media Type |
|
||||
|-------|-----------|
|
||||
| Function map predicate | `application/vnd.stella.function-map+json` |
|
||||
| DSSE-signed predicate | `application/vnd.dsse+json` |
|
||||
| Observations | `application/x-ndjson` |
|
||||
| Verification report | `application/vnd.stella.verification-report+json` |
|
||||
|
||||
---
|
||||
|
||||
## Observation Record (NDJSON)
|
||||
|
||||
Each line in an observations file:
|
||||
|
||||
```json
|
||||
{
|
||||
"observation_id": "obs-123",
|
||||
"node_hash": "sha256:...",
|
||||
"function_name": "crypto_sign",
|
||||
"probe_type": "uprobe",
|
||||
"observed_at": "2026-01-23T10:05:00Z",
|
||||
"observation_count": 42,
|
||||
"container_id": "abc123",
|
||||
"pod_name": "my-service-pod-xyz",
|
||||
"namespace": "production",
|
||||
"duration_microseconds": 150
|
||||
}
|
||||
```
|
||||
51
docs/contracts/sbom-volatile-fields.json
Normal file
51
docs/contracts/sbom-volatile-fields.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "SBOM Volatile Fields Contract",
|
||||
"description": "Authoritative list of SBOM fields stripped before canonicalization to ensure deterministic hashes. Referenced by SbomNormalizer.",
|
||||
"version": 1,
|
||||
"cyclonedx": {
|
||||
"strip": [
|
||||
{
|
||||
"path": "serialNumber",
|
||||
"scope": "root",
|
||||
"rationale": "UUID regenerated on every BOM creation; not content-derived."
|
||||
},
|
||||
{
|
||||
"path": "metadata.timestamp",
|
||||
"scope": "metadata",
|
||||
"rationale": "Generation timestamp varies per run; not content-derived."
|
||||
},
|
||||
{
|
||||
"path": "metadata.tools",
|
||||
"scope": "metadata",
|
||||
"rationale": "Tool name/version/vendor varies across scanner installs; does not reflect scanned content."
|
||||
},
|
||||
{
|
||||
"path": "metadata.authors",
|
||||
"scope": "metadata",
|
||||
"rationale": "Author identity varies per operator; does not affect component inventory."
|
||||
}
|
||||
],
|
||||
"specVersions": ["1.4", "1.5", "1.6", "1.7"]
|
||||
},
|
||||
"spdx": {
|
||||
"strip": [
|
||||
{
|
||||
"path": "creationInfo.created",
|
||||
"scope": "creationInfo",
|
||||
"rationale": "Timestamp of SPDX document creation; varies per run."
|
||||
},
|
||||
{
|
||||
"path": "creationInfo.creators",
|
||||
"scope": "creationInfo",
|
||||
"rationale": "Tool identifiers include version strings (e.g., 'Tool: syft-1.2.3'); varies across installs."
|
||||
},
|
||||
{
|
||||
"path": "creationInfo.licenseListVersion",
|
||||
"scope": "creationInfo",
|
||||
"rationale": "Tracks upstream SPDX license list version available at scan time; not content-derived."
|
||||
}
|
||||
],
|
||||
"specVersions": ["2.2", "2.3", "3.0", "3.0.1"]
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
# Sprint 20260120-029 · Delta Delivery Attestation (Planning Only)
|
||||
|
||||
## Topic & Scope
|
||||
|
||||
- **Status:** PLANNING - Not committed to implementation
|
||||
- **Origin:** Product advisory on delta-sig attestation for verifiable update delivery
|
||||
- **Purpose:** Scope the work required to extend delta-sig from CVE detection to delta patch delivery
|
||||
- Working directory: `src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.DeltaSig`
|
||||
- This document captures analysis and proposed tasks; no implementation is scheduled.
|
||||
|
||||
## Advisory Summary
|
||||
|
||||
The advisory proposes extending Stella Ops' delta-sig capabilities to support **patch delivery and reconstruction verification**, similar to:
|
||||
|
||||
- **Chromium Courgette/Zucchini:** Instruction-aware binary diffing for smaller patches
|
||||
- **Microsoft MSDelta/LZX-delta:** Windows Update delta compression
|
||||
- **deltarpm:** RPM delta packages that rebuild full RPMs from installed content
|
||||
- **zchunk:** Chunk-based delta format with HTTP range requests and independent verification
|
||||
|
||||
### Current vs. Proposed Use Cases
|
||||
|
||||
| Aspect | Current Implementation | Advisory Proposal |
|
||||
|--------|------------------------|-------------------|
|
||||
| **Purpose** | CVE detection via signature matching | Patch delivery and reconstruction |
|
||||
| **Question answered** | "Does this binary have the security patch?" | "Can I apply this delta to reconstruct the target?" |
|
||||
| **Data flow** | Signature DB → Match target → Verdict | Base + Delta → Apply → Reconstruct target |
|
||||
| **Output** | `vulnerable`/`patched` verdict | Reconstructed binary + verification |
|
||||
|
||||
## Gap Analysis
|
||||
|
||||
### Already Covered (No Gap)
|
||||
|
||||
1. **Function-level signatures** - v1 and v2 predicates with `DeltaSignature`, `SymbolSignature`, chunk hashes
|
||||
2. **Multiple hash algorithms** - SHA-256/384/512, CFG edge hash, semantic hash
|
||||
3. **Normalization recipes** - `NormalizationRef` with recipe ID, version, steps
|
||||
4. **Deterministic signature generation** - Fully implemented
|
||||
5. **IR-level semantic analysis** - v2 has `IrDiffReferenceV2` with CAS storage
|
||||
6. **DSSE envelope signing** - Implemented via `DeltaSigAttestorIntegration`
|
||||
7. **Reproducible rebuild infrastructure** - `IRebuildService` exists (for full packages)
|
||||
|
||||
### Identified Gaps
|
||||
|
||||
#### GAP-1: Base Artifact Reference for Delta Application
|
||||
|
||||
**Advisory requirement:** "Base artifact reference: canonical artifact ID + digest(s) of the required base."
|
||||
|
||||
**Current state:** `DeltaSigPredicateV2.Subject` is a single artifact. No field to specify base for reconstruction.
|
||||
|
||||
**Proposed schema extension:**
|
||||
```json
|
||||
{
|
||||
"baselineReference": {
|
||||
"purl": "pkg:deb/debian/openssl@1.1.1k-1",
|
||||
"digest": { "sha256": "abc123..." },
|
||||
"buildId": "...",
|
||||
"requiredExact": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GAP-2: Reconstruction Algorithm Fingerprint
|
||||
|
||||
**Advisory requirement:** "Algorithm fingerprint: `{courgette|zucchini|msdelta|deltarpm|zchunk}@version`"
|
||||
|
||||
**Current state:** `MatchAlgorithm` tracks detection algorithms, not reconstruction algorithms.
|
||||
|
||||
**Proposed schema extension:**
|
||||
```json
|
||||
{
|
||||
"reconstructionAlgorithm": {
|
||||
"algorithm": "zchunk",
|
||||
"version": "1.5.2",
|
||||
"dictionaryDigest": "sha256:def456..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GAP-3: Chunk/Segment Map for Stream Verification
|
||||
|
||||
**Advisory requirement:** "Chunk/segment map: offsets, sizes, per-chunk hashes to stream-verify during apply."
|
||||
|
||||
**Current state:** `ChunkHash` designed for matching, not reconstruction verification.
|
||||
|
||||
**Proposed schema extension:**
|
||||
```json
|
||||
{
|
||||
"segmentMap": [
|
||||
{ "offset": 0, "size": 4096, "status": "unchanged", "hash": "..." },
|
||||
{ "offset": 4096, "size": 512, "status": "modified", "oldHash": "...", "newHash": "..." },
|
||||
{ "offset": 4608, "size": 1024, "status": "new", "hash": "..." }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### GAP-4: Proof Reference to Build Info
|
||||
|
||||
**Advisory requirement:** "Human/readable `proof_ref`: link to buildinfo or exact reproduce-instructions."
|
||||
|
||||
**Current state:** `IRebuildService` exists but not linked from predicates.
|
||||
|
||||
**Proposed schema extension:**
|
||||
```json
|
||||
{
|
||||
"proofRef": {
|
||||
"buildinfoUrl": "https://buildinfo.example.com/openssl_1.1.1k-1.buildinfo",
|
||||
"buildinfoDigest": "sha256:...",
|
||||
"reproducibilityBackend": "reproduce.debian.net"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GAP-5: Two-Part Trust (Content + Manifest Signing)
|
||||
|
||||
**Advisory requirement:** "Two-part trust: code-sign the post-image AND sign the update manifest."
|
||||
|
||||
**Current state:** Single DSSE envelope signs the predicate.
|
||||
|
||||
**Proposed new type:**
|
||||
```json
|
||||
{
|
||||
"manifestType": "https://stella-ops.org/delta-manifest/v1",
|
||||
"baseArtifact": { "purl": "...", "digest": {...} },
|
||||
"deltaArtifact": { "url": "...", "digest": {...}, "algorithm": "zchunk@1.5" },
|
||||
"targetArtifact": { "purl": "...", "digest": {...} },
|
||||
"predicateRef": "sha256:...",
|
||||
"manifestSignatures": [...]
|
||||
}
|
||||
```
|
||||
|
||||
#### GAP-6: Reconstruction Service
|
||||
|
||||
**Advisory requirement:** "Reconstruction-first: given base + delta, reassemble in a clean sandbox."
|
||||
|
||||
**Current state:** No `IDeltaApplicationService`.
|
||||
|
||||
**Proposed interface:**
|
||||
```csharp
|
||||
public interface IDeltaApplicationService
|
||||
{
|
||||
Task<DeltaApplicationResult> ApplyAsync(
|
||||
Stream baseArtifact,
|
||||
Stream deltaArtifact,
|
||||
ReconstructionAlgorithm algorithm,
|
||||
CancellationToken ct);
|
||||
|
||||
Task<bool> VerifyReconstructionAsync(
|
||||
Stream reconstructedArtifact,
|
||||
string expectedDigest,
|
||||
CancellationToken ct);
|
||||
}
|
||||
```
|
||||
|
||||
#### GAP-7: Acceptance Test Harness
|
||||
|
||||
**Advisory requirement:** "Signature/manifest checks, byte-for-byte equality."
|
||||
|
||||
**Current state:** No reconstruction tests.
|
||||
|
||||
**Required:** Test harness for base + delta → reconstruction → verification.
|
||||
|
||||
## Dependencies & Concurrency
|
||||
|
||||
- **Upstream:** Existing delta-sig v2 predicates (SPRINT_20260119_004 - DONE)
|
||||
- **Upstream:** Reproducible rebuild infrastructure (SPRINT_20260119_005)
|
||||
- **New dependency:** zchunk library or native binding
|
||||
- **Optional:** Courgette/Zucchini (Chrome's algorithm) if PE/ELF optimization needed
|
||||
- **Parallel-safe:** Schema design can proceed independently of algorithm implementation
|
||||
|
||||
## Proposed Task Breakdown
|
||||
|
||||
### Phase 1: Schema Extensions
|
||||
|
||||
| Task ID | Description | Effort |
|
||||
|---------|-------------|--------|
|
||||
| DDS-001 | Extend `DeltaSigPredicateV2` with `BaselineReference` field | Small |
|
||||
| DDS-002 | Add `ReconstructionAlgorithm` to predicate schema | Small |
|
||||
| DDS-003 | Define `SegmentMap` model for stream verification | Medium |
|
||||
| DDS-004 | Link predicate to `.buildinfo` via `ProofRef` | Small |
|
||||
| DDS-005 | Define `DeltaManifest` type and signing flow | Medium |
|
||||
|
||||
### Phase 2: Service Implementation
|
||||
|
||||
| Task ID | Description | Effort |
|
||||
|---------|-------------|--------|
|
||||
| DDS-006 | Implement `IDeltaApplicationService` interface | Medium |
|
||||
| DDS-007 | zchunk backend for delta application | Large |
|
||||
| DDS-008 | Optional: Courgette/Zucchini backend for PE/ELF | Large |
|
||||
| DDS-009 | Optional: MSDelta backend for Windows | Medium |
|
||||
|
||||
### Phase 3: Verification & Testing
|
||||
|
||||
| Task ID | Description | Effort |
|
||||
|---------|-------------|--------|
|
||||
| DDS-010 | Reconstruction test harness | Medium |
|
||||
| DDS-011 | Byte-for-byte equality verification tests | Small |
|
||||
| DDS-012 | Manifest signature verification tests | Small |
|
||||
|
||||
### Phase 4: Documentation
|
||||
|
||||
| Task ID | Description | Effort |
|
||||
|---------|-------------|--------|
|
||||
| DDS-013 | JSON schema for delta-manifest | Small |
|
||||
| DDS-014 | Documentation updates for delta delivery | Medium |
|
||||
| DDS-015 | CLI command updates (if applicable) | Medium |
|
||||
|
||||
## Decisions & Risks
|
||||
|
||||
### Key Decisions Needed
|
||||
|
||||
- **D1:** Which reconstruction algorithms to support initially? (zchunk recommended for cross-platform)
|
||||
- **D2:** Is manifest signing required, or is predicate signing sufficient?
|
||||
- **D3:** Should delta delivery be a separate predicate type or extension of v2?
|
||||
- **D4:** Air-gap story: pre-bundle deltas or rely on CAS?
|
||||
|
||||
### Risks
|
||||
|
||||
- **R1:** zchunk library may require native bindings (no pure .NET implementation)
|
||||
- **R2:** Courgette/Zucchini are C++ and require interop
|
||||
- **R3:** Scope creep: this is orthogonal to CVE detection and could become a separate product area
|
||||
- **R4:** Testing requires vendor binary pairs (base + patched) which may be hard to acquire
|
||||
|
||||
### Architectural Notes
|
||||
|
||||
Per the advisory:
|
||||
|
||||
> Prefer **zchunk-like chunk maps** + **trained zstd dictionaries** across package families to maximize reuse.
|
||||
|
||||
> For large PE/ELF apps, support **Zucchini/Courgette** paths for maximal shrink.
|
||||
|
||||
> Keep **MSDelta/LZX-delta** as a Windows-native backend for server components and agents.
|
||||
|
||||
> Treat **base availability as policy**: don't even queue a delta unless the precise base digest is present.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Product decision:** Prioritize delta delivery relative to other roadmap items
|
||||
2. **Architecture review:** Validate proposed schema extensions with Attestor guild
|
||||
3. **Prototype:** Spike zchunk integration to validate effort estimates
|
||||
4. **Air-gap analysis:** Determine how deltas fit into offline deployment model
|
||||
|
||||
## References
|
||||
|
||||
- [Chromium Courgette/Zucchini](https://www.chromium.org/developers/design-documents/software-updates-courgette/)
|
||||
- [Microsoft MSDelta](https://learn.microsoft.com/en-us/windows/win32/devnotes/msdelta)
|
||||
- [deltarpm](https://www.novell.com/documentation/opensuse103/opensuse103_reference/data/sec_rpm_delta.html)
|
||||
- [zchunk](https://github.com/zchunk/zchunk)
|
||||
- [Apple Software Update Process](https://support.apple.com/guide/deployment/software-update-process-dep02c211f3e/web)
|
||||
|
||||
## Execution Log
|
||||
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2026-01-20 | Planning document created from product advisory gap analysis | Planning |
|
||||
| 2026-01-20 | Kickoff: started decision capture for D1-D4 to move planning toward execution. | Planning |
|
||||
|
||||
## Sprint Status
|
||||
|
||||
**STATUS: PLANNING ONLY** - This document captures scope and analysis. No implementation is committed. Convert to active sprint when prioritized.
|
||||
@@ -1,488 +0,0 @@
|
||||
# Sprint 037 – Unified Trust Score Facade (B+C+D Approach)
|
||||
|
||||
## Topic & Scope
|
||||
|
||||
Implement a **facade layer** over existing EWS and Determinization systems to provide:
|
||||
- **B: Unified API** - Single interface combining EWS scores + Determinization entropy
|
||||
- **C: Versioned weight manifests** - Extract EWS weights to `etc/weights/*.json` files
|
||||
- **D: Unknowns fraction (U)** - Expose Determinization entropy as unified metric
|
||||
|
||||
**Key principle:** Preserve existing guardrails, conflict detection, anchor verification, and decay mechanisms. No formula changes - only unification and better exposure.
|
||||
|
||||
**Working directory:** `src/Signals/`
|
||||
**Secondary directories:** `src/Policy/`, `src/Cli/`, `src/Platform/`
|
||||
**Expected evidence:** Unit tests, integration tests, CLI updates, API endpoints, updated documentation
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Concurrency
|
||||
|
||||
- **Upstream (existing, no changes to core logic):**
|
||||
- EWS: `src/Signals/StellaOps.Signals/EvidenceWeightedScore/` - 6-dimension scoring with guardrails
|
||||
- Determinization: `src/Policy/__Libraries/StellaOps.Policy.Determinization/` - entropy, decay, fingerprints
|
||||
- CLI: `src/Cli/StellaOps.Cli/Commands/ScoreGateCommandGroup.cs` - existing `stella gate score`
|
||||
|
||||
- **Concurrency:** Safe to run in parallel with other sprints; no breaking changes
|
||||
|
||||
---
|
||||
|
||||
## Documentation Prerequisites
|
||||
|
||||
- [Policy architecture](../modules/policy/architecture.md) §3.1 Determinization Configuration
|
||||
- [EWS migration](../modules/policy/design/confidence-to-ews-migration.md) - existing scoring
|
||||
- [Score Proofs API](../api/scanner-score-proofs-api.md) - determinism patterns
|
||||
|
||||
---
|
||||
|
||||
## Delivery Tracker
|
||||
|
||||
### TSF-001 - Extract EWS Weights to Manifest Files
|
||||
Status: TODO
|
||||
Dependency: none
|
||||
Owners: Signals Guild
|
||||
|
||||
Task description:
|
||||
Extract existing EWS weight configuration from `EvidenceWeightPolicy` into versioned JSON manifest files. EWS continues to work exactly as before, but weights are now loaded from files.
|
||||
|
||||
**Implementation:**
|
||||
- Create `etc/weights/` directory structure
|
||||
- Create `WeightManifest` record matching existing `EvidenceWeights` structure
|
||||
- Create `IWeightManifestLoader` interface + `FileBasedWeightManifestLoader`
|
||||
- Update `EvidenceWeightPolicy` to load weights from manifest files (with fallback to defaults)
|
||||
- Add SHA-256 content hash to manifests for audit trail
|
||||
- Migrate existing default weights: `etc/weights/v2026-01-22.weights.json`
|
||||
|
||||
**Key constraint:** No change to scoring formula or behavior - just externalize configuration.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `etc/weights/v2026-01-22.weights.json` with current EWS defaults
|
||||
- [ ] `WeightManifest.cs` record with version, effectiveFrom, weights, hash
|
||||
- [ ] `FileBasedWeightManifestLoader.cs` loading from `etc/weights/`
|
||||
- [ ] `EvidenceWeightPolicy` updated to use loader
|
||||
- [ ] Unit tests verifying identical scoring before/after extraction
|
||||
- [ ] Existing determinism tests still pass
|
||||
|
||||
---
|
||||
|
||||
### TSF-002 - Unified Score Facade Service
|
||||
Status: TODO
|
||||
Dependency: TSF-001
|
||||
Owners: Signals Guild
|
||||
|
||||
Task description:
|
||||
Create `IUnifiedScoreService` facade that combines EWS computation with Determinization entropy in a single call. Returns unified result with score, U metric, breakdown, and evidence.
|
||||
|
||||
**Implementation:**
|
||||
- Create `IUnifiedScoreService` interface in `src/Signals/StellaOps.Signals/UnifiedScore/`:
|
||||
```csharp
|
||||
Task<UnifiedScoreResult> ComputeAsync(UnifiedScoreRequest request, CancellationToken ct);
|
||||
```
|
||||
- Create `UnifiedScoreService` that internally:
|
||||
1. Calls `IEvidenceWeightedScoreCalculator.Calculate()` for EWS score
|
||||
2. Calls `IUncertaintyScoreCalculator.CalculateEntropy()` for entropy (U)
|
||||
3. Calls `IConflictDetector.Detect()` for conflict information
|
||||
4. Combines into `UnifiedScoreResult`
|
||||
- `UnifiedScoreResult` includes:
|
||||
- `Score` (0-100 from EWS)
|
||||
- `Bucket` (ActNow/ScheduleNext/Investigate/Watchlist)
|
||||
- `UnknownsFraction` (U from Determinization entropy)
|
||||
- `UnknownsBand` (Complete/Adequate/Sparse/Insufficient)
|
||||
- `Breakdown` (EWS dimension contributions)
|
||||
- `Guardrails` (which caps/floors applied)
|
||||
- `Conflicts` (from ConflictDetector)
|
||||
- `WeightManifestRef` (version + hash)
|
||||
- `EwsDigest` + `DeterminizationFingerprint` (for replay)
|
||||
- Register in DI container
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `IUnifiedScoreService` interface defined
|
||||
- [ ] `UnifiedScoreService` implementation composing EWS + Determinization
|
||||
- [ ] `UnifiedScoreRequest` / `UnifiedScoreResult` DTOs
|
||||
- [ ] DI registration in `ServiceCollectionExtensions`
|
||||
- [ ] Unit tests for facade composition
|
||||
- [ ] Verify identical EWS scores pass through unchanged
|
||||
|
||||
---
|
||||
|
||||
### TSF-003 - Unknowns Band Mapping
|
||||
Status: TODO
|
||||
Dependency: TSF-002
|
||||
Owners: Signals Guild / Policy Guild
|
||||
|
||||
Task description:
|
||||
Map Determinization entropy (0.0-1.0) to user-friendly unknowns bands with actionable thresholds.
|
||||
|
||||
**Implementation:**
|
||||
- Create `UnknownsBandMapper` in `src/Signals/StellaOps.Signals/UnifiedScore/`:
|
||||
```csharp
|
||||
UnknownsBand MapEntropyToBand(double entropy);
|
||||
string GetBandDescription(UnknownsBand band);
|
||||
string GetBandAction(UnknownsBand band);
|
||||
```
|
||||
- Band definitions (matching existing Determinization thresholds):
|
||||
| U Range | Band | Description | Action |
|
||||
|---------|------|-------------|--------|
|
||||
| 0.0-0.2 | Complete | Full signal coverage | Automated decisions |
|
||||
| 0.2-0.4 | Adequate | Sufficient signals | Automated decisions |
|
||||
| 0.4-0.6 | Sparse | Signal gaps exist | Manual review recommended |
|
||||
| 0.6-1.0 | Insufficient | Critical gaps | Block pending more signals |
|
||||
- Integrate with existing `ManualReviewEntropyThreshold` (0.60) and `RefreshEntropyThreshold` (0.40) from Determinization config
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `UnknownsBandMapper.cs` with configurable thresholds
|
||||
- [ ] `UnknownsBand` enum (Complete, Adequate, Sparse, Insufficient)
|
||||
- [ ] Configuration via `appsettings.json` aligned with Determinization
|
||||
- [ ] Unit tests for threshold boundaries
|
||||
- [ ] Integration with `UnifiedScoreResult`
|
||||
|
||||
---
|
||||
|
||||
### TSF-004 - Delta-If-Present Calculations
|
||||
Status: TODO
|
||||
Dependency: TSF-002
|
||||
Owners: Signals Guild
|
||||
|
||||
Task description:
|
||||
When signals are missing, calculate and include "delta if present" showing potential score impact. Uses existing Determinization `SignalGap` information.
|
||||
|
||||
**Implementation:**
|
||||
- Extend `UnifiedScoreResult` with `DeltaIfPresent` list:
|
||||
```csharp
|
||||
IReadOnlyList<SignalDelta> DeltaIfPresent { get; }
|
||||
```
|
||||
- `SignalDelta` record:
|
||||
```csharp
|
||||
record SignalDelta(string Signal, double MinImpact, double MaxImpact, string Description);
|
||||
```
|
||||
- For each missing signal (from Determinization gaps):
|
||||
- Calculate EWS contribution if signal were 0.0 vs 1.0
|
||||
- Include weight from manifest
|
||||
- Add descriptive text (e.g., "If reachability confirmed, score could change by -15 to +8")
|
||||
- Use existing `SignalGap` from Determinization for missing signal list
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `SignalDelta` record defined
|
||||
- [ ] Delta calculation logic in `UnifiedScoreService`
|
||||
- [ ] Integration with `UnifiedScoreResult.DeltaIfPresent`
|
||||
- [ ] Unit tests for delta calculation accuracy
|
||||
- [ ] Test with various missing signal combinations
|
||||
|
||||
---
|
||||
|
||||
### TSF-005 - Platform API Endpoints (Score Evaluate)
|
||||
Status: TODO
|
||||
Dependency: TSF-002, TSF-003, TSF-004
|
||||
Owners: Platform Guild
|
||||
|
||||
Task description:
|
||||
Expose unified score via Platform service REST API endpoints.
|
||||
|
||||
**Implementation:**
|
||||
- Add endpoints to `src/Platform/StellaOps.Platform.WebService/Endpoints/`:
|
||||
- `POST /api/v1/score/evaluate` - Compute unified score (primary scoring endpoint)
|
||||
- `GET /api/v1/score/weights` - List available weight manifests
|
||||
- `GET /api/v1/score/weights/{version}` - Get specific manifest
|
||||
- Request contract for `/score/evaluate`:
|
||||
```json
|
||||
{
|
||||
"sbom_ref": "oci://registry/app@sha256:…",
|
||||
"cvss_vector": "CVSS:3.1/…",
|
||||
"vex_refs": ["oci://…/vex1"],
|
||||
"rekor_receipts": ["BASE64-RECEIPT"],
|
||||
"runtime_witnesses": [{"type":"process","data":"…"}],
|
||||
"options": {"decay_lambda": 0.015, "weight_set_id": "v2026-01-22"}
|
||||
}
|
||||
```
|
||||
- Response includes:
|
||||
- `score_id` - unique identifier for replay lookup
|
||||
- `score_value` - 0-100 score
|
||||
- `unknowns` - list of unknown package refs
|
||||
- `proof_ref` - OCI reference to score proof bundle
|
||||
- Full `UnifiedScoreResult` structure (breakdown, U, band, deltas)
|
||||
- Support `?include_delta=true` query param for delta calculations
|
||||
- Add OpenAPI documentation
|
||||
- Tenant-scoped via Authority
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `POST /api/v1/score/evaluate` endpoint implemented
|
||||
- [ ] `/api/v1/score/weights` endpoints implemented
|
||||
- [ ] Request/response contracts match advisory spec
|
||||
- [ ] OpenAPI spec generated
|
||||
- [ ] Authentication/authorization configured
|
||||
- [ ] Integration tests for each endpoint
|
||||
|
||||
---
|
||||
|
||||
### TSF-006 - CLI `stella gate score` Enhancement
|
||||
Status: TODO
|
||||
Dependency: TSF-005
|
||||
Owners: CLI Guild
|
||||
|
||||
Task description:
|
||||
Enhance existing `stella gate score evaluate` command to show unified metrics (U, bands, deltas).
|
||||
|
||||
**Implementation:**
|
||||
- Update `ScoreGateCommandGroup.cs`:
|
||||
- Add `--show-unknowns` flag to include U metric and band
|
||||
- Add `--show-deltas` flag to include delta-if-present
|
||||
- Add `--weights-version` option to pin specific manifest
|
||||
- Update table output to show U and band when requested
|
||||
- Update JSON output to include full unified result
|
||||
- Add new subcommand `stella gate score weights`:
|
||||
- `list` - Show available weight manifest versions
|
||||
- `show <version>` - Display manifest details
|
||||
- `diff <v1> <v2>` - Compare two manifests
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `--show-unknowns` flag showing U and band
|
||||
- [ ] `--show-deltas` flag showing delta-if-present
|
||||
- [ ] `--weights-version` option for pinning
|
||||
- [ ] `stella gate score weights list|show|diff` commands
|
||||
- [ ] Updated help text and examples
|
||||
- [ ] CLI tests for new options
|
||||
|
||||
---
|
||||
|
||||
### TSF-007 - CLI `stella score` Top-Level Command
|
||||
Status: TODO
|
||||
Dependency: TSF-005, TSF-011
|
||||
Owners: CLI Guild
|
||||
|
||||
Task description:
|
||||
Add new top-level `stella score` command group for direct scoring operations (complementing existing `stella gate score` which is gate-focused).
|
||||
|
||||
**Implementation:**
|
||||
- Create `ScoreCommandGroup.cs` in `src/Cli/StellaOps.Cli/Commands/`:
|
||||
- `stella score compute` - Compute unified score from signals (similar inputs to gate evaluate)
|
||||
- `stella score explain <finding-id>` - Detailed explanation with breakdown
|
||||
- `stella score history <finding-id>` - Score history over time
|
||||
- `stella score compare <finding-id-1> <finding-id-2>` - Compare two findings
|
||||
- `stella score replay <score-id>` - Fetch and display replay proof (depends on TSF-011)
|
||||
- `stella score verify <score-id>` - Verify score by replaying computation locally
|
||||
- Support `--format json|table|markdown` output
|
||||
- Support `--offline` mode using bundled weights
|
||||
- Replay/verify commands output:
|
||||
- Canonical input hashes
|
||||
- Step-by-step algebra decisions
|
||||
- Rekor inclusion proof (if anchored)
|
||||
- Verification status (pass/fail with diff if mismatch)
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `stella score compute` command
|
||||
- [ ] `stella score explain` command
|
||||
- [ ] `stella score history` command (if backend supports)
|
||||
- [ ] `stella score compare` command
|
||||
- [ ] `stella score replay` command
|
||||
- [ ] `stella score verify` command
|
||||
- [ ] Multiple output formats
|
||||
- [ ] Offline mode support
|
||||
- [ ] CLI tests
|
||||
|
||||
---
|
||||
|
||||
### TSF-008 - Console UI Score Display Enhancement
|
||||
Status: TODO
|
||||
Dependency: TSF-005
|
||||
Owners: FE Guild
|
||||
|
||||
Task description:
|
||||
Update Console UI components that display scores to include unknowns fraction and band.
|
||||
|
||||
**Implementation:**
|
||||
- Update finding detail views to show:
|
||||
- Score with bucket (existing)
|
||||
- Unknowns fraction (U) with visual indicator
|
||||
- Unknowns band with color coding
|
||||
- Delta-if-present for missing signals
|
||||
- Weight manifest version used
|
||||
- Add tooltip/popover explaining U and what it means
|
||||
- Update score trend charts to optionally show U over time
|
||||
- Update findings list to show U indicator for high-uncertainty findings
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Finding detail view shows U metric and band
|
||||
- [ ] Color-coded band indicator (green/yellow/orange/red)
|
||||
- [ ] Delta-if-present display for missing signals
|
||||
- [ ] Tooltip explaining unknowns
|
||||
- [ ] Findings list shows high-U indicator
|
||||
- [ ] Score trend chart option for U
|
||||
|
||||
---
|
||||
|
||||
### TSF-009 - Determinism & Replay Tests
|
||||
Status: TODO
|
||||
Dependency: TSF-002
|
||||
Owners: QA / Signals Guild
|
||||
|
||||
Task description:
|
||||
Verify that the unified facade maintains determinism guarantees from underlying EWS and Determinization systems.
|
||||
|
||||
**Implementation:**
|
||||
- Create `UnifiedScoreDeterminismTests.cs`:
|
||||
- Same inputs → same unified result (100+ iterations)
|
||||
- EWS score unchanged through facade
|
||||
- Determinization entropy unchanged through facade
|
||||
- Weight manifest hash stable
|
||||
- Delta calculations deterministic
|
||||
- Create golden test fixtures:
|
||||
- Known inputs with expected unified outputs
|
||||
- Fixtures for various U bands
|
||||
- Fixtures for delta calculations
|
||||
- Verify existing EWS determinism tests still pass
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `UnifiedScoreDeterminismTests.cs` with iteration tests
|
||||
- [ ] Golden fixtures in `__Tests/Fixtures/UnifiedScore/`
|
||||
- [ ] EWS pass-through verification
|
||||
- [ ] Determinization pass-through verification
|
||||
- [ ] CI gate for determinism regression
|
||||
- [ ] Existing EWS/Determinization tests unaffected
|
||||
|
||||
---
|
||||
|
||||
### TSF-010 - Documentation Updates
|
||||
Status: TODO
|
||||
Dependency: TSF-001 through TSF-009
|
||||
Owners: Documentation
|
||||
|
||||
Task description:
|
||||
Update documentation to reflect the unified scoring facade.
|
||||
|
||||
**Implementation:**
|
||||
- Update `docs/technical/scoring-algebra.md` to describe facade approach (not rewrite)
|
||||
- Update `docs/modules/policy/architecture.md` §3.1 to reference weight manifests
|
||||
- Create `docs/modules/signals/unified-score.md` explaining:
|
||||
- What the facade provides
|
||||
- How U metric works
|
||||
- How to interpret bands
|
||||
- CLI command reference
|
||||
- Update `docs/modules/cli/guides/commands/reference.md` with new commands
|
||||
- Add troubleshooting section for common U-related issues
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `docs/technical/scoring-algebra.md` updated for facade approach
|
||||
- [ ] Policy architecture doc updated
|
||||
- [ ] `docs/modules/signals/unified-score.md` guide created
|
||||
- [ ] CLI reference updated
|
||||
- [ ] Troubleshooting guide for U issues
|
||||
|
||||
---
|
||||
|
||||
### TSF-011 - Score Replay & Verification Endpoint
|
||||
Status: TODO
|
||||
Dependency: TSF-005
|
||||
Owners: Platform Guild / Signals Guild
|
||||
|
||||
Task description:
|
||||
Add explicit replay endpoint that returns a signed replay log, enabling external auditors to independently verify any score computation.
|
||||
|
||||
**Implementation:**
|
||||
- Add endpoint to `src/Platform/StellaOps.Platform.WebService/Endpoints/`:
|
||||
- `GET /api/v1/score/{id}/replay` - Fetch signed replay proof for a score
|
||||
- Response contract:
|
||||
```json
|
||||
{
|
||||
"signed_replay_log_dsse": "BASE64",
|
||||
"rekor_inclusion": {"logIndex": 12345, "rootHash": "…"},
|
||||
"canonical_inputs": [
|
||||
{"name": "sbom.json", "sha256": "…"},
|
||||
{"name": "vex.json", "sha256": "…"},
|
||||
{"name": "kev.snapshot", "sha256": "…"}
|
||||
],
|
||||
"transforms": [
|
||||
{"name": "canonicalize_spdx", "version": "1.1"},
|
||||
{"name": "normalize_cvss_v4", "version": "1.0"},
|
||||
{"name": "age_decay", "params": {"lambda": 0.02}}
|
||||
],
|
||||
"algebra_steps": [
|
||||
{"signal": "cvss_v4_base_norm", "w": 0.30, "value": 0.78, "term": 0.234},
|
||||
{"signal": "kev_flag", "w": 0.25, "value": 1, "term": 0.25}
|
||||
],
|
||||
"final_score": 85,
|
||||
"computed_at": "2026-01-22T12:00:00Z"
|
||||
}
|
||||
```
|
||||
- DSSE attestation format:
|
||||
- Payload type: `application/vnd.stella.score+json`
|
||||
- Sign with Authority key
|
||||
- Store replay log as OCI referrer ("StellaBundle" pattern):
|
||||
- Reference: `oci://registry/score-proofs@sha256:…`
|
||||
- Attach to original artifact via OCI referrers API
|
||||
- Create `IReplayLogBuilder` service:
|
||||
- Collects canonical input hashes during scoring
|
||||
- Records transform versions and parameters
|
||||
- Captures step-by-step algebra decisions
|
||||
- Generates DSSE-signed attestation
|
||||
- Create `IReplayVerifier` service:
|
||||
- Takes replay log + original inputs
|
||||
- Re-executes scoring with pinned versions
|
||||
- Returns verification result (pass/fail with diff)
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `GET /api/v1/score/{id}/replay` endpoint implemented
|
||||
- [ ] `IReplayLogBuilder` service capturing full computation trace
|
||||
- [ ] `IReplayVerifier` service for independent verification
|
||||
- [ ] DSSE signing with `application/vnd.stella.score+json` payload type
|
||||
- [ ] OCI referrer storage for replay proofs
|
||||
- [ ] Rekor anchoring integration (optional, configurable)
|
||||
- [ ] OpenAPI spec for replay endpoint
|
||||
- [ ] Integration tests for replay/verify flow
|
||||
- [ ] Golden corpus test: score → replay → verify round-trip
|
||||
|
||||
---
|
||||
|
||||
## Execution Log
|
||||
|
||||
| Date (UTC) | Update | Owner |
|
||||
|------------|--------|-------|
|
||||
| 2026-01-22 | Sprint created from product advisory | Planning |
|
||||
| 2026-01-22 | Revised to B+C+D facade approach after deep analysis of existing systems | Planning |
|
||||
| 2026-01-22 | Added TSF-011 (replay endpoint) per second advisory; renamed `/score/unified` to `/score/evaluate`; added `stella score replay|verify` CLI commands | Planning |
|
||||
|
||||
---
|
||||
|
||||
## Decisions & Risks
|
||||
|
||||
### Decisions Made
|
||||
|
||||
1. **Facade over rewrite** - Preserve existing EWS guardrails, conflict detection, anchor verification
|
||||
2. **Weight manifest format** - JSON with SHA-256 hash, stored in `etc/weights/`
|
||||
3. **U band thresholds** - Aligned with existing Determinization config (0.40/0.60 thresholds)
|
||||
4. **No formula changes** - EWS scoring logic unchanged; only exposed differently
|
||||
5. **Endpoint naming** - Use `/score/evaluate` (per second advisory) instead of `/score/unified` for industry alignment
|
||||
6. **Explicit replay endpoint** - Add `/score/{id}/replay` returning signed DSSE attestation for auditor verification
|
||||
7. **DSSE payload type** - Use `application/vnd.stella.score+json` for score attestations
|
||||
8. **OCI referrer pattern** - Store replay proofs as OCI referrers ("StellaBundle") attached to scored artifacts
|
||||
|
||||
### Risks
|
||||
|
||||
1. **Performance** - Facade adds overhead calling two services
|
||||
- Mitigation: Both services are fast (<100μs); combined still sub-millisecond
|
||||
|
||||
2. **Backward compatibility** - Existing CLI/API consumers expect current format
|
||||
- Mitigation: New fields are additive; existing fields unchanged
|
||||
|
||||
3. **Configuration drift** - Weight manifest vs Determinization config could diverge
|
||||
- Mitigation: Single source of truth via weight manifest; Determinization references it
|
||||
|
||||
### What We're NOT Doing
|
||||
|
||||
- ❌ Replacing EWS formula
|
||||
- ❌ Replacing Determinization entropy calculation
|
||||
- ❌ Changing guardrail logic
|
||||
- ❌ Changing conflict detection
|
||||
- ❌ Breaking existing CLI commands
|
||||
- ❌ Breaking existing API contracts
|
||||
|
||||
---
|
||||
|
||||
## Next Checkpoints
|
||||
|
||||
- [ ] TSF-001 complete - Weights externalized
|
||||
- [ ] TSF-002, TSF-003, TSF-004 complete - Facade functional
|
||||
- [ ] TSF-005 complete - Score evaluate API endpoint
|
||||
- [ ] TSF-011 complete - Replay/verification endpoint + DSSE attestation
|
||||
- [ ] TSF-006, TSF-007 complete - CLI updated (including replay/verify commands)
|
||||
- [ ] TSF-008 complete - UI updated
|
||||
- [ ] TSF-009 complete - Determinism verified
|
||||
- [ ] TSF-010 complete - Documentation finalized
|
||||
@@ -1,115 +0,0 @@
|
||||
# Sprint 038 - eBPF Probe Type Enhancement
|
||||
|
||||
## Topic & Scope
|
||||
- Add probe-type categorization to runtime observation models for eBPF sources
|
||||
- Enable finer-grained filtering and policy evaluation based on probe type
|
||||
- Document offline replay verification algorithm
|
||||
- Working directory: `src/RuntimeInstrumentation/StellaOps.RuntimeInstrumentation.Tetragon/`
|
||||
- Secondary directories: `src/Cli/StellaOps.Cli/Commands/`, `docs/modules/zastava/`
|
||||
- Expected evidence: unit tests, updated CLI, architecture docs
|
||||
|
||||
## Dependencies & Concurrency
|
||||
- Upstream: None (backwards-compatible enhancement)
|
||||
- Can run in parallel with other sprints
|
||||
- Uses existing `runtimeWitness@v1` predicate type (no new type needed)
|
||||
|
||||
## Documentation Prerequisites
|
||||
- Archive manifest: `docs-archived/product/advisories/2026-01-22-ebpf-witness-contract/ARCHIVE_MANIFEST.md`
|
||||
- Tetragon bridge: `src/RuntimeInstrumentation/StellaOps.RuntimeInstrumentation.Tetragon/TetragonWitnessBridge.cs`
|
||||
- Zastava architecture: `docs/modules/zastava/architecture.md`
|
||||
|
||||
## Delivery Tracker
|
||||
|
||||
### EBPF-001 - Add ProbeType field to RuntimeObservation
|
||||
Status: TODO
|
||||
Dependency: none
|
||||
Owners: Developer
|
||||
|
||||
Task description:
|
||||
Extend the `RuntimeObservation` record in `TetragonWitnessBridge.cs` to include an optional `ProbeType` field. This allows distinguishing between kprobe, uprobe, tracepoint, and USDT observations while remaining backwards compatible.
|
||||
|
||||
Add enum and field:
|
||||
```csharp
|
||||
public enum EbpfProbeType
|
||||
{
|
||||
Kprobe,
|
||||
Kretprobe,
|
||||
Uprobe,
|
||||
Uretprobe,
|
||||
Tracepoint,
|
||||
Usdt,
|
||||
Fentry,
|
||||
Fexit
|
||||
}
|
||||
|
||||
// Add to RuntimeObservation record:
|
||||
public EbpfProbeType? ProbeType { get; init; }
|
||||
public string? FunctionName { get; init; }
|
||||
public long? FunctionAddress { get; init; }
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `EbpfProbeType` enum added
|
||||
- [ ] `ProbeType`, `FunctionName`, `FunctionAddress` fields added to `RuntimeObservation`
|
||||
- [ ] Existing code continues to work (fields are optional)
|
||||
- [ ] Unit tests for new fields
|
||||
|
||||
### EBPF-002 - Update Tetragon event parser to populate ProbeType
|
||||
Status: TODO
|
||||
Dependency: EBPF-001
|
||||
Owners: Developer
|
||||
|
||||
Task description:
|
||||
Update the Tetragon event parsing logic to extract and populate the `ProbeType` field from Tetragon events. Tetragon events include probe type information that should be mapped to the new enum.
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Tetragon event parser extracts probe type
|
||||
- [ ] Mapping from Tetragon probe types to `EbpfProbeType` enum
|
||||
- [ ] Integration tests with sample Tetragon events
|
||||
|
||||
### EBPF-003 - Add --probe-type filter to witness list CLI
|
||||
Status: TODO
|
||||
Dependency: EBPF-001
|
||||
Owners: Developer
|
||||
|
||||
Task description:
|
||||
Extend the `witness list` CLI command to support filtering by probe type. Add a `--probe-type` option that accepts: kprobe, uprobe, tracepoint, usdt.
|
||||
|
||||
Location: `src/Cli/StellaOps.Cli/Commands/WitnessCommandGroup.cs`
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `--probe-type` option added to `witness list` command
|
||||
- [ ] Filtering logic implemented in handler
|
||||
- [ ] Help text updated
|
||||
- [ ] CLI test coverage added
|
||||
|
||||
### EBPF-004 - Document offline replay verification algorithm
|
||||
Status: TODO
|
||||
Dependency: none
|
||||
Owners: Documentation author
|
||||
|
||||
Task description:
|
||||
Add a section to `docs/modules/zastava/architecture.md` documenting the deterministic replay verification algorithm for runtime witnesses. This should specify:
|
||||
- Input canonicalization steps (RFC 8785 JCS)
|
||||
- Observation ordering rules for deterministic hashing
|
||||
- Signature verification sequence
|
||||
- Offline bundle structure requirements for witness verification
|
||||
|
||||
Completion criteria:
|
||||
- [ ] New section "Offline Witness Verification" added to Zastava architecture
|
||||
- [ ] Canonicalization steps documented
|
||||
- [ ] Observation ordering rules specified
|
||||
- [ ] Offline bundle requirements defined
|
||||
|
||||
## Execution Log
|
||||
| Date (UTC) | Update | Owner |
|
||||
| --- | --- | --- |
|
||||
| 2026-01-22 | Sprint created from eBPF witness advisory. Simplified approach: extend existing model rather than new predicate type. | Planning |
|
||||
|
||||
## Decisions & Risks
|
||||
- **Decision**: Extend existing `RuntimeObservation` with optional `ProbeType` field rather than creating new `ebpfWitness@v1` predicate type. Rationale: simpler, backwards compatible, `SourceType=Tetragon` already identifies eBPF source.
|
||||
- **Risk**: None significant - all new fields are optional, existing witnesses remain valid.
|
||||
|
||||
## Next Checkpoints
|
||||
- EBPF-001 and EBPF-004 can start immediately (no dependencies)
|
||||
- EBPF-002 and EBPF-003 depend on EBPF-001
|
||||
@@ -1,886 +0,0 @@
|
||||
# Sprint 039 – Runtime→Static Linkage Verification
|
||||
|
||||
## Topic & Scope
|
||||
|
||||
Implement the **proof layer** that connects runtime eBPF observations to static analysis claims, enabling users to:
|
||||
- Declare expected call-paths via a **function_map predicate** derived from SBOM
|
||||
- Verify that runtime observations match declared expectations
|
||||
- Complete the offline trust chain with **checkpoint signature verification**
|
||||
- Query historical observations for compliance reporting
|
||||
|
||||
This sprint delivers the missing "contract" and "proof" layers identified in the eBPF witness advisory gap analysis.
|
||||
|
||||
**Working directory:** `src/Scanner/__Libraries/StellaOps.Scanner.Reachability/`
|
||||
**Secondary directories:**
|
||||
- `src/Attestor/` (checkpoint signature fix)
|
||||
- `src/Cli/StellaOps.Cli/Commands/` (CLI commands)
|
||||
- `src/RuntimeInstrumentation/` (observation persistence)
|
||||
- `src/Platform/` (API endpoints)
|
||||
- `src/Web/` (UI components)
|
||||
|
||||
**Expected evidence:** Unit tests, integration tests, CLI commands, API endpoints, UI components, updated documentation
|
||||
|
||||
---
|
||||
|
||||
## User Stories
|
||||
|
||||
### US-1: Security Engineer declares expected call-paths
|
||||
> "As a security engineer, I want to declare which functions my service is expected to call, so I can detect unexpected runtime behavior."
|
||||
|
||||
### US-2: DevOps verifies runtime matches expectations
|
||||
> "As a DevOps engineer, I want to verify that runtime observations match our declared function map, so I can prove our services behave as expected."
|
||||
|
||||
### US-3: Auditor verifies offline
|
||||
> "As an auditor, I want to verify runtime-to-static linkage in an air-gapped environment with full cryptographic proof."
|
||||
|
||||
### US-4: SOC analyst queries observation history
|
||||
> "As a SOC analyst, I want to query historical observations for a specific function to investigate anomalies."
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Concurrency
|
||||
|
||||
- **Upstream (required before starting):**
|
||||
- Sprint 038 EBPF-001: `ProbeType` field in `RuntimeObservation` (for richer verification)
|
||||
- Existing `PathWitness` model in `src/Scanner/__Libraries/StellaOps.Scanner.Reachability/Witnesses/`
|
||||
- Existing `ClaimIdGenerator` for claim linking
|
||||
- Existing `TetragonWitnessBridge` for observation buffering
|
||||
|
||||
- **Upstream (no changes needed):**
|
||||
- `HttpRekorClient` - will be patched for checkpoint signatures
|
||||
- `BundleManifest` v2.0.0 - function_map will be added as artifact type
|
||||
|
||||
- **Concurrency:**
|
||||
- Safe to run in parallel with Sprint 037 (trust score)
|
||||
- Depends on Sprint 038 EBPF-001 completing first
|
||||
|
||||
---
|
||||
|
||||
## Documentation Prerequisites
|
||||
|
||||
- [Witness contract v1](../contracts/witness-v1.md) - Node hash and path hash recipes
|
||||
- [Zastava architecture](../modules/zastava/architecture.md) - Runtime signal flow
|
||||
- [Attestor offline verification](../modules/attestor/guides/offline-verification.md) - Bundle verification
|
||||
- Sprint 038 EBPF-004 output (offline replay algorithm docs)
|
||||
|
||||
---
|
||||
|
||||
## Delivery Tracker
|
||||
|
||||
### RLV-001 - Define function_map Predicate Schema
|
||||
Status: TODO
|
||||
Dependency: none
|
||||
Owners: Scanner Guild / Attestor Guild
|
||||
|
||||
Task description:
|
||||
Define the `function_map` predicate schema that declares expected call-paths for a service. This is the "contract" that runtime observations will be verified against.
|
||||
|
||||
**Schema design:**
|
||||
```json
|
||||
{
|
||||
"_type": "https://stella.ops/predicates/function-map/v1",
|
||||
"subject": {
|
||||
"purl": "pkg:oci/myservice@sha256:abc123...",
|
||||
"digest": { "sha256": "abc123..." }
|
||||
},
|
||||
"predicate": {
|
||||
"schemaVersion": "1.0.0",
|
||||
"service": "myservice",
|
||||
"buildId": "abc123def456...",
|
||||
"generatedFrom": {
|
||||
"sbomRef": "sha256:...",
|
||||
"staticAnalysisRef": "sha256:..."
|
||||
},
|
||||
"expectedPaths": [
|
||||
{
|
||||
"pathId": "path-001",
|
||||
"description": "TLS handshake via OpenSSL",
|
||||
"entrypoint": {
|
||||
"symbol": "myservice::handle_request",
|
||||
"nodeHash": "sha256:..."
|
||||
},
|
||||
"expectedCalls": [
|
||||
{
|
||||
"symbol": "SSL_connect",
|
||||
"purl": "pkg:deb/debian/openssl@3.0.11",
|
||||
"nodeHash": "sha256:...",
|
||||
"probeTypes": ["uprobe", "uretprobe"],
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"symbol": "SSL_read",
|
||||
"purl": "pkg:deb/debian/openssl@3.0.11",
|
||||
"nodeHash": "sha256:...",
|
||||
"probeTypes": ["uprobe"],
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"pathHash": "sha256:..."
|
||||
}
|
||||
],
|
||||
"coverage": {
|
||||
"minObservationRate": 0.95,
|
||||
"windowSeconds": 1800
|
||||
},
|
||||
"generatedAt": "2026-01-22T12:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key design decisions:**
|
||||
- Uses existing `nodeHash` recipe from witness-v1 contract for consistency
|
||||
- `expectedCalls` array defines the "hot functions" from the advisory
|
||||
- `probeTypes` specifies which probe types are acceptable for each function
|
||||
- `coverage.minObservationRate` maps to advisory's "≥ 95% of calls witnessed"
|
||||
- `optional` flag allows for conditional paths (feature flags, error handlers)
|
||||
|
||||
**Implementation:**
|
||||
- Create `FunctionMapPredicate.cs` record in `src/Scanner/__Libraries/StellaOps.Scanner.Reachability/FunctionMap/`
|
||||
- Create `ExpectedPath.cs` and `ExpectedCall.cs` supporting records
|
||||
- Add JSON schema to `docs/schemas/function-map-v1.schema.json`
|
||||
- Register predicate type with Attestor predicate router
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `FunctionMapPredicate.cs` with full schema
|
||||
- [ ] JSON schema in `docs/schemas/`
|
||||
- [ ] Predicate type registered: `https://stella.ops/predicates/function-map/v1`
|
||||
- [ ] Unit tests for serialization/deserialization
|
||||
- [ ] Schema validation tests
|
||||
|
||||
---
|
||||
|
||||
### RLV-002 - Implement FunctionMapGenerator
|
||||
Status: TODO
|
||||
Dependency: RLV-001
|
||||
Owners: Scanner Guild
|
||||
|
||||
Task description:
|
||||
Implement a generator that produces a `function_map` predicate from SBOM + static analysis results. This enables users to declare expected paths without manually authoring JSON.
|
||||
|
||||
**Implementation:**
|
||||
- Create `IFunctionMapGenerator` interface:
|
||||
```csharp
|
||||
public interface IFunctionMapGenerator
|
||||
{
|
||||
Task<FunctionMapPredicate> GenerateAsync(
|
||||
FunctionMapGenerationRequest request,
|
||||
CancellationToken ct);
|
||||
}
|
||||
```
|
||||
- Create `FunctionMapGenerationRequest`:
|
||||
```csharp
|
||||
public record FunctionMapGenerationRequest
|
||||
{
|
||||
public required string SbomPath { get; init; }
|
||||
public required string ServiceName { get; init; }
|
||||
public string? StaticAnalysisPath { get; init; }
|
||||
public IReadOnlyList<string>? HotFunctionPatterns { get; init; }
|
||||
public double MinObservationRate { get; init; } = 0.95;
|
||||
public int WindowSeconds { get; init; } = 1800;
|
||||
}
|
||||
```
|
||||
- Create `FunctionMapGenerator` implementation:
|
||||
1. Parse SBOM to extract components with PURLs
|
||||
2. If static analysis provided, extract call paths
|
||||
3. If hot function patterns provided, filter to matching symbols
|
||||
4. Generate node hashes using existing `NodeHashRecipe`
|
||||
5. Compute path hashes using existing `PathHashRecipe`
|
||||
6. Return populated `FunctionMapPredicate`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Reachability/FunctionMap/`
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `IFunctionMapGenerator` interface
|
||||
- [ ] `FunctionMapGenerator` implementation
|
||||
- [ ] Integration with existing SBOM parser
|
||||
- [ ] Support for hot function pattern matching (glob/regex)
|
||||
- [ ] Unit tests with sample SBOM
|
||||
- [ ] Integration test: SBOM → function_map → valid predicate
|
||||
|
||||
---
|
||||
|
||||
### RLV-003 - Implement IClaimVerifier
|
||||
Status: TODO
|
||||
Dependency: RLV-001, Sprint 038 EBPF-001
|
||||
Owners: Scanner Guild
|
||||
|
||||
Task description:
|
||||
Implement the claim verification logic that proves runtime observations match a declared function_map. This is the core "proof" step.
|
||||
|
||||
**Implementation:**
|
||||
- Create `IClaimVerifier` interface in `src/Scanner/__Libraries/StellaOps.Scanner.Reachability/Verification/`:
|
||||
```csharp
|
||||
public interface IClaimVerifier
|
||||
{
|
||||
Task<ClaimVerificationResult> VerifyAsync(
|
||||
FunctionMapPredicate functionMap,
|
||||
IReadOnlyList<RuntimeObservation> observations,
|
||||
ClaimVerificationOptions options,
|
||||
CancellationToken ct);
|
||||
}
|
||||
```
|
||||
- Create `ClaimVerificationResult`:
|
||||
```csharp
|
||||
public record ClaimVerificationResult
|
||||
{
|
||||
public required bool Verified { get; init; }
|
||||
public required double ObservationRate { get; init; }
|
||||
public required IReadOnlyList<PathVerificationResult> Paths { get; init; }
|
||||
public required IReadOnlyList<string> UnexpectedSymbols { get; init; }
|
||||
public required IReadOnlyList<string> MissingExpectedSymbols { get; init; }
|
||||
public required ClaimVerificationEvidence Evidence { get; init; }
|
||||
}
|
||||
|
||||
public record PathVerificationResult
|
||||
{
|
||||
public required string PathId { get; init; }
|
||||
public required bool Observed { get; init; }
|
||||
public required int ObservationCount { get; init; }
|
||||
public required IReadOnlyList<string> MatchedNodeHashes { get; init; }
|
||||
public required IReadOnlyList<string> MissingNodeHashes { get; init; }
|
||||
}
|
||||
|
||||
public record ClaimVerificationEvidence
|
||||
{
|
||||
public required string FunctionMapDigest { get; init; }
|
||||
public required string ObservationsDigest { get; init; }
|
||||
public required DateTimeOffset VerifiedAt { get; init; }
|
||||
public required string VerifierVersion { get; init; }
|
||||
}
|
||||
```
|
||||
- Create `ClaimVerifier` implementation:
|
||||
1. Group observations by node hash
|
||||
2. For each expected path in function_map:
|
||||
- Check if all required node hashes were observed
|
||||
- Check if probe types match expectations
|
||||
- Calculate observation rate
|
||||
3. Detect unexpected symbols (observed but not in function_map)
|
||||
4. Calculate overall observation rate
|
||||
5. Compare against `coverage.minObservationRate`
|
||||
6. Build evidence record for audit trail
|
||||
|
||||
**Verification algorithm:**
|
||||
```
|
||||
For each path in functionMap.expectedPaths:
|
||||
matched = 0
|
||||
for each call in path.expectedCalls:
|
||||
if observations.any(o => o.nodeHash == call.nodeHash && call.probeTypes.contains(o.probeType)):
|
||||
matched++
|
||||
path.observationRate = matched / path.expectedCalls.count
|
||||
|
||||
overallRate = observedPaths / totalPaths
|
||||
verified = overallRate >= functionMap.coverage.minObservationRate
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `IClaimVerifier` interface defined
|
||||
- [ ] `ClaimVerifier` implementation with verification algorithm
|
||||
- [ ] `ClaimVerificationResult` with detailed breakdown
|
||||
- [ ] Evidence record for audit trail
|
||||
- [ ] Detection of unexpected symbols
|
||||
- [ ] Unit tests for various scenarios (full match, partial, no match)
|
||||
- [ ] Integration test with real observations
|
||||
|
||||
---
|
||||
|
||||
### RLV-004 - Fix Checkpoint Signature Verification
|
||||
Status: TODO
|
||||
Dependency: none
|
||||
Owners: Attestor Guild
|
||||
|
||||
Task description:
|
||||
Complete the Rekor checkpoint signature verification that currently returns `false` unconditionally. This is required for full offline trust chain.
|
||||
|
||||
**Current state (HttpRekorClient.cs:282-289):**
|
||||
```csharp
|
||||
_logger.LogDebug(
|
||||
"Checkpoint signature verification is unavailable for UUID {Uuid}; treating checkpoint as unverified",
|
||||
rekorUuid);
|
||||
// ...
|
||||
return RekorInclusionVerificationResult.Success(
|
||||
logIndex.Value,
|
||||
computedRootHex,
|
||||
proof.Checkpoint.RootHash,
|
||||
checkpointSignatureValid: false); // Always false
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Update `HttpRekorClient.VerifyInclusionAsync()` to:
|
||||
1. Extract checkpoint note from response
|
||||
2. Parse note format: body + signature lines
|
||||
3. Verify signature using `CheckpointSignatureVerifier` (already exists)
|
||||
4. Return actual verification result
|
||||
- Add `RekorPublicKey` configuration option for pinned verification
|
||||
- Support both online (fetch from Rekor) and offline (pinned key) modes
|
||||
|
||||
**Location:** `src/Attestor/__Libraries/StellaOps.Attestor.Infrastructure/Rekor/HttpRekorClient.cs`
|
||||
|
||||
**Testing:**
|
||||
- Verify against real Rekor checkpoint
|
||||
- Verify with pinned public key (offline mode)
|
||||
- Verify rejection of tampered checkpoint
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Checkpoint signature verification implemented
|
||||
- [ ] `checkpointSignatureValid` returns actual result
|
||||
- [ ] Support for pinned public key (air-gap mode)
|
||||
- [ ] Unit tests with test vectors
|
||||
- [ ] Integration test against Rekor staging
|
||||
|
||||
---
|
||||
|
||||
### RLV-005 - Implement Runtime Observation Store
|
||||
Status: TODO
|
||||
Dependency: Sprint 038 EBPF-001
|
||||
Owners: Signals Guild
|
||||
|
||||
Task description:
|
||||
Implement persistent storage for runtime observations to support historical queries and compliance reporting.
|
||||
|
||||
**Implementation:**
|
||||
- Create `IRuntimeObservationStore` interface (if not exists) in `src/RuntimeInstrumentation/`:
|
||||
```csharp
|
||||
public interface IRuntimeObservationStore
|
||||
{
|
||||
Task StoreAsync(RuntimeObservation observation, CancellationToken ct);
|
||||
Task StoreBatchAsync(IReadOnlyList<RuntimeObservation> observations, CancellationToken ct);
|
||||
|
||||
Task<IReadOnlyList<RuntimeObservation>> QueryBySymbolAsync(
|
||||
string nodeHash,
|
||||
DateTimeOffset from,
|
||||
DateTimeOffset to,
|
||||
CancellationToken ct);
|
||||
|
||||
Task<IReadOnlyList<RuntimeObservation>> QueryByContainerAsync(
|
||||
string containerId,
|
||||
DateTimeOffset from,
|
||||
DateTimeOffset to,
|
||||
CancellationToken ct);
|
||||
|
||||
Task<ObservationSummary> GetSummaryAsync(
|
||||
string nodeHash,
|
||||
DateTimeOffset from,
|
||||
DateTimeOffset to,
|
||||
CancellationToken ct);
|
||||
|
||||
Task PruneOlderThanAsync(TimeSpan retention, CancellationToken ct);
|
||||
}
|
||||
```
|
||||
- Create `PostgresRuntimeObservationStore` implementation:
|
||||
- Table: `runtime_observations` with indexes on `node_hash`, `container_id`, `observed_at`
|
||||
- Batch insert with conflict handling (dedup by observation_id)
|
||||
- Efficient time-range queries using BRIN index on `observed_at`
|
||||
- Configurable retention policy (default: 7 days)
|
||||
- Create migration: `src/RuntimeInstrumentation/.../Migrations/001_runtime_observations.sql`
|
||||
- Wire into `TetragonWitnessBridge` to persist observations as they arrive
|
||||
|
||||
**Schema:**
|
||||
```sql
|
||||
CREATE TABLE runtime_observations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
observation_id TEXT NOT NULL UNIQUE,
|
||||
node_hash TEXT NOT NULL,
|
||||
symbol_name TEXT,
|
||||
container_id TEXT NOT NULL,
|
||||
pod_name TEXT,
|
||||
namespace TEXT,
|
||||
probe_type TEXT,
|
||||
function_address BIGINT,
|
||||
stack_sample_hash TEXT,
|
||||
observation_count INTEGER DEFAULT 1,
|
||||
duration_us BIGINT,
|
||||
observed_at TIMESTAMPTZ NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_observations_node_hash ON runtime_observations (node_hash);
|
||||
CREATE INDEX idx_observations_container ON runtime_observations (container_id);
|
||||
CREATE INDEX idx_observations_time USING BRIN ON runtime_observations (observed_at);
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `IRuntimeObservationStore` interface
|
||||
- [ ] `PostgresRuntimeObservationStore` implementation
|
||||
- [ ] Database migration
|
||||
- [ ] Integration with `TetragonWitnessBridge`
|
||||
- [ ] Configurable retention policy
|
||||
- [ ] Unit tests for store operations
|
||||
- [ ] Integration tests with real Postgres
|
||||
|
||||
---
|
||||
|
||||
### RLV-006 - CLI: `stella function-map generate`
|
||||
Status: TODO
|
||||
Dependency: RLV-002
|
||||
Owners: CLI Guild
|
||||
|
||||
Task description:
|
||||
Add CLI command to generate a function_map predicate from SBOM.
|
||||
|
||||
**Implementation:**
|
||||
- Create `FunctionMapCommandGroup.cs` in `src/Cli/StellaOps.Cli/Commands/`
|
||||
- Add command: `stella function-map generate`
|
||||
|
||||
**Command spec:**
|
||||
```
|
||||
stella function-map generate [options]
|
||||
|
||||
Options:
|
||||
--sbom <path> Path to SBOM file (CycloneDX/SPDX) [required]
|
||||
--service <name> Service name for the function map [required]
|
||||
--static-analysis <path> Path to static analysis results (optional)
|
||||
--hot-functions <pattern> Glob pattern for hot functions (can repeat)
|
||||
Example: --hot-functions "SSL_*" --hot-functions "crypto_*"
|
||||
--min-rate <0.0-1.0> Minimum observation rate (default: 0.95)
|
||||
--window <seconds> Observation window in seconds (default: 1800)
|
||||
--output <path> Output path (default: stdout)
|
||||
--format <json|yaml> Output format (default: json)
|
||||
--sign Sign the predicate with configured key
|
||||
--attest Create DSSE envelope and push to Rekor
|
||||
|
||||
Examples:
|
||||
# Generate from SBOM with default hot functions
|
||||
stella function-map generate --sbom sbom.json --service myservice
|
||||
|
||||
# Generate with specific hot functions
|
||||
stella function-map generate --sbom sbom.json --service myservice \
|
||||
--hot-functions "SSL_*" --hot-functions "EVP_*" --hot-functions "connect"
|
||||
|
||||
# Generate, sign, and attest
|
||||
stella function-map generate --sbom sbom.json --service myservice \
|
||||
--sign --attest --output function-map.json
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `stella function-map generate` command implemented
|
||||
- [ ] All options working
|
||||
- [ ] DSSE signing integration (--sign)
|
||||
- [ ] Rekor attestation integration (--attest)
|
||||
- [ ] JSON and YAML output formats
|
||||
- [ ] Help text and examples
|
||||
- [ ] CLI tests
|
||||
|
||||
---
|
||||
|
||||
### RLV-007 - CLI: `stella function-map verify`
|
||||
Status: TODO
|
||||
Dependency: RLV-003, RLV-005
|
||||
Owners: CLI Guild
|
||||
|
||||
Task description:
|
||||
Add CLI command to verify runtime observations against a function_map.
|
||||
|
||||
**Command spec:**
|
||||
```
|
||||
stella function-map verify [options]
|
||||
|
||||
Options:
|
||||
--function-map <path|ref> Path or OCI reference to function_map predicate [required]
|
||||
--container <id> Container ID to verify (optional, default: all)
|
||||
--from <timestamp> Start of observation window (default: 30 minutes ago)
|
||||
--to <timestamp> End of observation window (default: now)
|
||||
--output <path> Output verification report (default: stdout)
|
||||
--format <json|table|md> Output format (default: table)
|
||||
--strict Fail on any unexpected symbols
|
||||
--sign Sign the verification report
|
||||
--offline Offline mode (use bundled observations)
|
||||
--observations <path> Path to observations file (for offline mode)
|
||||
|
||||
Output:
|
||||
Verified: true/false
|
||||
Observation Rate: 97.2% (target: 95.0%)
|
||||
|
||||
Path Coverage:
|
||||
┌──────────────┬──────────┬───────────┬─────────────┐
|
||||
│ Path ID │ Status │ Rate │ Missing │
|
||||
├──────────────┼──────────┼───────────┼─────────────┤
|
||||
│ path-001 │ ✓ │ 100% │ - │
|
||||
│ path-002 │ ✓ │ 95.5% │ - │
|
||||
│ path-003 │ ✗ │ 80.0% │ SSL_write │
|
||||
└──────────────┴──────────┴───────────┴─────────────┘
|
||||
|
||||
Unexpected Symbols: none
|
||||
|
||||
Evidence:
|
||||
Function Map Digest: sha256:abc123...
|
||||
Observations Digest: sha256:def456...
|
||||
Verified At: 2026-01-22T12:00:00Z
|
||||
|
||||
Examples:
|
||||
# Verify against stored observations
|
||||
stella function-map verify --function-map function-map.json
|
||||
|
||||
# Verify specific container
|
||||
stella function-map verify --function-map function-map.json \
|
||||
--container abc123 --from "2026-01-22T11:30:00Z"
|
||||
|
||||
# Offline verification with bundled observations
|
||||
stella function-map verify --function-map function-map.json \
|
||||
--offline --observations observations.ndjson
|
||||
|
||||
# Sign verification report for audit
|
||||
stella function-map verify --function-map function-map.json \
|
||||
--sign --output verification-report.json
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `stella function-map verify` command implemented
|
||||
- [ ] Query observations from store
|
||||
- [ ] Offline mode with file input
|
||||
- [ ] Table, JSON, and Markdown output formats
|
||||
- [ ] Signed verification report option
|
||||
- [ ] CLI tests
|
||||
|
||||
---
|
||||
|
||||
### RLV-008 - CLI: `stella observations query`
|
||||
Status: TODO
|
||||
Dependency: RLV-005
|
||||
Owners: CLI Guild
|
||||
|
||||
Task description:
|
||||
Add CLI command to query historical runtime observations.
|
||||
|
||||
**Command spec:**
|
||||
```
|
||||
stella observations query [options]
|
||||
|
||||
Options:
|
||||
--symbol <name> Filter by symbol name (glob pattern)
|
||||
--node-hash <hash> Filter by exact node hash
|
||||
--container <id> Filter by container ID
|
||||
--pod <name> Filter by pod name
|
||||
--namespace <ns> Filter by Kubernetes namespace
|
||||
--probe-type <type> Filter by probe type (kprobe|uprobe|tracepoint|usdt)
|
||||
--from <timestamp> Start time (default: 1 hour ago)
|
||||
--to <timestamp> End time (default: now)
|
||||
--limit <n> Maximum results (default: 100)
|
||||
--format <json|table|csv> Output format (default: table)
|
||||
--summary Show summary statistics instead of individual observations
|
||||
|
||||
Examples:
|
||||
# Query all SSL_connect observations in last hour
|
||||
stella observations query --symbol "SSL_connect"
|
||||
|
||||
# Query by container
|
||||
stella observations query --container abc123 --from "2026-01-22T11:00:00Z"
|
||||
|
||||
# Get summary statistics
|
||||
stella observations query --symbol "SSL_*" --summary
|
||||
|
||||
# Export to CSV for analysis
|
||||
stella observations query --namespace production --format csv > observations.csv
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `stella observations query` command implemented
|
||||
- [ ] All filter options working
|
||||
- [ ] Summary statistics mode
|
||||
- [ ] CSV export for external analysis
|
||||
- [ ] CLI tests
|
||||
|
||||
---
|
||||
|
||||
### RLV-009 - Platform API: Function Map Endpoints
|
||||
Status: TODO
|
||||
Dependency: RLV-002, RLV-003
|
||||
Owners: Platform Guild
|
||||
|
||||
Task description:
|
||||
Expose function_map operations via Platform service REST API.
|
||||
|
||||
**Endpoints:**
|
||||
```
|
||||
POST /api/v1/function-maps Create/store function map
|
||||
GET /api/v1/function-maps List function maps
|
||||
GET /api/v1/function-maps/{id} Get function map by ID
|
||||
DELETE /api/v1/function-maps/{id} Delete function map
|
||||
|
||||
POST /api/v1/function-maps/{id}/verify Verify observations against map
|
||||
GET /api/v1/function-maps/{id}/coverage Get current coverage statistics
|
||||
```
|
||||
|
||||
**Request/Response contracts:**
|
||||
|
||||
`POST /api/v1/function-maps`:
|
||||
```json
|
||||
{
|
||||
"sbomRef": "oci://registry/app@sha256:...",
|
||||
"serviceName": "myservice",
|
||||
"hotFunctions": ["SSL_*", "EVP_*"],
|
||||
"options": {
|
||||
"minObservationRate": 0.95,
|
||||
"windowSeconds": 1800
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`POST /api/v1/function-maps/{id}/verify`:
|
||||
```json
|
||||
{
|
||||
"containerId": "abc123",
|
||||
"from": "2026-01-22T11:00:00Z",
|
||||
"to": "2026-01-22T12:00:00Z",
|
||||
"strict": false
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"verified": true,
|
||||
"observationRate": 0.972,
|
||||
"targetRate": 0.95,
|
||||
"paths": [...],
|
||||
"unexpectedSymbols": [],
|
||||
"evidence": {
|
||||
"functionMapDigest": "sha256:...",
|
||||
"observationsDigest": "sha256:...",
|
||||
"verifiedAt": "2026-01-22T12:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] All endpoints implemented
|
||||
- [ ] OpenAPI spec generated
|
||||
- [ ] Tenant-scoped authorization
|
||||
- [ ] Integration tests
|
||||
- [ ] Rate limiting configured
|
||||
|
||||
---
|
||||
|
||||
### RLV-010 - UI: Function Map Management
|
||||
Status: TODO
|
||||
Dependency: RLV-009
|
||||
Owners: FE Guild
|
||||
|
||||
Task description:
|
||||
Add UI components for managing function maps and viewing verification results.
|
||||
|
||||
**Components:**
|
||||
|
||||
1. **Function Map List View** (`/settings/function-maps`)
|
||||
- Table showing all function maps for tenant
|
||||
- Columns: Service, Created, Last Verified, Coverage Status
|
||||
- Actions: View, Verify Now, Delete
|
||||
|
||||
2. **Function Map Detail View** (`/settings/function-maps/{id}`)
|
||||
- Service info and generation metadata
|
||||
- Expected paths table with symbols
|
||||
- Coverage thresholds configuration
|
||||
- Recent verification history
|
||||
|
||||
3. **Function Map Generator Wizard** (`/settings/function-maps/new`)
|
||||
- Step 1: Select SBOM source (file upload or OCI reference)
|
||||
- Step 2: Configure hot function patterns (with suggestions)
|
||||
- Step 3: Set coverage thresholds
|
||||
- Step 4: Review and create
|
||||
|
||||
4. **Verification Results Panel** (embedded in service detail)
|
||||
- Current verification status (verified/not verified)
|
||||
- Observation rate gauge with threshold indicator
|
||||
- Path coverage breakdown (expandable)
|
||||
- Unexpected symbols warning (if any)
|
||||
- Link to full verification report
|
||||
|
||||
5. **Observation Timeline** (`/services/{id}/observations`)
|
||||
- Time-series chart of observation counts
|
||||
- Filter by symbol/probe type
|
||||
- Drill-down to individual observations
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Function map list view
|
||||
- [ ] Function map detail view
|
||||
- [ ] Generator wizard
|
||||
- [ ] Verification results panel
|
||||
- [ ] Observation timeline chart
|
||||
- [ ] Responsive design
|
||||
- [ ] Loading states and error handling
|
||||
- [ ] E2E tests
|
||||
|
||||
---
|
||||
|
||||
### RLV-011 - Bundle Integration: function_map Artifact Type
|
||||
Status: TODO
|
||||
Dependency: RLV-001
|
||||
Owners: AirGap Guild
|
||||
|
||||
Task description:
|
||||
Add `function_map` as a supported artifact type in StellaBundle for offline verification.
|
||||
|
||||
**Implementation:**
|
||||
- Update `BundleArtifactType` enum to include `FunctionMap`
|
||||
- Update `BundleBuilder` to package function_map predicates
|
||||
- Update `BundleValidator` to validate function_map artifacts
|
||||
- Update `BundleVerifyCommand` to verify function_map signatures
|
||||
|
||||
**Bundle structure addition:**
|
||||
```
|
||||
bundle/
|
||||
├── manifest.json
|
||||
├── function-maps/
|
||||
│ └── myservice-function-map.json
|
||||
├── observations/
|
||||
│ └── observations-2026-01-22.ndjson
|
||||
└── verification/
|
||||
└── verification-report.dsse.json
|
||||
```
|
||||
|
||||
Completion criteria:
|
||||
- [ ] `FunctionMap` artifact type added
|
||||
- [ ] Bundle export includes function maps
|
||||
- [ ] Bundle verify validates function map signatures
|
||||
- [ ] Offline verification includes function map checking
|
||||
- [ ] Documentation updated
|
||||
|
||||
---
|
||||
|
||||
### RLV-012 - Documentation: Runtime Linkage Verification Guide
|
||||
Status: TODO
|
||||
Dependency: RLV-001 through RLV-011
|
||||
Owners: Documentation
|
||||
|
||||
Task description:
|
||||
Create comprehensive documentation for the runtime→static linkage verification feature.
|
||||
|
||||
**Documents to create/update:**
|
||||
|
||||
1. **New: `docs/modules/scanner/guides/runtime-linkage.md`**
|
||||
- What is runtime→static linkage verification?
|
||||
- When to use function maps
|
||||
- Step-by-step guide: generate → deploy probes → verify
|
||||
- Troubleshooting common issues
|
||||
|
||||
2. **New: `docs/contracts/function-map-v1.md`**
|
||||
- Predicate schema specification
|
||||
- Node hash and path hash recipes (reference witness-v1)
|
||||
- Coverage calculation algorithm
|
||||
- Verification algorithm
|
||||
|
||||
3. **Update: `docs/modules/cli/guides/commands/reference.md`**
|
||||
- Add `stella function-map` command group
|
||||
- Add `stella observations` command group
|
||||
|
||||
4. **Update: `docs/modules/airgap/guides/offline-bundle-format.md`**
|
||||
- Add function_map artifact type documentation
|
||||
|
||||
5. **New: `docs/runbooks/runtime-linkage-ops.md`**
|
||||
- Operational runbook for production deployment
|
||||
- Probe selection guidance
|
||||
- Performance tuning
|
||||
- Alert configuration
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Runtime linkage guide created
|
||||
- [ ] function_map contract documented
|
||||
- [ ] CLI reference updated
|
||||
- [ ] Bundle format docs updated
|
||||
- [ ] Operational runbook created
|
||||
|
||||
---
|
||||
|
||||
### RLV-013 - Acceptance Tests: 90-Day Pilot Criteria
|
||||
Status: TODO
|
||||
Dependency: All above tasks
|
||||
Owners: QA Guild
|
||||
|
||||
Task description:
|
||||
Implement acceptance tests matching the advisory's success criteria:
|
||||
|
||||
**Advisory acceptance criteria:**
|
||||
1. **Coverage:** ≥ 95% of calls to the 6 hot funcs are witnessed over a steady-state 30-min window
|
||||
2. **Integrity:** 100% DSSE sig verify + valid Rekor inclusion + valid TST
|
||||
3. **Replayability:** Offline verifier reproduces the same mapping on 3 separate air-gapped runs
|
||||
4. **Perf:** < 2% CPU overhead, < 50 MB RSS for collector under target load
|
||||
5. **Privacy:** No raw args; only hashes and minimal context
|
||||
|
||||
**Test implementation:**
|
||||
|
||||
1. **Coverage test:**
|
||||
- Generate function_map with 6 hot functions
|
||||
- Run load generator for 30 minutes
|
||||
- Verify observation rate ≥ 95%
|
||||
|
||||
2. **Integrity test:**
|
||||
- Generate function_map with signing
|
||||
- Create DSSE envelope
|
||||
- Post to Rekor
|
||||
- Add RFC-3161 timestamp
|
||||
- Verify all signatures and proofs
|
||||
|
||||
3. **Replayability test:**
|
||||
- Export StellaBundle with function_map + observations
|
||||
- Run offline verification 3 times in isolated environments
|
||||
- Assert identical results
|
||||
|
||||
4. **Performance test (if feasible in CI):**
|
||||
- Measure CPU overhead with/without probes
|
||||
- Measure collector memory usage
|
||||
- Assert within thresholds
|
||||
|
||||
5. **Privacy test:**
|
||||
- Inspect all observation payloads
|
||||
- Assert no raw arguments present
|
||||
- Assert only hashes and minimal context
|
||||
|
||||
Completion criteria:
|
||||
- [ ] Coverage acceptance test
|
||||
- [ ] Integrity acceptance test
|
||||
- [ ] Replayability acceptance test (3 runs)
|
||||
- [ ] Performance benchmark (manual or CI)
|
||||
- [ ] Privacy audit test
|
||||
- [ ] All tests passing in CI
|
||||
|
||||
---
|
||||
|
||||
## Execution Log
|
||||
|
||||
| Date (UTC) | Update | Owner |
|
||||
|------------|--------|-------|
|
||||
| 2026-01-22 | Sprint created from eBPF witness advisory gap analysis | Planning |
|
||||
|
||||
---
|
||||
|
||||
## Decisions & Risks
|
||||
|
||||
### Decisions Made
|
||||
|
||||
1. **function_map as separate predicate** - Not extending witness-v1, cleaner separation of concerns
|
||||
2. **Reuse existing hash recipes** - NodeHash and PathHash from witness-v1 contract for consistency
|
||||
3. **Postgres for observation storage** - Leverages existing infrastructure, supports time-range queries
|
||||
4. **CLI-first verification** - Offline verification via CLI before UI for air-gap users
|
||||
|
||||
### Risks
|
||||
|
||||
1. **Observation volume** - High-traffic services may generate many observations
|
||||
- Mitigation: Configurable sampling, aggregation, retention policy
|
||||
|
||||
2. **Clock skew** - Distributed observations may have timestamp drift
|
||||
- Mitigation: Use server-side timestamps, configurable tolerance
|
||||
|
||||
3. **Symbol resolution accuracy** - Different runtimes have different symbol formats
|
||||
- Mitigation: Use node hashes (PURL + normalized symbol) for matching
|
||||
|
||||
4. **Performance impact of persistence** - Writing every observation could be costly
|
||||
- Mitigation: Batch writes, async persistence, sampling option
|
||||
|
||||
### Open Questions
|
||||
|
||||
1. Should function_map support version ranges for expected components, or exact versions only?
|
||||
2. Should we support "learning mode" that auto-generates function_map from observations?
|
||||
3. How to handle function maps for services with feature flags (conditional paths)?
|
||||
|
||||
---
|
||||
|
||||
## Next Checkpoints
|
||||
|
||||
- [ ] RLV-001 complete - Schema defined
|
||||
- [ ] RLV-002, RLV-003 complete - Core verification logic works
|
||||
- [ ] RLV-004 complete - Checkpoint signatures verified (trust chain complete)
|
||||
- [ ] RLV-005 complete - Observations persisted
|
||||
- [ ] RLV-006, RLV-007, RLV-008 complete - CLI fully functional
|
||||
- [ ] RLV-009, RLV-010 complete - API and UI ready
|
||||
- [ ] RLV-011 complete - Bundle integration for offline
|
||||
- [ ] RLV-012 complete - Documentation finalized
|
||||
- [ ] RLV-013 complete - Acceptance criteria met
|
||||
@@ -191,6 +191,50 @@ stellaops alert bundle verify --file ./bundles/alert-123.stella.bundle.tgz
|
||||
stellaops alert bundle import --file ./bundles/alert-123.stella.bundle.tgz
|
||||
```
|
||||
|
||||
## Function Map Artifacts
|
||||
|
||||
Bundles can include runtime linkage verification artifacts. These are stored in dedicated subdirectories:
|
||||
|
||||
```
|
||||
bundle.stella.bundle.tgz
|
||||
├── ...existing structure...
|
||||
├── function-maps/
|
||||
│ ├── {service}-function-map.json
|
||||
│ └── {service}-function-map.dsse.json
|
||||
├── observations/
|
||||
│ └── {date-label}-observations.ndjson
|
||||
└── verification/
|
||||
├── verification-report.json
|
||||
└── verification-report.dsse.json
|
||||
```
|
||||
|
||||
### Artifact Types
|
||||
|
||||
| Artifact Type | Media Type | Description |
|
||||
|---------------|-----------|-------------|
|
||||
| `function-map` | `application/vnd.stella.function-map+json` | Function map predicate |
|
||||
| `function-map.dsse` | `application/vnd.dsse+json` | DSSE-signed function map |
|
||||
| `observations` | `application/x-ndjson` | Runtime observations (NDJSON) |
|
||||
| `verification-report` | `application/vnd.stella.verification-report+json` | Verification result |
|
||||
| `verification-report.dsse` | `application/vnd.dsse+json` | DSSE-signed verification report |
|
||||
|
||||
### Offline Verification Workflow
|
||||
|
||||
In air-gapped environments:
|
||||
|
||||
1. Export the bundle with function map and observations included
|
||||
2. Transfer to the air-gapped instance
|
||||
3. Run offline verification:
|
||||
```bash
|
||||
stella function-map verify \
|
||||
--function-map ./function-maps/my-service-function-map.json \
|
||||
--offline --observations ./observations/2026-01-23-observations.ndjson
|
||||
```
|
||||
|
||||
See [Function Map V1 Contract](../../../contracts/function-map-v1.md) for the predicate schema specification.
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Hash Verification**: Always verify bundle hash before processing
|
||||
|
||||
@@ -44,7 +44,81 @@ Notes:
|
||||
- Revocation evidence is verified using bundled OCSP/CRL data.
|
||||
- Rekor proofs are verified against the pinned checkpoint when provided.
|
||||
|
||||
## 5. References
|
||||
## 5. Two-Tier Bundle Modes
|
||||
|
||||
> **Sprint:** SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline (040-04, 040-06)
|
||||
|
||||
Evidence bundles are exported in one of two modes:
|
||||
|
||||
### 5.1 Light Mode (Default)
|
||||
|
||||
Contains only metadata and attestation envelopes. Binary blobs referenced in `largeBlobs[]` are not embedded.
|
||||
|
||||
```
|
||||
bundle/
|
||||
├── manifest.json # Bundle manifest with exportMode: "light"
|
||||
├── attestations/
|
||||
│ └── delta-sig.dsse.json
|
||||
└── tsa/
|
||||
├── chain/
|
||||
└── ocsp/
|
||||
```
|
||||
|
||||
**Advantages:** Small size, fast transfer.
|
||||
**Limitation:** Blob replay requires a source (`--blob-source`) or network access.
|
||||
|
||||
### 5.2 Full Mode (`--full`)
|
||||
|
||||
Includes all binary blobs referenced by attestations, enabling fully self-contained offline verification.
|
||||
|
||||
```
|
||||
bundle/
|
||||
├── manifest.json # Bundle manifest with exportMode: "full"
|
||||
├── attestations/
|
||||
│ └── delta-sig.dsse.json
|
||||
├── blobs/
|
||||
│ ├── sha256-<hex1> # Binary patch blob
|
||||
│ └── sha256-<hex2> # SBOM fragment blob
|
||||
└── tsa/
|
||||
├── chain/
|
||||
└── ocsp/
|
||||
```
|
||||
|
||||
**Advantages:** Fully self-contained, no network needed for replay.
|
||||
**Limitation:** Larger bundle size.
|
||||
|
||||
## 6. Blob Replay Verification
|
||||
|
||||
When `--replay` is specified, the verifier fetches and checks binary blobs referenced in attestation predicates:
|
||||
|
||||
```bash
|
||||
# Full bundle: blobs are embedded, no external source needed
|
||||
stella bundle verify --bundle full-bundle/ --offline --replay
|
||||
|
||||
# Light bundle: provide local blob source
|
||||
stella bundle verify --bundle light-bundle/ --replay --blob-source /path/to/blobs/
|
||||
|
||||
# Light bundle: fetch from registry (requires network)
|
||||
stella bundle verify --bundle light-bundle/ --replay --blob-source https://registry.example.com/blobs/
|
||||
```
|
||||
|
||||
### 6.1 Replay Steps
|
||||
|
||||
1. Parse attestation envelopes in `attestations/` directory
|
||||
2. Decode DSSE payloads and extract `largeBlobs[]` references
|
||||
3. For each blob reference:
|
||||
- Resolve content from embedded blobs, local source, or registry
|
||||
- Compute digest using declared algorithm (sha256/sha384/sha512)
|
||||
- Compare computed digest against declared digest
|
||||
4. Report pass/fail for each blob
|
||||
|
||||
### 6.2 Offline Constraints
|
||||
|
||||
- In `--offline` mode, registry blob fetches are blocked
|
||||
- Light bundles in offline mode require `--blob-source` pointing to a local directory
|
||||
- Full bundles work in offline mode without additional configuration
|
||||
|
||||
## 7. References
|
||||
|
||||
- `docs/modules/attestor/guides/timestamp-policy.md`
|
||||
- `docs/modules/attestor/airgap.md`
|
||||
|
||||
@@ -1407,7 +1407,75 @@ Evidence bundles follow OCI/ORAS conventions:
|
||||
└── sha256:<timestamp> # RFC 3161 timestamp
|
||||
```
|
||||
|
||||
### 10.6 Related Documentation
|
||||
### 10.6 Two-Tier Bundle Design and Large Blob References
|
||||
|
||||
> **Sprint:** SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline (040-04)
|
||||
|
||||
Evidence bundles support two export modes to balance transfer speed with auditability:
|
||||
|
||||
| Mode | Export Flag | Contents | Use Case |
|
||||
|------|------------|----------|----------|
|
||||
| **Light** | (default) | Manifest + attestation envelopes + metadata | Quick transfer, metadata-only audit |
|
||||
| **Full** | `--full` | Light + embedded binary blobs in `blobs/` | Air-gap replay, full provenance verification |
|
||||
|
||||
#### 10.6.1 `largeBlobs[]` Field
|
||||
|
||||
The `DeltaSigPredicate` includes a `largeBlobs` array referencing binary artifacts that may be too large to embed in attestation payloads:
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": "1.0.0",
|
||||
"subject": [...],
|
||||
"delta": [...],
|
||||
"largeBlobs": [
|
||||
{
|
||||
"kind": "binary-patch",
|
||||
"digest": "sha256:a1b2c3...",
|
||||
"mediaType": "application/octet-stream",
|
||||
"sizeBytes": 1048576
|
||||
},
|
||||
{
|
||||
"kind": "sbom-fragment",
|
||||
"digest": "sha256:d4e5f6...",
|
||||
"mediaType": "application/spdx+json",
|
||||
"sizeBytes": 32768
|
||||
}
|
||||
],
|
||||
"sbomDigest": "sha256:789abc..."
|
||||
}
|
||||
```
|
||||
|
||||
**Field Definitions:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `largeBlobs[].kind` | string | Blob category: `binary-patch`, `sbom-fragment`, `debug-symbols`, etc. |
|
||||
| `largeBlobs[].digest` | string | Content-addressable digest (`sha256:<hex>`, `sha384:<hex>`, `sha512:<hex>`) |
|
||||
| `largeBlobs[].mediaType` | string | IANA media type of the blob |
|
||||
| `largeBlobs[].sizeBytes` | long | Blob size in bytes |
|
||||
| `sbomDigest` | string | Digest of the canonical SBOM associated with this delta |
|
||||
|
||||
#### 10.6.2 Blob Fetch Strategy
|
||||
|
||||
During `stella bundle verify --replay`, blobs are resolved in priority order:
|
||||
|
||||
1. **Embedded** (full bundles): Read from `blobs/<digest-with-dash>` in bundle directory
|
||||
2. **Local source** (`--blob-source /path/`): Read from specified local directory
|
||||
3. **Registry** (`--blob-source https://...`): HTTP GET from OCI registry (blocked in `--offline` mode)
|
||||
|
||||
#### 10.6.3 Digest Verification
|
||||
|
||||
Fetched blobs are verified against their declared digest using the algorithm prefix:
|
||||
|
||||
```
|
||||
sha256:<hex> → SHA-256
|
||||
sha384:<hex> → SHA-384
|
||||
sha512:<hex> → SHA-512
|
||||
```
|
||||
|
||||
A mismatch fails the blob replay verification step.
|
||||
|
||||
### 10.7 Related Documentation
|
||||
|
||||
- [Golden Corpus KPIs](../../benchmarks/golden-corpus-kpis.md)
|
||||
- [Golden Corpus Seed List](../../benchmarks/golden-corpus-seed-list.md)
|
||||
|
||||
@@ -593,6 +593,159 @@ Token expires: 2025-12-24T10:30:00Z
|
||||
|
||||
---
|
||||
|
||||
## Score Commands
|
||||
|
||||
### stella score compute
|
||||
|
||||
Compute a unified trust score from signal values.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella score compute [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--finding-id <ID>` | CVE@PURL finding identifier |
|
||||
| `--cvss <score>` | CVSS base score (0-10) |
|
||||
| `--epss <score>` | EPSS probability (0-1) |
|
||||
| `--reachability <value>` | Reachability signal (0-1) |
|
||||
| `--runtime <value>` | Runtime observation signal (0-1) |
|
||||
| `--exploit <value>` | Exploit maturity signal (0-1) |
|
||||
| `--backport <value>` | Backport availability signal (0-1) |
|
||||
| `--source <value>` | Source confidence signal (0-1) |
|
||||
| `--mitigation <value>` | Mitigation strength signal (0-1) |
|
||||
| `--weights-version <ver>` | Pin specific weight manifest version |
|
||||
| `--show-unknowns` | Include U metric and band in output |
|
||||
| `--show-deltas` | Include delta-if-present calculations |
|
||||
| `--format <fmt>` | Output format: `table`, `json`, `markdown` |
|
||||
| `--offline` | Use bundled weights (no server required) |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Basic score computation
|
||||
stella score compute --finding-id CVE-2024-1234@pkg:npm/lodash@4.17.0 \
|
||||
--cvss 7.5 --epss 0.15 --reachability 0.9
|
||||
|
||||
# Full output with deltas
|
||||
stella score compute --finding-id CVE-2024-1234@pkg:npm/lodash@4.17.0 \
|
||||
--cvss 7.5 --reachability 0.9 --runtime 0.7 \
|
||||
--show-unknowns --show-deltas --format json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella score explain
|
||||
|
||||
Display detailed breakdown of a score computation.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella score explain <FINDING-ID> [OPTIONS]
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
stella score explain CVE-2024-1234@pkg:npm/lodash@4.17.0
|
||||
stella score explain CVE-2024-1234@pkg:npm/lodash@4.17.0 --format markdown
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella score replay
|
||||
|
||||
Fetch the signed replay proof for a previously computed score.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella score replay <SCORE-ID> [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--format <fmt>` | Output format: `table`, `json`, `markdown` |
|
||||
| `--verify-rekor` | Also verify Rekor inclusion proof |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
stella score replay score_a1b2c3d4e5f67890
|
||||
stella score replay score_a1b2c3d4e5f67890 --format json --verify-rekor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella score verify
|
||||
|
||||
Re-execute a score computation and verify it matches the original.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella score verify <SCORE-ID> [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--format <fmt>` | Output format: `table`, `json`, `markdown` |
|
||||
| `--verify-rekor` | Also verify Rekor inclusion proof |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
stella score verify score_a1b2c3d4e5f67890
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella gate score evaluate
|
||||
|
||||
Compute unified score as part of a gate evaluation (enhanced with unknowns support).
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella gate score evaluate [OPTIONS]
|
||||
```
|
||||
|
||||
**Additional Options (new):**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--show-unknowns` | Include U metric and unknowns band |
|
||||
| `--show-deltas` | Include delta-if-present for missing signals |
|
||||
| `--weights-version <ver>` | Pin specific weight manifest version |
|
||||
|
||||
---
|
||||
|
||||
### stella gate score weights
|
||||
|
||||
Manage EWS weight manifests.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella gate score weights <SUBCOMMAND>
|
||||
```
|
||||
|
||||
**Subcommands:**
|
||||
|
||||
| Subcommand | Description |
|
||||
|------------|-------------|
|
||||
| `list` | List available weight manifest versions |
|
||||
| `show <version>` | Display manifest details |
|
||||
| `diff <v1> <v2>` | Compare two manifests |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
stella gate score weights list
|
||||
stella gate score weights show v2026-01-22
|
||||
stella gate score weights diff v2026-01-22 v2026-02-01
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Policy Commands
|
||||
|
||||
### stella policy test
|
||||
@@ -781,6 +934,133 @@ stella analytics sbom-lake vulnerabilities --environment prod --min-severity hig
|
||||
|
||||
---
|
||||
|
||||
## Function Map Commands
|
||||
|
||||
### stella function-map generate
|
||||
|
||||
Generate a function map predicate from an SBOM and optional static analysis.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella function-map generate [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Alias | Description |
|
||||
|--------|-------|-------------|
|
||||
| `--sbom <path>` | `-s` | Path to SBOM file (required) |
|
||||
| `--service <name>` | | Service name (required) |
|
||||
| `--subject <purl>` | | Subject artifact PURL (derived from SBOM if omitted) |
|
||||
| `--static-analysis <path>` | | Path to static analysis results |
|
||||
| `--hot-functions <glob>` | `-H` | Glob patterns for functions of interest (repeatable) |
|
||||
| `--min-rate <value>` | | Minimum observation rate 0.0-1.0 (default 0.95) |
|
||||
| `--window <seconds>` | | Observation window in seconds (default 1800) |
|
||||
| `--fail-on-unexpected` | | Fail verification on unexpected symbols |
|
||||
| `--output <path>` | `-o` | Output file path |
|
||||
| `--format <fmt>` | `-f` | Output format: `json`, `yaml` (default json) |
|
||||
| `--build-id <id>` | | Build ID for provenance correlation |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Basic generation from SBOM
|
||||
stella function-map generate --sbom app.cdx.json --service my-backend
|
||||
|
||||
# With hot function filtering and custom thresholds
|
||||
stella function-map generate \
|
||||
--sbom app.cdx.json \
|
||||
--service my-backend \
|
||||
--hot-functions "crypto/*" --hot-functions "auth/*" \
|
||||
--min-rate 0.90 --window 3600 \
|
||||
--output function-map.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella function-map verify
|
||||
|
||||
Verify runtime observations against a function map predicate.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella function-map verify [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Alias | Description |
|
||||
|--------|-------|-------------|
|
||||
| `--function-map <path>` | `-m` | Path or OCI reference to predicate (required) |
|
||||
| `--container <id>` | `-c` | Filter to specific container ID |
|
||||
| `--from <timestamp>` | | ISO 8601 start time (default: 30 min ago) |
|
||||
| `--to <timestamp>` | | ISO 8601 end time (default: now) |
|
||||
| `--output <path>` | `-o` | Output verification report path |
|
||||
| `--format <fmt>` | `-f` | Output format: `json`, `table`, `md` (default table) |
|
||||
| `--strict` | | Fail on any unexpected symbols |
|
||||
| `--offline` | | Use bundled observations file |
|
||||
| `--observations <path>` | | Path to observations file (NDJSON) |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Online verification against live observations
|
||||
stella function-map verify \
|
||||
--function-map function-map.json \
|
||||
--from "2026-01-23T00:00:00Z" --to "2026-01-23T01:00:00Z"
|
||||
|
||||
# Offline verification with bundled observations
|
||||
stella function-map verify \
|
||||
--function-map function-map.json \
|
||||
--offline --observations obs.ndjson \
|
||||
--format json --output report.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Observations Commands
|
||||
|
||||
### stella observations query
|
||||
|
||||
Query runtime observations from the observation store.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella observations query [OPTIONS]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Alias | Description |
|
||||
|--------|-------|-------------|
|
||||
| `--symbol <glob>` | `-s` | Glob pattern for symbol name |
|
||||
| `--node-hash <hash>` | `-n` | Exact node hash filter |
|
||||
| `--container <id>` | `-c` | Container ID filter |
|
||||
| `--pod <name>` | `-p` | Pod name filter |
|
||||
| `--namespace <ns>` | `-N` | Kubernetes namespace filter |
|
||||
| `--probe-type <type>` | | Probe type filter |
|
||||
| `--from <timestamp>` | | ISO 8601 start time (default: 1 hour ago) |
|
||||
| `--to <timestamp>` | | ISO 8601 end time (default: now) |
|
||||
| `--limit <n>` | `-l` | Maximum results (default 100) |
|
||||
| `--offset <n>` | | Pagination offset (default 0) |
|
||||
| `--format <fmt>` | `-f` | Output format: `json`, `table`, `csv` (default table) |
|
||||
| `--summary` | | Show statistics instead of individual records |
|
||||
| `--output <path>` | `-o` | Output file path |
|
||||
| `--offline` | | Use local observations file |
|
||||
| `--observations-file <path>` | | Path to observations file for offline mode |
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Query all crypto-related observations
|
||||
stella observations query --symbol "crypto_*" --from "2026-01-23T00:00:00Z"
|
||||
|
||||
# Summary for a specific container
|
||||
stella observations query --container abc123 --summary
|
||||
|
||||
# Export as CSV for analysis
|
||||
stella observations query --pod my-service-pod --format csv --output obs.csv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ground-Truth Corpus Commands
|
||||
|
||||
### stella groundtruth
|
||||
@@ -1337,6 +1617,269 @@ KPIs:
|
||||
|
||||
**See Also:** [Ground-Truth CLI Guide](../ground-truth-cli.md)
|
||||
|
||||
---
|
||||
|
||||
## Attestation Commands
|
||||
|
||||
### stella attest attach
|
||||
|
||||
Attach an attestation (DSSE envelope) to an OCI image via ORAS referrers.
|
||||
|
||||
**Sprint:** SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline (040-01)
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella attest attach --image <ref> --attestation <path> [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
| Option | Alias | Description | Default |
|
||||
|--------|-------|-------------|---------|
|
||||
| `--image` | `-i` | OCI image reference (e.g., `registry.example.com/app:v1.2`) | (required) |
|
||||
| `--attestation` | `-a` | Path to DSSE envelope JSON file | (required) |
|
||||
| `--media-type` | | Media type for the attestation layer | `application/vnd.dsse.envelope.v1+json` |
|
||||
| `--registry-url` | | Override registry URL | From image reference |
|
||||
| `--verbose` | `-v` | Show detailed progress | `false` |
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
stella attest attach \
|
||||
--image registry.example.com/app:v1.2 \
|
||||
--attestation delta-sig.dsse.json \
|
||||
--verbose
|
||||
```
|
||||
|
||||
**Exit Codes:**
|
||||
- `0` - Attestation attached successfully
|
||||
- `1` - Attachment failed (registry error, invalid envelope)
|
||||
- `2` - Invalid input or configuration error
|
||||
|
||||
---
|
||||
|
||||
### stella attest verify
|
||||
|
||||
Verify attestations attached to an OCI image. Lists and validates DSSE envelopes, checks signatures, and optionally verifies Rekor annotations.
|
||||
|
||||
**Sprint:** SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline (040-02)
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella attest verify --image <ref> [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
| Option | Alias | Description | Default |
|
||||
|--------|-------|-------------|---------|
|
||||
| `--image` | `-i` | OCI image reference to verify | (required) |
|
||||
| `--predicate-type` | | Filter by predicate type URI | (all) |
|
||||
| `--trusted-keys` | | Path to trusted public keys directory | (none) |
|
||||
| `--require-rekor` | | Require valid Rekor inclusion annotations | `false` |
|
||||
| `--output` | `-o` | Output format: `table`, `json` | `table` |
|
||||
| `--verbose` | `-v` | Show detailed verification steps | `false` |
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
stella attest verify \
|
||||
--image registry.example.com/app:v1.2 \
|
||||
--predicate-type "https://stellaops.dev/delta-sig/v1" \
|
||||
--require-rekor \
|
||||
--output json
|
||||
```
|
||||
|
||||
**Exit Codes:**
|
||||
- `0` - All attestations verified successfully
|
||||
- `1` - One or more attestations failed verification
|
||||
- `2` - Invalid input or configuration error
|
||||
|
||||
---
|
||||
|
||||
## Binary Analysis Commands
|
||||
|
||||
### stella binary delta-sig attest
|
||||
|
||||
Sign a delta-sig predicate with an EC key and optionally submit to a Rekor transparency log. Produces a DSSE envelope suitable for `stella attest attach`.
|
||||
|
||||
**Sprint:** SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline (040-05)
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella binary delta-sig attest --predicate <path> --key <path> [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
| Option | Alias | Description | Default |
|
||||
|--------|-------|-------------|---------|
|
||||
| `--predicate` | `-p` | Path to delta-sig predicate JSON file | (required) |
|
||||
| `--key` | `-k` | Path to EC private key (PEM) for DSSE signing | (required) |
|
||||
| `--output` | `-o` | Path to write the DSSE envelope | stdout |
|
||||
| `--rekor-url` | | Rekor transparency log URL for submission | (none) |
|
||||
| `--receipt` | | Path to save Rekor receipt JSON | (none, only with `--rekor-url`) |
|
||||
| `--dry-run` | | Validate predicate and key without signing | `false` |
|
||||
| `--verbose` | `-v` | Show detailed signing and submission steps | `false` |
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Sign predicate and submit to Rekor
|
||||
stella binary delta-sig attest \
|
||||
--predicate delta-sig-predicate.json \
|
||||
--key signing-key.pem \
|
||||
--output signed-envelope.dsse.json \
|
||||
--rekor-url https://rekor.sigstore.dev \
|
||||
--receipt rekor-receipt.json \
|
||||
--verbose
|
||||
|
||||
# Dry run (validate only)
|
||||
stella binary delta-sig attest \
|
||||
--predicate delta-sig-predicate.json \
|
||||
--key signing-key.pem \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
**Signing Behavior:**
|
||||
- Key must be an ECDSA private key (PEM format)
|
||||
- Produces an in-toto v1 statement wrapping the predicate as DSSE payload
|
||||
- PAE (Pre-Authentication Encoding) used per DSSE specification
|
||||
- Signature is Base64-encoded in the envelope
|
||||
|
||||
**Rekor Submission:**
|
||||
- When `--rekor-url` is provided, the signed envelope is submitted to the transparency log
|
||||
- On success, Rekor UUID and log index are displayed
|
||||
- Receipt JSON includes `uuid`, `logIndex`, `integratedTime`, and `logUrl`
|
||||
|
||||
**Exit Codes:**
|
||||
- `0` - Signing (and optional Rekor submission) succeeded
|
||||
- `1` - Signing or submission failed
|
||||
- `2` - Invalid predicate, key format, or configuration error
|
||||
|
||||
---
|
||||
|
||||
## Bundle Commands
|
||||
|
||||
### stella bundle verify
|
||||
|
||||
Verify offline evidence bundles with full cryptographic verification. Checks manifest integrity, blob digests, DSSE signatures, Rekor proofs, timestamps, payload types, and optionally replays large blob content verification.
|
||||
|
||||
**Sprint:** SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline (040-06)
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella bundle verify --bundle <path> [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
| Option | Alias | Description | Default |
|
||||
|--------|-------|-------------|---------|
|
||||
| `--bundle` | `-b` | Path to bundle (tar.gz or directory) | (required) |
|
||||
| `--trust-root` | | Path to trusted root certificate (PEM) | (none) |
|
||||
| `--rekor-checkpoint` | | Path to Rekor checkpoint for offline proof verification | (none) |
|
||||
| `--offline` | | Run in offline mode (no network access) | `false` |
|
||||
| `--output` | `-o` | Output format: `table`, `json` | `table` |
|
||||
| `--strict` | | Fail on any warning (missing optional artifacts) | `false` |
|
||||
| `--signer` | | Path to signing key (PEM) for verification report | (none) |
|
||||
| `--signer-cert` | | Path to signer certificate PEM (for report metadata) | (none) |
|
||||
| `--replay` | | Verify binary content by fetching/reading large blobs referenced in attestations | `false` |
|
||||
| `--blob-source` | | Override blob source (registry URL or local directory path) | (auto-detect) |
|
||||
| `--verbose` | `-v` | Show detailed verification steps | `false` |
|
||||
|
||||
**Verification Steps:**
|
||||
1. **Manifest checksum** - Validate bundle manifest integrity
|
||||
2. **Blob digests** - Verify all blob file SHA-256 digests match manifest
|
||||
3. **DSSE signatures** - Validate envelope signatures against trusted keys
|
||||
4. **Rekor proofs** - Verify inclusion proofs against checkpoint (when provided)
|
||||
5. **Timestamps** - Validate RFC 3161 timestamps against TSA certificates
|
||||
6. **Payload types** - Verify predicate types match expectations
|
||||
7. **Blob Replay** (when `--replay`) - Fetch and verify large blobs referenced in attestations
|
||||
|
||||
**Blob Replay Behavior:**
|
||||
- For **full bundles** (blobs embedded): verifies content from `blobs/` directory against attestation digests
|
||||
- For **light bundles** (metadata only): fetches blobs from `--blob-source` (local dir or registry URL)
|
||||
- Supports `sha256`, `sha384`, `sha512` digest algorithms
|
||||
- In `--offline` mode, blob fetch from registries is blocked (only local sources work)
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Basic verification
|
||||
stella bundle verify --bundle evidence-bundle.tar.gz
|
||||
|
||||
# Full verification with replay and trust root
|
||||
stella bundle verify \
|
||||
--bundle /path/to/bundle \
|
||||
--trust-root /etc/stellaops/tsa-root.pem \
|
||||
--rekor-checkpoint checkpoint.json \
|
||||
--replay \
|
||||
--verbose
|
||||
|
||||
# Light bundle with local blob source
|
||||
stella bundle verify \
|
||||
--bundle light-bundle/ \
|
||||
--replay \
|
||||
--blob-source /path/to/blobs/
|
||||
|
||||
# Strict offline verification with signed report
|
||||
stella bundle verify \
|
||||
--bundle evidence-bundle/ \
|
||||
--offline \
|
||||
--strict \
|
||||
--signer report-key.pem \
|
||||
--signer-cert report-cert.pem
|
||||
```
|
||||
|
||||
**Exit Codes:**
|
||||
- `0` - All verifications passed
|
||||
- `1` - One or more verifications failed
|
||||
- `2` - Invalid input or configuration error
|
||||
|
||||
---
|
||||
|
||||
## Evidence Commands
|
||||
|
||||
### stella evidence export-bundle
|
||||
|
||||
Export evidence bundles for offline verification. Supports two-tier export modes: **light** (metadata and attestations only) and **full** (includes embedded binary blobs).
|
||||
|
||||
**Sprint:** SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline (040-04)
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
stella evidence export-bundle --image <ref> --output <path> [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
| Option | Alias | Description | Default |
|
||||
|--------|-------|-------------|---------|
|
||||
| `--image` | `-i` | OCI image reference to export evidence for | (required) |
|
||||
| `--output` | `-o` | Output bundle path (.tar.gz or directory) | (required) |
|
||||
| `--full` | | Export in full mode (embed binary blobs alongside attestations) | `false` (light mode) |
|
||||
| `--sign-with` | | Signing method for bundle: `cosign`, `sigstore`, `none` | `none` |
|
||||
| `--verbose` | `-v` | Show detailed export progress | `false` |
|
||||
|
||||
**Export Modes:**
|
||||
|
||||
| Mode | Flag | Contents | Size | Use Case |
|
||||
|------|------|----------|------|----------|
|
||||
| **Light** | (default) | Manifest, attestation envelopes, metadata | Small | Quick transfer, metadata audit |
|
||||
| **Full** | `--full` | Light + embedded binary blobs in `blobs/` dir | Large | Air-gap verification, replay |
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
# Light export (default)
|
||||
stella evidence export-bundle \
|
||||
--image registry.example.com/app:v1.2 \
|
||||
--output evidence-light.tar.gz
|
||||
|
||||
# Full export with embedded blobs
|
||||
stella evidence export-bundle \
|
||||
--image registry.example.com/app:v1.2 \
|
||||
--output evidence-full.tar.gz \
|
||||
--full \
|
||||
--verbose
|
||||
```
|
||||
|
||||
**Exit Codes:**
|
||||
- `0` - Bundle exported successfully
|
||||
- `1` - Export failed
|
||||
- `2` - Invalid input or configuration error
|
||||
|
||||
---
|
||||
## Reporting & Export Commands
|
||||
|
||||
|
||||
@@ -133,5 +133,95 @@ signed-sbom-{digest}-{timestamp}.tar.gz
|
||||
### Related Commands
|
||||
|
||||
- `stella sbom generate` — Generate SBOM from container image
|
||||
- `stella sbom publish` — Publish canonical SBOM as OCI referrer
|
||||
- `stella attest verify --offline` — Verify attestation bundles offline
|
||||
- `stella evidence export` — Export evidence bundle with signed SBOM
|
||||
|
||||
---
|
||||
|
||||
## stella sbom publish — OCI SBOM Publication
|
||||
|
||||
### Synopsis
|
||||
|
||||
```bash
|
||||
stella sbom publish --image <ref> [--file <path>] [--format cdx|spdx] [--overwrite]
|
||||
```
|
||||
|
||||
Publishes a canonical (volatile-fields-stripped, key-sorted) SBOM as an OCI referrer artifact attached to the specified container image. The published artifact is discoverable via the OCI Distribution Spec 1.1 referrers API.
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Alias | Description |
|
||||
|--------|-------|-------------|
|
||||
| `--image <ref>` | `-i` | **Required.** Target image reference (`registry/repo@sha256:...`). Must include digest. |
|
||||
| `--file <path>` | `-f` | Path to SBOM file. If omitted, fetches from Scanner CAS for this image. |
|
||||
| `--format <fmt>` | | SBOM format: `cdx` (CycloneDX) or `spdx`. Auto-detected from file content if omitted. |
|
||||
| `--overwrite` | | Supersede the current active SBOM referrer for this image. |
|
||||
| `--registry-url <url>` | | Override registry URL (defaults to parsed from `--image`). |
|
||||
| `--verbose` | | Show detailed output including blob digest and normalization info. |
|
||||
|
||||
### Behavior
|
||||
|
||||
1. **Normalization**: The SBOM is canonicalized before publication:
|
||||
- Volatile fields stripped: `serialNumber`, `metadata.tools`, `metadata.authors`, `metadata.timestamp` (CycloneDX); `creationInfo.created`, `creationInfo.creators`, `creationInfo.licenseListVersion` (SPDX).
|
||||
- Object keys sorted lexicographically (ordinal).
|
||||
- Arrays of objects sorted by deterministic keys (bom-ref, purl, name@version).
|
||||
- See `docs/contracts/sbom-volatile-fields.json` for the authoritative field list.
|
||||
|
||||
2. **Publication**: The canonical SBOM bytes are pushed as an OCI artifact with:
|
||||
- `artifactType`: `application/vnd.stellaops.sbom.cdx+json` or `application/vnd.stellaops.sbom.spdx+json`
|
||||
- `subject`: points to the image manifest digest
|
||||
- Annotations: `dev.stellaops/sbom-version`, `dev.stellaops/sbom-format`
|
||||
|
||||
3. **Overwrite/Supersede**: When `--overwrite` is specified:
|
||||
- The current active SBOM referrer is resolved (highest version number).
|
||||
- A new referrer is pushed with `version = prior + 1` and a `dev.stellaops/sbom-supersedes` annotation pointing to the prior manifest digest.
|
||||
- No registry deletes are performed (purely additive).
|
||||
|
||||
### Exit Codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | Publication succeeded |
|
||||
| 1 | Publication failed (registry error, auth failure) |
|
||||
| 2 | Error (file not found, invalid image reference, parse error) |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
# Publish a CycloneDX SBOM to an image
|
||||
stella sbom publish --image registry.example.com/myapp@sha256:abc123... --file app.cdx.json
|
||||
|
||||
# Publish with explicit format
|
||||
stella sbom publish --image registry.example.com/myapp@sha256:abc123... --file app.json --format cdx
|
||||
|
||||
# Overwrite existing SBOM (supersede)
|
||||
stella sbom publish --image registry.example.com/myapp@sha256:abc123... --file improved.cdx.json --overwrite
|
||||
|
||||
# Verbose output
|
||||
stella sbom publish --image registry.example.com/myapp@sha256:abc123... --file app.cdx.json --verbose
|
||||
```
|
||||
|
||||
### Sample Output
|
||||
|
||||
```
|
||||
Published SBOM as OCI referrer:
|
||||
Blob digest: sha256:e3b0c44298fc1c149afbf4c8996fb924...
|
||||
Manifest digest: sha256:7d865e959b2466918c9863afca942d0f...
|
||||
Version: 1
|
||||
Artifact type: application/vnd.stellaops.sbom.cdx+json
|
||||
```
|
||||
|
||||
### Verifier Discovery
|
||||
|
||||
Third-party verifiers can discover published SBOMs via the OCI referrers API:
|
||||
|
||||
```bash
|
||||
# List SBOM referrers for an image (using oras CLI)
|
||||
oras discover registry.example.com/myapp@sha256:abc123... \
|
||||
--artifact-type application/vnd.stellaops.sbom.cdx+json
|
||||
|
||||
# Pull the latest SBOM
|
||||
oras pull registry.example.com/myapp@sha256:abc123... \
|
||||
--artifact-type application/vnd.stellaops.sbom.cdx+json
|
||||
```
|
||||
|
||||
223
docs/modules/cli/guides/delta-attestation-workflow.md
Normal file
223
docs/modules/cli/guides/delta-attestation-workflow.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Delta Attestation Workflow Guide
|
||||
|
||||
> **Audience:** CI/CD engineers, release operators, security auditors
|
||||
>
|
||||
> **Purpose:** End-to-end guide for generating, signing, attaching, verifying, and exporting delta-sig attestations.
|
||||
>
|
||||
> **Sprint:** SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline
|
||||
|
||||
## Overview
|
||||
|
||||
The delta attestation workflow provides verifiable evidence of binary-level changes between releases. It covers the full lifecycle from generating a delta-sig predicate through to offline bundle verification.
|
||||
|
||||
```
|
||||
diff → attest → attach → verify → export → offline-verify
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `stella` CLI installed and configured
|
||||
- EC signing key (PEM format) for attestation signing
|
||||
- Access to target OCI registry (for attach/verify)
|
||||
- (Optional) Rekor transparency log URL for public verifiability
|
||||
|
||||
## Step 1: Generate Delta-Sig Predicate
|
||||
|
||||
Compare two binary builds to produce a delta-sig predicate describing function-level changes:
|
||||
|
||||
```bash
|
||||
stella binary delta-sig diff \
|
||||
--old /path/to/old-binary \
|
||||
--new /path/to/new-binary \
|
||||
--output delta-predicate.json \
|
||||
--arch linux-amd64
|
||||
```
|
||||
|
||||
The predicate JSON follows the `https://stellaops.dev/delta-sig/v1` schema and includes:
|
||||
- `subject[]` - Old and new binary references with digests
|
||||
- `delta[]` - Function-level changes (added, removed, modified)
|
||||
- `summary` - Aggregate change statistics
|
||||
- `tooling` - Lifter and diff algorithm metadata
|
||||
- `largeBlobs[]` - References to binary patches or SBOM fragments (optional)
|
||||
- `sbomDigest` - Digest of the associated canonical SBOM (optional)
|
||||
|
||||
## Step 2: Sign and Attest
|
||||
|
||||
Sign the predicate with an EC key, producing a DSSE envelope. Optionally submit to a Rekor transparency log:
|
||||
|
||||
```bash
|
||||
stella binary delta-sig attest \
|
||||
--predicate delta-predicate.json \
|
||||
--key signing-key.pem \
|
||||
--output signed-envelope.dsse.json \
|
||||
--rekor-url https://rekor.sigstore.dev \
|
||||
--receipt rekor-receipt.json \
|
||||
--verbose
|
||||
```
|
||||
|
||||
**Output:**
|
||||
- `signed-envelope.dsse.json` - DSSE envelope with in-toto v1 statement
|
||||
- `rekor-receipt.json` - Rekor inclusion proof (UUID, log index, integrated time)
|
||||
|
||||
**Without Rekor (air-gapped environments):**
|
||||
|
||||
```bash
|
||||
stella binary delta-sig attest \
|
||||
--predicate delta-predicate.json \
|
||||
--key signing-key.pem \
|
||||
--output signed-envelope.dsse.json
|
||||
```
|
||||
|
||||
## Step 3: Attach to OCI Image
|
||||
|
||||
Attach the signed attestation to the target OCI image via ORAS referrers:
|
||||
|
||||
```bash
|
||||
stella attest attach \
|
||||
--image registry.example.com/app:v1.2 \
|
||||
--attestation signed-envelope.dsse.json \
|
||||
--verbose
|
||||
```
|
||||
|
||||
The attestation is stored as a referrer artifact in the registry, discoverable by image digest.
|
||||
|
||||
## Step 4: Verify Attestations
|
||||
|
||||
Verify that attestations are properly attached and valid:
|
||||
|
||||
```bash
|
||||
stella attest verify \
|
||||
--image registry.example.com/app:v1.2 \
|
||||
--predicate-type "https://stellaops.dev/delta-sig/v1" \
|
||||
--require-rekor \
|
||||
--verbose
|
||||
```
|
||||
|
||||
This checks:
|
||||
- DSSE envelope signature validity
|
||||
- Predicate type matches expected schema
|
||||
- Rekor annotations are present and valid (when `--require-rekor`)
|
||||
|
||||
## Step 5: Export Evidence Bundle
|
||||
|
||||
Export all attestation evidence for offline environments:
|
||||
|
||||
```bash
|
||||
# Light mode (metadata only, small size)
|
||||
stella evidence export-bundle \
|
||||
--image registry.example.com/app:v1.2 \
|
||||
--output evidence-light.tar.gz
|
||||
|
||||
# Full mode (includes binary blobs for replay)
|
||||
stella evidence export-bundle \
|
||||
--image registry.example.com/app:v1.2 \
|
||||
--output evidence-full.tar.gz \
|
||||
--full
|
||||
```
|
||||
|
||||
### Bundle Contents
|
||||
|
||||
**Light bundle:**
|
||||
```
|
||||
bundle/
|
||||
├── manifest.json # exportMode: "light"
|
||||
└── attestations/
|
||||
└── delta-sig.dsse.json
|
||||
```
|
||||
|
||||
**Full bundle:**
|
||||
```
|
||||
bundle/
|
||||
├── manifest.json # exportMode: "full"
|
||||
├── attestations/
|
||||
│ └── delta-sig.dsse.json
|
||||
└── blobs/
|
||||
├── sha256-<hex1> # Binary patch
|
||||
└── sha256-<hex2> # SBOM fragment
|
||||
```
|
||||
|
||||
## Step 6: Offline Bundle Verification
|
||||
|
||||
Verify the exported bundle in air-gapped environments:
|
||||
|
||||
```bash
|
||||
# Full bundle: self-contained verification with blob replay
|
||||
stella bundle verify \
|
||||
--bundle evidence-full.tar.gz \
|
||||
--offline \
|
||||
--trust-root /etc/stellaops/tsa-root.pem \
|
||||
--replay \
|
||||
--verbose
|
||||
|
||||
# Light bundle: provide local blob source for replay
|
||||
stella bundle verify \
|
||||
--bundle evidence-light.tar.gz \
|
||||
--offline \
|
||||
--replay \
|
||||
--blob-source /path/to/cached-blobs/
|
||||
```
|
||||
|
||||
### Verification Steps
|
||||
|
||||
| Step | Check | Failure Behavior |
|
||||
|------|-------|------------------|
|
||||
| 1 | Manifest checksum | Fatal |
|
||||
| 2 | Blob digests | Fatal |
|
||||
| 3 | DSSE signatures | Fatal |
|
||||
| 4 | Rekor proofs | Fatal (if checkpoint provided) |
|
||||
| 5 | RFC 3161 timestamps | Fatal (in strict mode) |
|
||||
| 6 | Payload type expectations | Warning (fatal in strict) |
|
||||
| 7 | Blob replay | Fatal (when `--replay` enabled) |
|
||||
|
||||
## CI/CD Integration Example
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/release.yaml
|
||||
jobs:
|
||||
attest:
|
||||
steps:
|
||||
- name: Generate delta predicate
|
||||
run: |
|
||||
stella binary delta-sig diff \
|
||||
--old ${{ steps.build.outputs.old_binary }} \
|
||||
--new ${{ steps.build.outputs.new_binary }} \
|
||||
--output delta-predicate.json
|
||||
|
||||
- name: Sign and submit to Rekor
|
||||
run: |
|
||||
stella binary delta-sig attest \
|
||||
--predicate delta-predicate.json \
|
||||
--key ${{ secrets.SIGNING_KEY_PATH }} \
|
||||
--output envelope.dsse.json \
|
||||
--rekor-url https://rekor.sigstore.dev \
|
||||
--receipt rekor-receipt.json
|
||||
|
||||
- name: Attach to image
|
||||
run: |
|
||||
stella attest attach \
|
||||
--image ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ env.TAG }} \
|
||||
--attestation envelope.dsse.json
|
||||
|
||||
- name: Export full bundle for auditors
|
||||
run: |
|
||||
stella evidence export-bundle \
|
||||
--image ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ env.TAG }} \
|
||||
--output evidence-bundle.tar.gz \
|
||||
--full
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Cause | Resolution |
|
||||
|-------|-------|------------|
|
||||
| `Blob Replay ✗` | Missing blobs in light bundle | Use `--blob-source` or export with `--full` |
|
||||
| `DSSE signature ✗` | Key mismatch | Verify signing key matches trusted keys |
|
||||
| `Rekor proof ✗` | No checkpoint provided | Add `--rekor-checkpoint` for offline |
|
||||
| Exit code 2 | Invalid predicate JSON | Check required fields: subject, delta, summary, tooling, computedAt |
|
||||
|
||||
## See Also
|
||||
|
||||
- [CLI Commands Reference](commands/reference.md)
|
||||
- [Offline Verification Guide](../../attestor/guides/offline-verification.md)
|
||||
- [BinaryIndex Architecture](../../binary-index/architecture.md)
|
||||
- [Audit Bundle Format](audit-bundle-format.md)
|
||||
@@ -173,6 +173,10 @@ The Determinization subsystem calculates uncertainty scores based on signal comp
|
||||
|
||||
Determinization scores are exposed to SPL policies via the `signals.trust.*` and `signals.uncertainty.*` namespaces. Use `signals.uncertainty.entropy` to access entropy values and `signals.trust.score` for aggregated trust scores that combine VEX, reachability, runtime, and other signals with decay/weighting.
|
||||
|
||||
**Weight Manifests:**
|
||||
|
||||
EWS weights are externalized to versioned JSON manifests in `etc/weights/`. The unified score facade (`IUnifiedScoreService`) loads weights from these manifests rather than using compiled defaults, enabling auditable weight changes without code modifications. See [Unified Score Architecture](../../technical/scoring-algebra.md) §4 for manifest schema and versioning rules.
|
||||
|
||||
### 3.2 - License compliance configuration
|
||||
|
||||
License compliance evaluation runs during SBOM evaluation when enabled in
|
||||
@@ -856,4 +860,141 @@ The following product advisories provide strategic context for Policy Engine fea
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-12-26 (Sprint 006).*
|
||||
## 13 · Policy Interop Layer
|
||||
|
||||
> **Sprint:** SPRINT_20260122_041_Policy_interop_import_export_rego
|
||||
|
||||
The Interop Layer provides bidirectional policy exchange between Stella's native C# gate engine and OPA/Rego. The C# engine remains primary; Rego serves as an interoperability adapter for teams using OPA-based toolchains.
|
||||
|
||||
### 13.1 · Supported Formats
|
||||
|
||||
| Format | Schema | Direction | Notes |
|
||||
|--------|--------|-----------|-------|
|
||||
| **PolicyPack v2 (JSON)** | `policy.stellaops.io/v2` | Import + Export | Canonical format with typed gates, environment overrides, remediation hints |
|
||||
| **OPA/Rego** | `package stella.release` | Export (+ Import with pattern matching) | Deny-by-default pattern, `remediation` output rules |
|
||||
|
||||
### 13.2 · Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Interop["StellaOps.Policy.Interop"]
|
||||
Exporter[JsonPolicyExporter / RegoPolicyExporter]
|
||||
Importer[JsonPolicyImporter / RegoPolicyImporter]
|
||||
Validator[PolicySchemaValidator]
|
||||
Generator[RegoCodeGenerator]
|
||||
Resolver[RemediationResolver]
|
||||
OPA[EmbeddedOpaEvaluator]
|
||||
Detector[FormatDetector]
|
||||
end
|
||||
subgraph Consumers
|
||||
CLI[stella policy export/import/validate/evaluate]
|
||||
API[Platform API /api/v1/policy/interop]
|
||||
UI[Policy Editor UI]
|
||||
end
|
||||
|
||||
CLI --> Exporter
|
||||
CLI --> Importer
|
||||
CLI --> Validator
|
||||
API --> Exporter
|
||||
API --> Importer
|
||||
API --> Validator
|
||||
UI --> API
|
||||
|
||||
Exporter --> Generator
|
||||
Exporter --> Resolver
|
||||
Importer --> Detector
|
||||
Importer --> OPA
|
||||
Generator --> Resolver
|
||||
```
|
||||
|
||||
### 13.3 · Gate-to-Rego Translation
|
||||
|
||||
Each C# gate type maps to a Rego deny rule pattern:
|
||||
|
||||
| Gate Type | Rego Pattern | Remediation Code |
|
||||
|-----------|-------------|-----------------|
|
||||
| `CvssThresholdGate` | `input.cvss.score >= threshold` | `CVSS_EXCEED` |
|
||||
| `SignatureRequiredGate` | `not input.dsse.verified` | `SIG_MISS` |
|
||||
| `EvidenceFreshnessGate` | `not input.freshness.tstVerified` | `FRESH_EXPIRED` |
|
||||
| `SbomPresenceGate` | `not input.sbom.canonicalDigest` | `SBOM_MISS` |
|
||||
| `MinimumConfidenceGate` | `input.confidence < threshold` | `CONF_LOW` |
|
||||
| `UnknownsBudgetGate` | `input.unknownsRatio > threshold` | `UNK_EXCEED` |
|
||||
| `ReachabilityRequirementGate` | `not input.reachability.status` | `REACH_REQUIRED` |
|
||||
|
||||
### 13.4 · Remediation Hints
|
||||
|
||||
When a gate blocks, the system resolves structured remediation hints:
|
||||
|
||||
```
|
||||
Priority: Gate-defined hint > Built-in defaults > null
|
||||
|
||||
RemediationHint:
|
||||
Code: Machine-readable (e.g., "CVSS_EXCEED")
|
||||
Title: Human-readable summary
|
||||
Actions[]: CLI command templates with {placeholders}
|
||||
References: External documentation links
|
||||
Severity: critical | high | medium | low
|
||||
```
|
||||
|
||||
Placeholders (`{purl}`, `{image}`, `{reason}`) are resolved via `RemediationContext` at evaluation time.
|
||||
|
||||
### 13.5 · Determinism
|
||||
|
||||
All exports and evaluations are deterministic:
|
||||
- Same policy + same input = same output (hash-verifiable)
|
||||
- Exports include SHA-256 `digest` field
|
||||
- No time-dependent logic in deterministic mode
|
||||
- `outputDigest` in evaluation results enables replay verification
|
||||
|
||||
### 13.6 · Implementation Reference
|
||||
|
||||
| Component | Source File |
|
||||
|-----------|-------------|
|
||||
| Contracts | `src/Policy/__Libraries/StellaOps.Policy.Interop/Contracts/PolicyPackDocument.cs` |
|
||||
| Remediation Models | `src/Policy/__Libraries/StellaOps.Policy.Interop/Contracts/RemediationModels.cs` |
|
||||
| Interfaces | `src/Policy/__Libraries/StellaOps.Policy.Interop/Abstractions/` |
|
||||
| JSON Exporter | `src/Policy/__Libraries/StellaOps.Policy.Interop/Export/JsonPolicyExporter.cs` |
|
||||
| JSON Importer | `src/Policy/__Libraries/StellaOps.Policy.Interop/Import/JsonPolicyImporter.cs` |
|
||||
| Rego Generator | `src/Policy/__Libraries/StellaOps.Policy.Interop/Rego/RegoCodeGenerator.cs` |
|
||||
| Rego Importer | `src/Policy/__Libraries/StellaOps.Policy.Interop/Import/RegoPolicyImporter.cs` |
|
||||
| Embedded OPA | `src/Policy/__Libraries/StellaOps.Policy.Interop/Evaluation/EmbeddedOpaEvaluator.cs` |
|
||||
| Remediation Resolver | `src/Policy/__Libraries/StellaOps.Policy.Interop/Evaluation/RemediationResolver.cs` |
|
||||
| Format Detector | `src/Policy/__Libraries/StellaOps.Policy.Interop/Import/FormatDetector.cs` |
|
||||
| Schema Validator | `src/Policy/__Libraries/StellaOps.Policy.Interop/Validation/PolicySchemaValidator.cs` |
|
||||
| CLI Commands | `src/Cli/StellaOps.Cli/Commands/Policy/PolicyInteropCommandGroup.cs` |
|
||||
| Platform API | `src/Platform/StellaOps.Platform.WebService/Endpoints/PolicyInteropEndpoints.cs` |
|
||||
| JSON Schema | `docs/schemas/policy-pack-v2.schema.json` |
|
||||
|
||||
### 13.7 · CLI Interface
|
||||
|
||||
```bash
|
||||
# Export to Rego
|
||||
stella policy export --file policy.json --format rego --output-file release.rego
|
||||
|
||||
# Import with validation
|
||||
stella policy import --file external.rego --validate-only
|
||||
|
||||
# Validate policy document
|
||||
stella policy validate --file policy.json --strict
|
||||
|
||||
# Evaluate with remediation hints
|
||||
stella policy evaluate --policy baseline.json --input evidence.json --environment production
|
||||
```
|
||||
|
||||
Exit codes: `0` = success/allow, `1` = warn, `2` = block/errors, `10` = input-error, `12` = policy-error.
|
||||
|
||||
### 13.8 · Platform API
|
||||
|
||||
Group: `/api/v1/policy/interop` with tag `PolicyInterop`
|
||||
|
||||
| Method | Path | Auth Policy | Description |
|
||||
|--------|------|-------------|-------------|
|
||||
| POST | `/export` | `platform.policy.read` | Export policy to format |
|
||||
| POST | `/import` | `platform.policy.write` | Import policy from format |
|
||||
| POST | `/validate` | `platform.policy.read` | Validate policy document |
|
||||
| POST | `/evaluate` | `platform.policy.evaluate` | Evaluate policy against input |
|
||||
| GET | `/formats` | `platform.policy.read` | List supported formats |
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-01-23 (Sprint 041).*
|
||||
|
||||
219
docs/modules/policy/guides/policy-import-export.md
Normal file
219
docs/modules/policy/guides/policy-import-export.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Policy Import/Export Guide
|
||||
|
||||
This guide covers bidirectional policy exchange between Stella's native C# engine and OPA/Rego.
|
||||
|
||||
## Overview
|
||||
|
||||
Stella supports two policy formats:
|
||||
- **PolicyPack v2 (JSON)**: Canonical format with typed gates, environment overrides, and remediation hints.
|
||||
- **OPA/Rego**: Standard policy-as-code format for interoperability with OPA-based toolchains.
|
||||
|
||||
The C# gate engine remains primary. Rego is an export target for teams using OPA, and an import source for adopting external policies.
|
||||
|
||||
## Formats
|
||||
|
||||
### PolicyPack v2 (JSON)
|
||||
|
||||
Schema: `policy.stellaops.io/v2`
|
||||
|
||||
Structure:
|
||||
```json
|
||||
{
|
||||
"apiVersion": "policy.stellaops.io/v2",
|
||||
"kind": "PolicyPack",
|
||||
"metadata": { "name": "...", "version": "1.0.0" },
|
||||
"spec": {
|
||||
"settings": { "defaultAction": "block", "deterministicMode": true },
|
||||
"gates": [...],
|
||||
"rules": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Key features:
|
||||
- Per-environment configuration overrides (production/staging/development thresholds)
|
||||
- Structured remediation hints with CLI command templates
|
||||
- Deterministic evaluation mode
|
||||
- SHA-256 content digest for integrity
|
||||
|
||||
### OPA/Rego
|
||||
|
||||
Generated or imported Rego follows the deny-by-default pattern:
|
||||
|
||||
```rego
|
||||
package stella.release
|
||||
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
deny contains msg if {
|
||||
not input.dsse.verified
|
||||
msg := "DSSE signature missing"
|
||||
}
|
||||
|
||||
allow if { count(deny) == 0 }
|
||||
|
||||
remediation contains hint if {
|
||||
some msg in deny
|
||||
msg == "DSSE signature missing"
|
||||
hint := {"code": "DSSE_MISS", "fix": "...", "severity": "critical"}
|
||||
}
|
||||
```
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### Export
|
||||
|
||||
Export a policy to JSON or Rego:
|
||||
|
||||
```bash
|
||||
# Export to Rego
|
||||
stella policy export --file policy.json --format rego --output-file release.rego
|
||||
|
||||
# Export with environment-specific thresholds
|
||||
stella policy export --file policy.json --format rego --environment production
|
||||
|
||||
# Export without remediation hints
|
||||
stella policy export --file policy.json --format json --include-remediation false
|
||||
|
||||
# Export to stdout (pipe-friendly)
|
||||
stella policy export --file policy.json --format rego | opa check -
|
||||
```
|
||||
|
||||
### Import
|
||||
|
||||
Import a policy from JSON or Rego:
|
||||
|
||||
```bash
|
||||
# Import and validate a JSON policy
|
||||
stella policy import --file production-baseline.json
|
||||
|
||||
# Import with validation only (no persist)
|
||||
stella policy import --file external-policy.rego --validate-only
|
||||
|
||||
# Dry-run to preview changes
|
||||
stella policy import --file new-rules.json --dry-run
|
||||
|
||||
# Force format detection
|
||||
stella policy import --file rules.txt --format rego
|
||||
```
|
||||
|
||||
### Validate
|
||||
|
||||
Validate a policy file:
|
||||
|
||||
```bash
|
||||
# Basic validation
|
||||
stella policy validate --file policy.json
|
||||
|
||||
# Strict mode (warnings become errors)
|
||||
stella policy validate --file policy.json --strict
|
||||
|
||||
# JSON output for CI integration
|
||||
stella policy validate --file policy.json --output json
|
||||
```
|
||||
|
||||
Exit codes: `0` = valid, `1` = warnings, `2` = errors.
|
||||
|
||||
### Evaluate
|
||||
|
||||
Evaluate a policy against evidence:
|
||||
|
||||
```bash
|
||||
# Evaluate with table output
|
||||
stella policy evaluate --policy baseline.json --input evidence.json
|
||||
|
||||
# With environment override
|
||||
stella policy evaluate --policy baseline.json --input evidence.json --environment staging
|
||||
|
||||
# JSON output for programmatic use
|
||||
stella policy evaluate --policy baseline.json --input evidence.json --output json
|
||||
|
||||
# CI mode (GitHub Actions annotations)
|
||||
stella policy evaluate --policy baseline.json --input evidence.json --output ci
|
||||
```
|
||||
|
||||
Exit codes: `0` = allow, `1` = warn, `2` = block.
|
||||
|
||||
## Evidence Input Format
|
||||
|
||||
The evaluation input follows the canonical evidence JSON schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"environment": "production",
|
||||
"subject": {
|
||||
"imageDigest": "sha256:abc...",
|
||||
"purl": "pkg:docker/myapp@1.0.0",
|
||||
"tags": ["env:prod"]
|
||||
},
|
||||
"dsse": { "verified": true, "signers": ["ca://fulcio/..."] },
|
||||
"rekor": { "verified": true, "logID": "...", "integratedTime": 1737480000 },
|
||||
"sbom": { "format": "cyclonedx-1.6", "canonicalDigest": "sha256:..." },
|
||||
"freshness": { "tstVerified": true, "timestamp": "2026-01-22T10:00:00Z", "maxAgeHours": 24 },
|
||||
"cvss": { "score": 7.5, "version": "3.1" },
|
||||
"reachability": { "status": "confirmed", "confidence": 0.85 },
|
||||
"confidence": 0.82
|
||||
}
|
||||
```
|
||||
|
||||
## Remediation Hints
|
||||
|
||||
When a gate blocks, the CLI displays actionable fix suggestions:
|
||||
|
||||
```
|
||||
Decision: BLOCK
|
||||
|
||||
Gate Type Result Reason
|
||||
signature SignatureRequiredGate FAIL Required signature missing
|
||||
sbom SbomPresenceGate PASS passed
|
||||
|
||||
Remediation:
|
||||
SIG_MISS: Required signature missing
|
||||
- Sign attestation with DSSE.
|
||||
$ stella attest attach --sign --image sha256:abc...
|
||||
- Anchor attestation in Rekor.
|
||||
$ stella attest attach --rekor --image sha256:abc...
|
||||
```
|
||||
|
||||
## Rego Import Behavior
|
||||
|
||||
When importing Rego files, the system:
|
||||
1. Parses `deny` rules and maps known patterns to native gates (CVSS comparisons, boolean checks).
|
||||
2. Extracts `remediation` rules into structured hints.
|
||||
3. Unknown patterns are preserved and evaluated via the embedded OPA evaluator.
|
||||
4. Validation reports which rules mapped natively vs. remain OPA-evaluated.
|
||||
|
||||
## Determinism
|
||||
|
||||
All evaluations are deterministic:
|
||||
- Same policy + same input = same output (hash-verifiable)
|
||||
- No time-dependent logic in deterministic mode
|
||||
- `outputDigest` in evaluation results enables replay verification
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The Platform API exposes policy interop at `/api/v1/policy/interop`:
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| POST | `/export` | Export policy to format |
|
||||
| POST | `/import` | Import policy from format |
|
||||
| POST | `/validate` | Validate policy document |
|
||||
| POST | `/evaluate` | Evaluate policy against input |
|
||||
| GET | `/formats` | List supported formats |
|
||||
|
||||
## Gate Types
|
||||
|
||||
Supported gate types with Rego translation:
|
||||
|
||||
| Gate Type | Rego Pattern | Remediation Code |
|
||||
|-----------|-------------|-----------------|
|
||||
| `CvssThresholdGate` | `input.cvss.score >= threshold` | `CVSS_EXCEED` |
|
||||
| `SignatureRequiredGate` | `not input.dsse.verified` | `SIG_MISS` |
|
||||
| `EvidenceFreshnessGate` | `not input.freshness.tstVerified` | `FRESH_EXPIRED` |
|
||||
| `SbomPresenceGate` | `not input.sbom.canonicalDigest` | `SBOM_MISS` |
|
||||
| `MinimumConfidenceGate` | `input.confidence < threshold` | `CONF_LOW` |
|
||||
| `UnknownsBudgetGate` | `input.unknownsRatio > threshold` | `UNK_EXCEED` |
|
||||
| `ReachabilityRequirementGate` | `not input.reachability.status` | `REACH_REQUIRED` |
|
||||
198
docs/modules/scanner/guides/runtime-linkage.md
Normal file
198
docs/modules/scanner/guides/runtime-linkage.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Runtime Linkage Verification Guide
|
||||
|
||||
> **Ownership:** Scanner Guild / Signals Guild
|
||||
> **Services:** `StellaOps.Scanner.Reachability.FunctionMap`
|
||||
> **API:** `POST /api/v1/function-maps`, `POST /api/v1/function-maps/{id}/verify`
|
||||
> **CLI:** `stella function-map generate|verify`, `stella observations query`
|
||||
|
||||
## What is Runtime Linkage Verification?
|
||||
|
||||
Runtime linkage verification bridges the gap between **static analysis** (what code _could_ run) and **runtime observation** (what code _actually_ runs). It works by:
|
||||
|
||||
1. **Generating a function map** from static analysis (SBOM + call graph) that declares expected call paths
|
||||
2. **Deploying probes** (eBPF uprobes/kprobes) to observe actual function invocations at runtime
|
||||
3. **Verifying** that observed call patterns match the expected static model
|
||||
|
||||
This produces a confidence metric (observation rate) quantifying how much of the declared attack surface has been confirmed by runtime evidence.
|
||||
|
||||
---
|
||||
|
||||
## When to Use Function Maps
|
||||
|
||||
| Scenario | Benefit |
|
||||
|----------|---------|
|
||||
| **High-risk vulnerabilities** | Confirm whether vulnerable code paths are actually exercised |
|
||||
| **Reachability disputes** | Resolve static "maybe reachable" findings with runtime evidence |
|
||||
| **Compliance audits** | Provide cryptographic proof of runtime behavior |
|
||||
| **Air-gapped environments** | Bundle function maps and observations for offline verification |
|
||||
| **Continuous monitoring** | Track coverage drift over deployment lifecycle |
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Guide
|
||||
|
||||
### 1. Generate a Function Map
|
||||
|
||||
Create a function map predicate from your SBOM and optional static analysis:
|
||||
|
||||
```bash
|
||||
stella function-map generate \
|
||||
--sbom ./app.cdx.json \
|
||||
--service my-backend \
|
||||
--hot-functions "crypto/*" --hot-functions "auth/*" \
|
||||
--min-rate 0.95 \
|
||||
--window 1800 \
|
||||
--output function-map.json
|
||||
```
|
||||
|
||||
**Key options:**
|
||||
- `--hot-functions`: Glob patterns for functions of interest (crypto, auth, network are common)
|
||||
- `--min-rate`: Minimum observation rate to consider "verified" (default 0.95 = 95%)
|
||||
- `--window`: Observation window in seconds (default 1800 = 30 minutes)
|
||||
- `--static-analysis`: Path to static analysis results for richer call paths
|
||||
|
||||
The output is a JSON predicate conforming to `https://stella.ops/predicates/function-map/v1`.
|
||||
|
||||
### 2. Deploy Probes
|
||||
|
||||
Configure the Stella runtime agent to attach probes for the functions declared in your map. The agent uses eBPF to observe function calls without modifying application code.
|
||||
|
||||
Supported probe types:
|
||||
- `uprobe` / `uretprobe` — User-space function entry/exit
|
||||
- `kprobe` / `kretprobe` — Kernel function entry/exit
|
||||
- `tracepoint` — Kernel tracepoints
|
||||
- `usdt` — User-space statically defined tracing
|
||||
|
||||
The runtime agent writes observations in NDJSON format with fields:
|
||||
- `node_hash` — SHA-256(PURL + normalized symbol)
|
||||
- `function_name` — Observed function symbol
|
||||
- `probe_type` — How it was observed
|
||||
- `observed_at` — Timestamp
|
||||
- `container_id`, `pod_name`, `namespace` — Context
|
||||
|
||||
### 3. Verify Observations Against the Map
|
||||
|
||||
After accumulating observations, verify coverage:
|
||||
|
||||
```bash
|
||||
stella function-map verify \
|
||||
--function-map function-map.json \
|
||||
--from "2026-01-23T00:00:00Z" \
|
||||
--to "2026-01-23T01:00:00Z" \
|
||||
--format table
|
||||
```
|
||||
|
||||
For offline verification with a bundled observations file:
|
||||
|
||||
```bash
|
||||
stella function-map verify \
|
||||
--function-map function-map.json \
|
||||
--offline \
|
||||
--observations observations.ndjson \
|
||||
--format json
|
||||
```
|
||||
|
||||
**Output includes:**
|
||||
- `verified`: Whether observation rate meets the threshold
|
||||
- `observation_rate`: Fraction of expected paths confirmed (0.0-1.0)
|
||||
- `target_rate`: Required rate from the function map
|
||||
- `per_path_breakdown`: Status of each declared call path
|
||||
- `unexpected_symbols`: Functions observed but not in the map
|
||||
- `missing_symbols`: Expected functions not yet observed
|
||||
|
||||
### 4. Upload to Platform (Optional)
|
||||
|
||||
Store function maps in the Platform for centralized management:
|
||||
|
||||
```bash
|
||||
# Create via API
|
||||
curl -X POST /api/v1/function-maps \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @function-map.json
|
||||
|
||||
# Verify via API
|
||||
curl -X POST /api/v1/function-maps/{id}/verify \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"observations": [...]}'
|
||||
|
||||
# Check coverage dashboard
|
||||
curl GET /api/v1/function-maps/{id}/coverage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Predicate Schema
|
||||
|
||||
Function maps use the in-toto attestation framework with predicate type:
|
||||
|
||||
```
|
||||
https://stella.ops/predicates/function-map/v1
|
||||
```
|
||||
|
||||
See [Function Map V1 Contract](../../../contracts/function-map-v1.md) for the full schema specification.
|
||||
|
||||
---
|
||||
|
||||
## Integration with Air-Gap Bundles
|
||||
|
||||
Function maps, observations, and verification reports can be included in offline bundles:
|
||||
|
||||
```
|
||||
bundle.stella.bundle.tgz
|
||||
├── function-maps/
|
||||
│ ├── {service}-function-map.json
|
||||
│ └── {service}-function-map.dsse.json
|
||||
├── observations/
|
||||
│ └── {date-label}-observations.ndjson
|
||||
└── verification/
|
||||
├── verification-report.json
|
||||
└── verification-report.dsse.json
|
||||
```
|
||||
|
||||
See [Offline Bundle Format](../../airgap/guides/offline-bundle-format.md) for artifact type details.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Low Observation Rate
|
||||
|
||||
**Symptom:** Verification reports `observation_rate < target_rate`.
|
||||
|
||||
**Causes:**
|
||||
- Observation window too short — increase `--window` or widen `--from`/`--to`
|
||||
- Probes not attached — check runtime agent logs for attachment failures
|
||||
- Application hasn't exercised the code paths — generate representative load
|
||||
- Binary stripped or ASLR — provide `--binary-path` hints in the function map
|
||||
|
||||
**Resolution:**
|
||||
1. Use `stella observations query --summary` to see what's been collected
|
||||
2. Check per-path breakdown for which specific paths are unobserved
|
||||
3. Extend the observation window or trigger relevant application behavior
|
||||
|
||||
### Unexpected Symbols
|
||||
|
||||
**Symptom:** Verification reports unexpected function calls not in the map.
|
||||
|
||||
**Causes:**
|
||||
- Dynamic dispatch or reflection invoking functions not in static analysis
|
||||
- Shared libraries loaded at runtime that weren't in the SBOM
|
||||
- Hot functions pattern too narrow
|
||||
|
||||
**Resolution:**
|
||||
1. Regenerate the function map with broader `--hot-functions` patterns
|
||||
2. Add the unexpected symbols as optional paths if they're benign
|
||||
3. Set `--fail-on-unexpected false` if unexpected calls should be informational only
|
||||
|
||||
### Node Hash Mismatch
|
||||
|
||||
**Symptom:** Observations exist but don't match expected node hashes.
|
||||
|
||||
**Causes:**
|
||||
- PURL mismatch between SBOM and runtime (version drift)
|
||||
- Symbol name normalization differences (C++ mangling, etc.)
|
||||
|
||||
**Resolution:**
|
||||
1. Verify the PURL in observations matches the function map subject
|
||||
2. Check that symbol names are normalized consistently (same demangling rules)
|
||||
3. Regenerate the function map with the current deployed SBOM version
|
||||
253
docs/modules/signals/unified-score.md
Normal file
253
docs/modules/signals/unified-score.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Unified Trust Score
|
||||
|
||||
> **Ownership:** Signals Guild / Platform Guild
|
||||
> **Services:** `StellaOps.Signals.UnifiedScore`
|
||||
> **API:** `POST /api/v1/score/evaluate`, `GET /api/v1/score/{id}/replay`
|
||||
> **CLI:** `stella score compute|explain|replay|verify`, `stella gate score evaluate`
|
||||
|
||||
## Overview
|
||||
|
||||
The Unified Trust Score is a facade over existing EWS (Evidence-Weighted Score) and Determinization systems. It provides a single API for computing risk scores, uncertainty metrics, and score replay proofs without replacing any underlying scoring logic.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Input** — Caller provides signal values (reachability, runtime, exploit, etc.) and optional context (CVE ID, PURL, SBOM ref)
|
||||
2. **EWS computation** — The facade delegates to `IEvidenceWeightedScoreCalculator` using weights from a versioned manifest
|
||||
3. **Entropy calculation** — `IUncertaintyScoreCalculator` computes the unknowns fraction (U) from signal presence/absence
|
||||
4. **Conflict detection** — `IConflictDetector` identifies contradictory signals
|
||||
5. **Delta calculation** — For missing signals, computes potential score impact ranges
|
||||
6. **Result assembly** — Returns `UnifiedScoreResult` combining all outputs
|
||||
|
||||
---
|
||||
|
||||
## The Unknowns Fraction (U)
|
||||
|
||||
The `UnknownsFraction` exposes how much of the score depends on absent data:
|
||||
|
||||
```
|
||||
U = 1 - (weighted_present_signals / total_weight)
|
||||
```
|
||||
|
||||
### Unknowns Bands
|
||||
|
||||
| U Range | Band | Meaning | Recommended Action |
|
||||
|---------|------|---------|-------------------|
|
||||
| 0.0 – 0.2 | **Complete** | All signals present | Automated decisions safe |
|
||||
| 0.2 – 0.4 | **Adequate** | Sufficient signal coverage | Automated decisions safe |
|
||||
| 0.4 – 0.6 | **Sparse** | Signal gaps exist | Manual review recommended |
|
||||
| 0.6 – 1.0 | **Insufficient** | Critical data missing | Block until more signals arrive |
|
||||
|
||||
Band thresholds align with Determinization configuration:
|
||||
- `RefreshEntropyThreshold: 0.40` — triggers signal refresh attempt
|
||||
- `ManualReviewEntropyThreshold: 0.60` — requires human review
|
||||
|
||||
---
|
||||
|
||||
## Delta-If-Present
|
||||
|
||||
When signals are absent, the facade calculates how the score would change if each missing signal were provided:
|
||||
|
||||
```json
|
||||
{
|
||||
"signal": "reachability",
|
||||
"min_impact": -15,
|
||||
"max_impact": 8,
|
||||
"weight": 0.30,
|
||||
"description": "If reachability confirmed as not-reachable, score decreases by up to 15"
|
||||
}
|
||||
```
|
||||
|
||||
This helps operators prioritize which signals to gather first.
|
||||
|
||||
---
|
||||
|
||||
## Weight Manifests
|
||||
|
||||
EWS weights are stored in versioned JSON files under `etc/weights/`:
|
||||
|
||||
```
|
||||
etc/weights/v2026-01-22.weights.json
|
||||
```
|
||||
|
||||
Manifests are:
|
||||
- **Immutable** once published
|
||||
- **Content-addressed** via SHA-256 hash
|
||||
- **Pinnable** by policy rules via `weights_ref`
|
||||
- **Auditable** — the manifest version and hash are included in every score result
|
||||
|
||||
See [Scoring Algebra §4](../../technical/scoring-algebra.md) for the manifest schema.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| `POST` | `/api/v1/score/evaluate` | Compute unified score |
|
||||
| `GET` | `/api/v1/score/{scoreId}` | Retrieve previously computed score |
|
||||
| `GET` | `/api/v1/score/weights` | List weight manifest versions |
|
||||
| `GET` | `/api/v1/score/weights/{version}` | Get specific manifest |
|
||||
| `GET` | `/api/v1/score/weights/effective` | Get effective manifest for a date |
|
||||
| `GET` | `/api/v1/score/{scoreId}/replay` | Fetch signed replay proof |
|
||||
| `POST` | `/api/v1/score/verify` | Verify a replay log |
|
||||
|
||||
### Evaluate Request
|
||||
|
||||
```json
|
||||
{
|
||||
"cve_id": "CVE-2024-1234",
|
||||
"purl": "pkg:npm/lodash@4.17.0",
|
||||
"signals": {
|
||||
"reachability": 0.9,
|
||||
"runtime": 0.7,
|
||||
"exploit": 0.3,
|
||||
"backport": 0.0,
|
||||
"source": 0.5,
|
||||
"mitigation": 0.0
|
||||
},
|
||||
"options": {
|
||||
"include_breakdown": true,
|
||||
"include_delta": true,
|
||||
"weight_set_id": "v2026-01-22"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Evaluate Response (key fields)
|
||||
|
||||
```json
|
||||
{
|
||||
"score_id": "score_a1b2c3d4e5f67890",
|
||||
"score_value": 72,
|
||||
"bucket": "ScheduleNext",
|
||||
"unknowns_fraction": 0.15,
|
||||
"unknowns_band": "Complete",
|
||||
"weight_manifest": {
|
||||
"version": "v2026-01-22",
|
||||
"content_hash": "sha256:..."
|
||||
},
|
||||
"ews_digest": "sha256:...",
|
||||
"determinization_fingerprint": "sha256:...",
|
||||
"computed_at": "2026-01-23T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
### `stella score compute`
|
||||
|
||||
Compute a unified score from signal values:
|
||||
|
||||
```bash
|
||||
stella score compute \
|
||||
--finding-id CVE-2024-1234@pkg:npm/lodash@4.17.0 \
|
||||
--cvss 7.5 --epss 0.15 \
|
||||
--reachability 0.9 --runtime 0.7 \
|
||||
--format table
|
||||
```
|
||||
|
||||
### `stella score explain`
|
||||
|
||||
Show a detailed breakdown of a score:
|
||||
|
||||
```bash
|
||||
stella score explain CVE-2024-1234@pkg:npm/lodash@4.17.0
|
||||
```
|
||||
|
||||
### `stella score replay`
|
||||
|
||||
Fetch the signed replay proof for a previously computed score:
|
||||
|
||||
```bash
|
||||
stella score replay score_a1b2c3d4e5f67890
|
||||
```
|
||||
|
||||
### `stella score verify`
|
||||
|
||||
Re-execute the computation and verify it matches the original:
|
||||
|
||||
```bash
|
||||
stella score verify score_a1b2c3d4e5f67890
|
||||
```
|
||||
|
||||
### `stella gate score evaluate` (enhanced)
|
||||
|
||||
Existing gate command with new flags:
|
||||
|
||||
```bash
|
||||
stella gate score evaluate \
|
||||
--finding-id CVE-2024-1234@pkg:npm/lodash \
|
||||
--cvss 7.5 --epss 0.15 \
|
||||
--show-unknowns --show-deltas \
|
||||
--weights-version v2026-01-22
|
||||
```
|
||||
|
||||
### `stella gate score weights`
|
||||
|
||||
Manage weight manifests:
|
||||
|
||||
```bash
|
||||
stella gate score weights list
|
||||
stella gate score weights show v2026-01-22
|
||||
stella gate score weights diff v2026-01-22 v2026-02-01
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Score Replay and Verification
|
||||
|
||||
Every computed score can produce a **replay proof** — a DSSE-signed attestation (payload type `application/vnd.stella.score+json`) that records:
|
||||
|
||||
1. Canonical input hashes (SBOM, VEX, etc.)
|
||||
2. Transform versions applied (canonicalization, normalization, decay)
|
||||
3. Step-by-step algebra decisions (signal × weight = contribution)
|
||||
4. Final score and metadata
|
||||
|
||||
Replay proofs enable:
|
||||
- **Independent verification** — auditors re-execute the computation
|
||||
- **Transparency logging** — optional anchoring to Rekor for non-repudiation
|
||||
- **OCI storage** — proofs stored as OCI referrers ("StellaBundle" pattern)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### High Unknowns Fraction (U > 0.6)
|
||||
|
||||
**Symptom:** Score shows "Insufficient" band, decisions are blocked.
|
||||
|
||||
**Causes:**
|
||||
- Missing reachability analysis (run `stella scan` with `--reachability`)
|
||||
- No VEX data available (check VEX feed configuration)
|
||||
- Runtime observations not collected (configure runtime agent)
|
||||
|
||||
**Resolution:**
|
||||
1. Run `stella score explain <finding-id>` to see which signals are missing
|
||||
2. Use `--show-deltas` to understand which signals would have the most impact
|
||||
3. Prioritize gathering signals with the highest weight × delta
|
||||
|
||||
### Score Disagrees with CVSS
|
||||
|
||||
**Symptom:** EWS score is much lower than expected from CVSS alone.
|
||||
|
||||
**Explanation:** EWS incorporates reachability, runtime, backport, and mitigation signals that CVSS does not. A high-CVSS vulnerability that is not reachable or already mitigated will have a lower EWS score.
|
||||
|
||||
**Resolution:** Run `stella score explain` to see the per-dimension breakdown and understand which signals are reducing the score.
|
||||
|
||||
### Replay Verification Fails
|
||||
|
||||
**Symptom:** `stella score verify` reports `score_matches: false`.
|
||||
|
||||
**Causes:**
|
||||
- Weight manifest version changed between compute and verify
|
||||
- Signal inputs were modified after scoring
|
||||
- Non-determinism in signal providers (check for time-dependent signals)
|
||||
|
||||
**Resolution:**
|
||||
1. Pin the weight manifest version in the verify request
|
||||
2. Ensure canonical inputs match (compare SHA-256 hashes)
|
||||
3. Check the `differences` field in the verify response for specific mismatches
|
||||
@@ -503,3 +503,181 @@ webhooks:
|
||||
- Health endpoints: `/health/liveness`, `/health/readiness`, `/status`, `/surface/fs/cache/status` (see runbook).
|
||||
- Alert hints: deny spikes, latency > 800ms p99, cache freshness lag > 10m, any secrets failure.
|
||||
|
||||
---
|
||||
|
||||
## 17) Offline Witness Verification
|
||||
|
||||
> **Sprint:** SPRINT_20260122_038_Scanner_ebpf_probe_type (EBPF-004)
|
||||
|
||||
This section documents the deterministic replay verification algorithm for runtime witnesses, enabling air-gapped environments to independently verify witness attestations.
|
||||
|
||||
### 17.1 Input Canonicalization (RFC 8785 JCS)
|
||||
|
||||
All witness payloads MUST be canonicalized before hashing or signing using **JSON Canonicalization Scheme (JCS)** per RFC 8785:
|
||||
|
||||
1. **Property ordering**: Object properties are sorted lexicographically by key name (Unicode code point order).
|
||||
2. **Number serialization**: Numbers are serialized without unnecessary precision; integers as integers, decimals with minimal representation.
|
||||
3. **String encoding**: UTF-8 with no BOM; escape sequences normalized to `\uXXXX` form for control characters.
|
||||
4. **Whitespace**: No insignificant whitespace between tokens.
|
||||
5. **Null handling**: Explicit `null` values are preserved; absent keys are omitted.
|
||||
|
||||
**Canonicalization algorithm:**
|
||||
|
||||
```
|
||||
function canonicalize(json_object):
|
||||
if json_object is null:
|
||||
return "null"
|
||||
if json_object is boolean:
|
||||
return "true" | "false"
|
||||
if json_object is number:
|
||||
return serialize_number(json_object) # RFC 8785 §3.2.2.3
|
||||
if json_object is string:
|
||||
return quote(escape(json_object))
|
||||
if json_object is array:
|
||||
return "[" + join(",", [canonicalize(elem) for elem in json_object]) + "]"
|
||||
if json_object is object:
|
||||
keys = sorted(json_object.keys(), key=unicode_codepoint_order)
|
||||
pairs = [quote(key) + ":" + canonicalize(json_object[key]) for key in keys]
|
||||
return "{" + join(",", pairs) + "}"
|
||||
```
|
||||
|
||||
### 17.2 Observation Ordering Rules
|
||||
|
||||
When a witness contains multiple observations (e.g., from eBPF probes), they MUST be ordered deterministically before hashing:
|
||||
|
||||
1. **Primary sort**: By `observedAt` timestamp (UTC, ascending)
|
||||
2. **Secondary sort**: By `nodeHash` (lexicographic ascending)
|
||||
3. **Tertiary sort**: By `observationId` (lexicographic ascending, for tie-breaking)
|
||||
|
||||
**Observation hash computation:**
|
||||
|
||||
```
|
||||
function compute_observations_hash(observations):
|
||||
sorted_observations = sort(observations,
|
||||
key=lambda o: (o.observedAt, o.nodeHash, o.observationId))
|
||||
|
||||
canonical_array = []
|
||||
for obs in sorted_observations:
|
||||
canonical_array.append({
|
||||
"observedAt": obs.observedAt.toISOString(),
|
||||
"nodeHash": obs.nodeHash,
|
||||
"functionName": obs.functionName,
|
||||
"probeType": obs.probeType, # EBPF-001: kprobe|uprobe|tracepoint|usdt|fentry|fexit
|
||||
"containerHash": sha256(obs.containerId + obs.podName + obs.namespace)
|
||||
})
|
||||
|
||||
return sha256(canonicalize(canonical_array))
|
||||
```
|
||||
|
||||
### 17.3 Signature Verification Sequence
|
||||
|
||||
Offline verification MUST follow this exact sequence to ensure deterministic results:
|
||||
|
||||
1. **Parse DSSE envelope**: Extract `payloadType`, `payload` (base64-decoded), and `signatures[]`.
|
||||
|
||||
2. **Verify payload hash**:
|
||||
```
|
||||
expected_hash = sha256(payload_bytes)
|
||||
assert envelope.payload_sha256 == expected_hash
|
||||
```
|
||||
|
||||
3. **Verify DSSE signature(s)**: For each signature in `signatures[]`:
|
||||
```
|
||||
pae_string = "DSSEv1 " + len(payloadType) + " " + payloadType + " " + len(payload) + " " + payload
|
||||
verify_signature(signature.sig, pae_string, get_public_key(signature.keyid))
|
||||
```
|
||||
|
||||
4. **Verify Rekor inclusion** (if present):
|
||||
```
|
||||
fetch_or_load_checkpoint(rekor_log_id)
|
||||
verify_merkle_inclusion(entry_hash, inclusion_proof, checkpoint.root_hash)
|
||||
verify_checkpoint_signature(checkpoint, rekor_public_key)
|
||||
```
|
||||
|
||||
5. **Verify timestamp** (if RFC 3161 TST present):
|
||||
```
|
||||
verify_tst_signature(tst, tsa_certificate)
|
||||
assert tst.timestamp <= now() + allowed_skew
|
||||
```
|
||||
|
||||
6. **Verify witness content**:
|
||||
```
|
||||
witness = parse_json(payload)
|
||||
recomputed_observations_hash = compute_observations_hash(witness.observations)
|
||||
assert witness.observationsDigest == recomputed_observations_hash
|
||||
```
|
||||
|
||||
### 17.4 Offline Bundle Structure Requirements
|
||||
|
||||
A StellaBundle for offline witness verification MUST include:
|
||||
|
||||
```
|
||||
bundle/
|
||||
├── manifest.json # Bundle manifest v2.0.0
|
||||
├── witnesses/
|
||||
│ └── <claim_id>.witness.dsse.json # DSSE-signed witness
|
||||
├── proofs/
|
||||
│ ├── rekor-inclusion.json # Rekor inclusion proof
|
||||
│ ├── checkpoint.json # Rekor checkpoint (signed)
|
||||
│ └── rfc3161-tst.der # Optional RFC 3161 timestamp
|
||||
├── observations/
|
||||
│ └── observations.ndjson # Raw observations (for replay)
|
||||
├── keys/
|
||||
│ ├── signing-key.pub # Public key for DSSE verification
|
||||
│ └── rekor-key.pub # Rekor log public key
|
||||
└── trust/
|
||||
└── trust-root.json # Trust anchors for key verification
|
||||
```
|
||||
|
||||
**Manifest schema (witnesses section):**
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": "2.0.0",
|
||||
"artifacts": [
|
||||
{
|
||||
"type": "witness",
|
||||
"path": "witnesses/<claim_id>.witness.dsse.json",
|
||||
"digest": "sha256:...",
|
||||
"predicateType": "https://stella.ops/predicates/runtime-witness/v1",
|
||||
"proofs": {
|
||||
"rekor": "proofs/rekor-inclusion.json",
|
||||
"checkpoint": "proofs/checkpoint.json",
|
||||
"tst": "proofs/rfc3161-tst.der"
|
||||
},
|
||||
"observationsRef": "observations/observations.ndjson"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 17.5 Verification CLI Commands
|
||||
|
||||
```bash
|
||||
# Verify a witness bundle offline
|
||||
stella bundle verify --bundle witness-bundle.tar.gz --offline
|
||||
|
||||
# Verify with replay (recompute observations hash)
|
||||
stella bundle verify --bundle witness-bundle.tar.gz --offline --replay
|
||||
|
||||
# Verify specific witness from bundle
|
||||
stella witness verify --bundle witness-bundle.tar.gz --witness-id wit:sha256:abc123 --offline
|
||||
|
||||
# Export verification report
|
||||
stella witness verify --bundle witness-bundle.tar.gz --offline --output report.json
|
||||
```
|
||||
|
||||
### 17.6 Determinism Guarantees
|
||||
|
||||
The verification algorithm guarantees:
|
||||
|
||||
1. **Idempotent**: Running verification N times produces identical results.
|
||||
2. **Reproducible**: Different systems with the same bundle produce identical verification outcomes.
|
||||
3. **Isolated**: Verification requires no network access (fully air-gapped).
|
||||
4. **Auditable**: Every step produces evidence that can be independently checked.
|
||||
|
||||
**Test criteria** (per advisory):
|
||||
- Offline verifier reproduces the same mapping on 3 separate air-gapped runs.
|
||||
- No randomness in canonicalization, ordering, or hash computation.
|
||||
- Timestamps use UTC with fixed precision (milliseconds).
|
||||
|
||||
|
||||
232
docs/runbooks/runtime-linkage-ops.md
Normal file
232
docs/runbooks/runtime-linkage-ops.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Runtime Linkage Verification - Operational Runbook
|
||||
|
||||
> **Audience:** Platform operators, SREs, security engineers
|
||||
> **Related:** [Runtime Linkage Guide](../modules/scanner/guides/runtime-linkage.md), [Function Map V1 Contract](../contracts/function-map-v1.md)
|
||||
|
||||
## Overview
|
||||
|
||||
This runbook covers production deployment and operation of the runtime linkage verification system. The system uses eBPF probes to observe function calls and verifies them against declared function maps.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Linux kernel 5.8+ (for eBPF CO-RE support)
|
||||
- `CAP_BPF` and `CAP_PERFMON` capabilities for the runtime agent
|
||||
- BTF (BPF Type Format) enabled in kernel config
|
||||
- Stella runtime agent deployed as a DaemonSet or sidecar
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Runtime Agent Configuration
|
||||
|
||||
The Stella runtime agent (`stella-runtime-agent`) attaches eBPF probes based on function map predicates. Configuration via environment or YAML:
|
||||
|
||||
```yaml
|
||||
runtime_agent:
|
||||
observation_store:
|
||||
type: "memory" # or "postgres", "valkey"
|
||||
retention_hours: 72
|
||||
max_batch_size: 1000
|
||||
probes:
|
||||
max_concurrent: 256
|
||||
attach_timeout_ms: 5000
|
||||
default_types: ["uprobe", "kprobe"]
|
||||
export:
|
||||
format: "ndjson"
|
||||
flush_interval_ms: 5000
|
||||
output_path: "/var/stella/observations/"
|
||||
```
|
||||
|
||||
### Probe Selection Guidance
|
||||
|
||||
| Category | Probe Type | Use Case |
|
||||
|----------|-----------|----------|
|
||||
| Crypto functions | `uprobe` | OpenSSL/BoringSSL/libsodium calls |
|
||||
| Network I/O | `kprobe` | connect/sendto/recvfrom syscalls |
|
||||
| Auth flows | `uprobe` | PAM/LDAP/OAuth library calls |
|
||||
| File access | `kprobe` | open/read/write on sensitive paths |
|
||||
| TLS handshake | `uprobe` | SSL_do_handshake, TLS negotiation |
|
||||
|
||||
**Prioritization:**
|
||||
1. Start with crypto and auth paths (highest security relevance)
|
||||
2. Add network I/O for service mesh verification
|
||||
3. Expand to file access for compliance requirements
|
||||
|
||||
### Resource Overhead
|
||||
|
||||
Expected overhead per probe:
|
||||
- CPU: ~0.1-0.5% per active uprobe (per-call overhead ~100ns)
|
||||
- Memory: ~2KB per attached probe + observation buffer
|
||||
- Disk: ~100 bytes per observation record (NDJSON)
|
||||
|
||||
**Recommended limits:**
|
||||
- Max 256 concurrent probes per node
|
||||
- Observation buffer: 64MB
|
||||
- Flush interval: 5 seconds
|
||||
- Retention: 72 hours (configurable)
|
||||
|
||||
---
|
||||
|
||||
## Operations
|
||||
|
||||
### Generating Function Maps
|
||||
|
||||
Run generation as part of CI/CD pipeline after SBOM generation:
|
||||
|
||||
```bash
|
||||
# In CI after SBOM generation
|
||||
stella function-map generate \
|
||||
--sbom ${BUILD_DIR}/sbom.cdx.json \
|
||||
--service ${SERVICE_NAME} \
|
||||
--hot-functions "crypto/*" --hot-functions "net/*" --hot-functions "auth/*" \
|
||||
--min-rate 0.95 \
|
||||
--window 1800 \
|
||||
--build-id ${CI_BUILD_ID} \
|
||||
--output ${BUILD_DIR}/function-map.json
|
||||
```
|
||||
|
||||
Store the function map alongside the container image (OCI referrer or artifact registry).
|
||||
|
||||
### Continuous Verification
|
||||
|
||||
Set up periodic verification (cron or controller loop):
|
||||
|
||||
```bash
|
||||
# Every 30 minutes, verify the last hour of observations
|
||||
stella function-map verify \
|
||||
--function-map /etc/stella/function-map.json \
|
||||
--from "$(date -d '1 hour ago' -Iseconds)" \
|
||||
--to "$(date -Iseconds)" \
|
||||
--format json --output /var/stella/verification/latest.json
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
|
||||
Key metrics to alert on:
|
||||
|
||||
| Metric | Threshold | Action |
|
||||
|--------|-----------|--------|
|
||||
| `observation_rate` | < 0.80 | Warning: coverage dropping |
|
||||
| `observation_rate` | < 0.50 | Critical: significant coverage loss |
|
||||
| `unexpected_symbols_count` | > 0 | Investigate: undeclared functions executing |
|
||||
| `probe_attach_failures` | > 5% | Warning: probe attachment issues |
|
||||
| `observation_buffer_full` | true | Critical: observations being dropped |
|
||||
|
||||
### Alert Configuration
|
||||
|
||||
```yaml
|
||||
alerts:
|
||||
- name: "function-map-coverage-low"
|
||||
condition: observation_rate < 0.80
|
||||
severity: warning
|
||||
description: "Function map coverage below 80% for {service}"
|
||||
runbook: "Check probe attachment, verify no binary update without map regeneration"
|
||||
|
||||
- name: "function-map-unexpected-calls"
|
||||
condition: unexpected_symbols_count > 0
|
||||
severity: info
|
||||
description: "Unexpected function calls detected in {service}"
|
||||
runbook: "Review unexpected symbols, regenerate function map if benign"
|
||||
|
||||
- name: "function-map-probe-failures"
|
||||
condition: probe_attach_failure_rate > 0.05
|
||||
severity: warning
|
||||
description: "Probe attachment failure rate above 5%"
|
||||
runbook: "Check kernel version, verify BTF availability, check CAP_BPF"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### High-Traffic Services
|
||||
|
||||
For services with >10K calls/second on probed functions:
|
||||
|
||||
1. **Sampling:** Configure observation sampling rate:
|
||||
```yaml
|
||||
probes:
|
||||
sampling_rate: 0.01 # 1% of calls
|
||||
```
|
||||
|
||||
2. **Aggregation:** Use count-based observations instead of per-call:
|
||||
```yaml
|
||||
export:
|
||||
aggregation_window_ms: 1000 # Aggregate per second
|
||||
```
|
||||
|
||||
3. **Selective probing:** Use `--hot-functions` to limit to critical paths only
|
||||
|
||||
### Large Function Maps
|
||||
|
||||
For maps with >100 expected paths:
|
||||
|
||||
1. Tag paths by priority: `crypto` > `auth` > `network` > `general`
|
||||
2. Mark low-priority paths as `optional: true`
|
||||
3. Set per-tag minimum rates if needed
|
||||
|
||||
### Storage Optimization
|
||||
|
||||
For long-term observation storage:
|
||||
|
||||
1. Enable retention pruning: `pruneOlderThanAsync(72h)`
|
||||
2. Compress archived observations (gzip NDJSON)
|
||||
3. Use dedicated Postgres partitions by date for query performance
|
||||
|
||||
---
|
||||
|
||||
## Incident Response
|
||||
|
||||
### Coverage Dropped After Deployment
|
||||
|
||||
1. Check if binary was updated without regenerating the function map
|
||||
2. Verify probes are still attached: `stella observations query --summary`
|
||||
3. Check for symbol changes (ASLR, different build)
|
||||
4. Regenerate function map from new SBOM and redeploy
|
||||
|
||||
### Unexpected Symbols Detected
|
||||
|
||||
1. Identify the unexpected functions from the verification report
|
||||
2. Determine if they are:
|
||||
- **Benign:** Dynamic dispatch, plugins, lazy-loaded libraries → add to map
|
||||
- **Suspicious:** Unexpected crypto usage, network calls → escalate to security team
|
||||
3. If benign, regenerate function map with broader patterns
|
||||
4. If suspicious, correlate with vulnerability findings and open incident
|
||||
|
||||
### Probe Attachment Failures
|
||||
|
||||
1. Check kernel version: `uname -r` (need 5.8+)
|
||||
2. Verify BTF: `ls /sys/kernel/btf/vmlinux`
|
||||
3. Check capabilities: `capsh --print | grep bpf`
|
||||
4. Check binary paths: verify `binary_path` in function map matches deployed binary
|
||||
5. Check for SELinux/AppArmor blocking BPF operations
|
||||
|
||||
---
|
||||
|
||||
## Air-Gap Considerations
|
||||
|
||||
For air-gapped environments:
|
||||
|
||||
1. **Bundle generation** (connected side):
|
||||
```bash
|
||||
stella function-map generate --sbom app.cdx.json --service my-service --output fm.json
|
||||
# Package with observations
|
||||
tar czf linkage-bundle.tgz fm.json observations/*.ndjson
|
||||
```
|
||||
|
||||
2. **Transfer** via approved media to air-gapped environment
|
||||
|
||||
3. **Offline verification** (air-gapped side):
|
||||
```bash
|
||||
stella function-map verify --function-map fm.json --offline --observations obs.ndjson
|
||||
```
|
||||
|
||||
4. **Result export** for compliance reporting:
|
||||
```bash
|
||||
stella function-map verify ... --format json --output report.json
|
||||
# Sign the report
|
||||
stella attest sign --input report.json --output report.dsse.json
|
||||
```
|
||||
BIN
docs/samples/evidence-bundle/evidence-bundle-m0.tar.gz
Normal file
BIN
docs/samples/evidence-bundle/evidence-bundle-m0.tar.gz
Normal file
Binary file not shown.
10
docs/samples/evidence-bundle/manifest.json
Normal file
10
docs/samples/evidence-bundle/manifest.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"bundle_id": "evidence-bundle-m0",
|
||||
"version": "1.0.0",
|
||||
"tenant": "demo",
|
||||
"scope": "vex",
|
||||
"aoc": {
|
||||
"guardrails": true,
|
||||
"details": ["schema:frozen:1.0"]
|
||||
}
|
||||
}
|
||||
3
docs/samples/evidence-bundle/transparency.json
Normal file
3
docs/samples/evidence-bundle/transparency.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"skip_reason": "offline"
|
||||
}
|
||||
285
docs/schemas/function-map-v1.schema.json
Normal file
285
docs/schemas/function-map-v1.schema.json
Normal file
@@ -0,0 +1,285 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://stellaops.org/schemas/function-map-v1.schema.json",
|
||||
"title": "StellaOps Function Map v1",
|
||||
"description": "Predicate schema for declaring expected call-paths for runtime→static linkage verification",
|
||||
"type": "object",
|
||||
"required": ["_type", "subject", "predicate"],
|
||||
"properties": {
|
||||
"_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"https://stella.ops/predicates/function-map/v1",
|
||||
"stella.ops/functionMap@v1"
|
||||
],
|
||||
"description": "Predicate type URI"
|
||||
},
|
||||
"subject": {
|
||||
"$ref": "#/definitions/subject",
|
||||
"description": "Subject artifact that this function map applies to"
|
||||
},
|
||||
"predicate": {
|
||||
"$ref": "#/definitions/predicatePayload",
|
||||
"description": "The predicate payload containing the function map definition"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"subject": {
|
||||
"type": "object",
|
||||
"required": ["purl", "digest"],
|
||||
"properties": {
|
||||
"purl": {
|
||||
"type": "string",
|
||||
"description": "Package URL of the subject artifact",
|
||||
"pattern": "^pkg:[a-z]+/.+"
|
||||
},
|
||||
"digest": {
|
||||
"type": "object",
|
||||
"description": "Digest(s) of the subject artifact",
|
||||
"additionalProperties": { "type": "string" },
|
||||
"minProperties": 1
|
||||
},
|
||||
"name": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional artifact name"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"predicatePayload": {
|
||||
"type": "object",
|
||||
"required": ["schemaVersion", "service", "expectedPaths", "coverage", "generatedAt"],
|
||||
"properties": {
|
||||
"schemaVersion": {
|
||||
"type": "string",
|
||||
"const": "1.0.0",
|
||||
"description": "Schema version of this predicate"
|
||||
},
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "Service name that this function map applies to",
|
||||
"minLength": 1
|
||||
},
|
||||
"buildId": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Build ID or version of the service"
|
||||
},
|
||||
"generatedFrom": {
|
||||
"$ref": "#/definitions/generatedFrom",
|
||||
"description": "References to source materials used to generate this function map"
|
||||
},
|
||||
"expectedPaths": {
|
||||
"type": "array",
|
||||
"description": "Expected call-paths that should be observed at runtime",
|
||||
"items": { "$ref": "#/definitions/expectedPath" },
|
||||
"minItems": 1
|
||||
},
|
||||
"coverage": {
|
||||
"$ref": "#/definitions/coverageThresholds",
|
||||
"description": "Coverage thresholds for verification"
|
||||
},
|
||||
"generatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "When this function map was generated"
|
||||
},
|
||||
"generator": {
|
||||
"$ref": "#/definitions/generatorInfo",
|
||||
"description": "Optional generator tool information"
|
||||
},
|
||||
"metadata": {
|
||||
"type": ["object", "null"],
|
||||
"description": "Optional metadata for extensions",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"generatedFrom": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"sbomRef": {
|
||||
"type": ["string", "null"],
|
||||
"description": "SHA256 digest of the SBOM used"
|
||||
},
|
||||
"staticAnalysisRef": {
|
||||
"type": ["string", "null"],
|
||||
"description": "SHA256 digest of the static analysis results used"
|
||||
},
|
||||
"binaryAnalysisRef": {
|
||||
"type": ["string", "null"],
|
||||
"description": "SHA256 digest of the binary analysis results used"
|
||||
},
|
||||
"hotFunctionPatterns": {
|
||||
"type": ["array", "null"],
|
||||
"description": "Hot function patterns used for filtering",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"expectedPath": {
|
||||
"type": "object",
|
||||
"required": ["pathId", "entrypoint", "expectedCalls", "pathHash"],
|
||||
"properties": {
|
||||
"pathId": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for this path within the function map",
|
||||
"minLength": 1
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Human-readable description of this call path"
|
||||
},
|
||||
"entrypoint": {
|
||||
"$ref": "#/definitions/pathEntrypoint",
|
||||
"description": "Entrypoint function that initiates this call path"
|
||||
},
|
||||
"expectedCalls": {
|
||||
"type": "array",
|
||||
"description": "Expected function calls within this path",
|
||||
"items": { "$ref": "#/definitions/expectedCall" },
|
||||
"minItems": 1
|
||||
},
|
||||
"pathHash": {
|
||||
"type": "string",
|
||||
"description": "Hash of the canonical path representation",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||
},
|
||||
"optional": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether this entire path is optional"
|
||||
},
|
||||
"strictOrdering": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether strict ordering of expected calls should be verified"
|
||||
},
|
||||
"tags": {
|
||||
"type": ["array", "null"],
|
||||
"description": "Optional tags for categorizing paths",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"pathEntrypoint": {
|
||||
"type": "object",
|
||||
"required": ["symbol", "nodeHash"],
|
||||
"properties": {
|
||||
"symbol": {
|
||||
"type": "string",
|
||||
"description": "Symbol name of the entrypoint function",
|
||||
"minLength": 1
|
||||
},
|
||||
"nodeHash": {
|
||||
"type": "string",
|
||||
"description": "Node hash for this entrypoint (PURL + normalized symbol)",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||
},
|
||||
"purl": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional PURL of the component containing this entrypoint"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"expectedCall": {
|
||||
"type": "object",
|
||||
"required": ["symbol", "purl", "nodeHash", "probeTypes"],
|
||||
"properties": {
|
||||
"symbol": {
|
||||
"type": "string",
|
||||
"description": "Symbol name of the expected function call",
|
||||
"minLength": 1
|
||||
},
|
||||
"purl": {
|
||||
"type": "string",
|
||||
"description": "Package URL (PURL) of the component containing this function",
|
||||
"pattern": "^pkg:[a-z]+/.+"
|
||||
},
|
||||
"nodeHash": {
|
||||
"type": "string",
|
||||
"description": "Node hash for this function (PURL + normalized symbol)",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||
},
|
||||
"probeTypes": {
|
||||
"type": "array",
|
||||
"description": "Acceptable probe types for observing this function",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["kprobe", "kretprobe", "uprobe", "uretprobe", "tracepoint", "usdt"]
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"optional": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether this function call is optional"
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional human-readable description"
|
||||
},
|
||||
"functionAddress": {
|
||||
"type": ["integer", "null"],
|
||||
"description": "Optional function address hint for performance optimization"
|
||||
},
|
||||
"binaryPath": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional binary path where this function is located"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"coverageThresholds": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minObservationRate": {
|
||||
"type": "number",
|
||||
"minimum": 0.0,
|
||||
"maximum": 1.0,
|
||||
"default": 0.95,
|
||||
"description": "Minimum observation rate required for verification to pass"
|
||||
},
|
||||
"windowSeconds": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"default": 1800,
|
||||
"description": "Observation window in seconds"
|
||||
},
|
||||
"minObservationCount": {
|
||||
"type": ["integer", "null"],
|
||||
"minimum": 1,
|
||||
"description": "Minimum number of observations required before verification can succeed"
|
||||
},
|
||||
"failOnUnexpected": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to fail on unexpected symbols (not in the function map)"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"generatorInfo": {
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Name of the generator tool"
|
||||
},
|
||||
"version": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Version of the generator tool"
|
||||
},
|
||||
"commit": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Optional commit hash of the generator tool"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
273
docs/schemas/policy-pack-v2.schema.json
Normal file
273
docs/schemas/policy-pack-v2.schema.json
Normal file
@@ -0,0 +1,273 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://stella-ops.org/schemas/policy-pack-v2.schema.json",
|
||||
"title": "Stella Ops PolicyPack v2",
|
||||
"description": "Canonical policy pack format supporting bidirectional JSON/Rego interop with structured remediation hints.",
|
||||
"type": "object",
|
||||
"required": ["apiVersion", "kind", "metadata", "spec"],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"type": "string",
|
||||
"const": "policy.stellaops.io/v2",
|
||||
"description": "Schema version identifier."
|
||||
},
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": ["PolicyPack", "PolicyOverride"],
|
||||
"description": "Document kind."
|
||||
},
|
||||
"metadata": { "$ref": "#/$defs/PolicyPackMetadata" },
|
||||
"spec": { "$ref": "#/$defs/PolicyPackSpec" }
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"$defs": {
|
||||
"PolicyPackMetadata": {
|
||||
"type": "object",
|
||||
"required": ["name", "version"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
|
||||
"description": "Unique name (DNS-label format)."
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+",
|
||||
"description": "Semantic version."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 500,
|
||||
"description": "Human-readable description."
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$",
|
||||
"description": "SHA-256 digest of canonical content."
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Creation timestamp (ISO 8601 UTC)."
|
||||
},
|
||||
"exportedFrom": { "$ref": "#/$defs/PolicyExportProvenance" },
|
||||
"parent": {
|
||||
"type": "string",
|
||||
"description": "Parent policy pack name (for PolicyOverride)."
|
||||
},
|
||||
"environment": {
|
||||
"type": "string",
|
||||
"description": "Target environment (for PolicyOverride)."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PolicyExportProvenance": {
|
||||
"type": "object",
|
||||
"required": ["engine", "engineVersion"],
|
||||
"properties": {
|
||||
"engine": {
|
||||
"type": "string",
|
||||
"description": "Exporting engine name."
|
||||
},
|
||||
"engineVersion": {
|
||||
"type": "string",
|
||||
"description": "Engine version."
|
||||
},
|
||||
"exportedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Export timestamp."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PolicyPackSpec": {
|
||||
"type": "object",
|
||||
"required": ["settings"],
|
||||
"properties": {
|
||||
"settings": { "$ref": "#/$defs/PolicyPackSettings" },
|
||||
"gates": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/PolicyGateDefinition" },
|
||||
"description": "Gate definitions with typed configurations."
|
||||
},
|
||||
"rules": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/PolicyRuleDefinition" },
|
||||
"description": "Rule definitions with match conditions."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PolicyPackSettings": {
|
||||
"type": "object",
|
||||
"required": ["defaultAction"],
|
||||
"properties": {
|
||||
"defaultAction": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "warn", "block"],
|
||||
"description": "Default action when no rule matches."
|
||||
},
|
||||
"unknownsThreshold": {
|
||||
"type": "number",
|
||||
"minimum": 0.0,
|
||||
"maximum": 1.0,
|
||||
"default": 0.6,
|
||||
"description": "Threshold for unknowns budget."
|
||||
},
|
||||
"stopOnFirstFailure": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Stop evaluation on first failure."
|
||||
},
|
||||
"deterministicMode": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enforce deterministic evaluation."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PolicyGateDefinition": {
|
||||
"type": "object",
|
||||
"required": ["id", "type"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
|
||||
"description": "Unique gate identifier."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Gate type (C# gate class name)."
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether this gate is active."
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "Gate-specific configuration.",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"environments": {
|
||||
"type": "object",
|
||||
"description": "Per-environment config overrides.",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"remediation": { "$ref": "#/$defs/RemediationHint" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PolicyRuleDefinition": {
|
||||
"type": "object",
|
||||
"required": ["name", "action"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
|
||||
"description": "Unique rule name."
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["allow", "warn", "block"],
|
||||
"description": "Action when matched."
|
||||
},
|
||||
"priority": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 0,
|
||||
"description": "Evaluation priority (lower = first)."
|
||||
},
|
||||
"match": {
|
||||
"type": "object",
|
||||
"description": "Match conditions (dot-notation keys, typed values).",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"remediation": { "$ref": "#/$defs/RemediationHint" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RemediationHint": {
|
||||
"type": "object",
|
||||
"required": ["code", "title", "severity"],
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Z][A-Z0-9_]{1,30}$",
|
||||
"description": "Machine-readable remediation code."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 200,
|
||||
"description": "Human-readable title."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 1000,
|
||||
"description": "Detailed explanation."
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/RemediationAction" },
|
||||
"description": "Ordered remediation actions."
|
||||
},
|
||||
"references": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/RemediationReference" },
|
||||
"description": "External references."
|
||||
},
|
||||
"severity": {
|
||||
"type": "string",
|
||||
"enum": ["critical", "high", "medium", "low"],
|
||||
"description": "Issue severity."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RemediationAction": {
|
||||
"type": "object",
|
||||
"required": ["type", "description"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["upgrade", "patch", "vex", "sign", "anchor", "generate", "override", "investigate", "mitigate"],
|
||||
"description": "Action type."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 500,
|
||||
"description": "What this action does."
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"maxLength": 500,
|
||||
"description": "CLI command template with {placeholders}."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"RemediationReference": {
|
||||
"type": "object",
|
||||
"required": ["title", "url"],
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 200,
|
||||
"description": "Display title."
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "Reference URL."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,16 @@
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"description": "Additional metadata"
|
||||
},
|
||||
"sbomDigest": {
|
||||
"type": "string",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$",
|
||||
"description": "SHA-256 digest of the associated SBOM document"
|
||||
},
|
||||
"largeBlobs": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/largeBlobReference" },
|
||||
"description": "References to large binary blobs stored out-of-band (by digest)"
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
@@ -346,6 +356,31 @@
|
||||
"description": "Total size of IR diffs stored in CAS"
|
||||
}
|
||||
}
|
||||
},
|
||||
"largeBlobReference": {
|
||||
"type": "object",
|
||||
"required": ["kind", "digest"],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": ["preBinary", "postBinary", "debugSymbols", "irDiff"],
|
||||
"description": "Blob kind: preBinary, postBinary, debugSymbols, etc."
|
||||
},
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$",
|
||||
"description": "Content-addressable digest (e.g., sha256:abc123...)"
|
||||
},
|
||||
"mediaType": {
|
||||
"type": "string",
|
||||
"description": "Media type of the blob"
|
||||
},
|
||||
"sizeBytes": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Size in bytes (for transfer planning)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user