docs(ops): Complete operations runbooks for Epic 3500
Sprint 3500.0004.0004 (Documentation & Handoff) - T2 DONE Operations Runbooks Added: - score-replay-runbook.md: Deterministic replay procedures - proof-verification-runbook.md: DSSE/Merkle verification ops - airgap-operations-runbook.md: Offline kit management CLI Reference Docs: - reachability-cli-reference.md - score-proofs-cli-reference.md - unknowns-cli-reference.md Air-Gap Guides: - score-proofs-reachability-airgap-runbook.md Training Materials: - score-proofs-concept-guide.md UI API Clients: - proof.client.ts - reachability.client.ts - unknowns.client.ts All 5 operations runbooks now complete (reachability, unknowns-queue, score-replay, proof-verification, airgap-operations).
This commit is contained in:
@@ -222,6 +222,206 @@ LS --> IA: PoE (mTLS client cert or JWT with cnf=K_inst), CRL/OCSP/introspect
|
||||
|
||||
---
|
||||
|
||||
## 4A) Score Proofs & Deterministic Replay
|
||||
|
||||
### 4A.1 Overview
|
||||
|
||||
Score Proofs provide cryptographically verifiable audit trails for every scoring decision. They enable:
|
||||
|
||||
* **Deterministic replay**: Same inputs → same outputs, every time
|
||||
* **Audit compliance**: Full traceability from inputs to final scores
|
||||
* **Offline verification**: Proof bundles verifiable without network access
|
||||
* **Feed updates**: Re-score historical scans with new advisories
|
||||
|
||||
### 4A.2 Scan Manifest
|
||||
|
||||
Every scan captures its inputs deterministically:
|
||||
|
||||
```json
|
||||
{
|
||||
"scanId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"createdAtUtc": "2025-12-17T12:00:00Z",
|
||||
"artifactDigest": "sha256:abc123...",
|
||||
"artifactPurl": "pkg:oci/myapp@sha256:abc123...",
|
||||
"scannerVersion": "1.0.0",
|
||||
"workerVersion": "1.0.0",
|
||||
"concelierSnapshotHash": "sha256:feed123...",
|
||||
"excititorSnapshotHash": "sha256:vex456...",
|
||||
"latticePolicyHash": "sha256:policy789...",
|
||||
"deterministic": true,
|
||||
"seed": "AQIDBA==",
|
||||
"knobs": {"maxDepth": "10"}
|
||||
}
|
||||
```
|
||||
|
||||
### 4A.3 Proof Ledger (DAG)
|
||||
|
||||
Scoring computation is recorded as a directed acyclic graph of `ProofNode`:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `id` | Node identifier |
|
||||
| `kind` | `Input`, `Transform`, `Delta`, `Score` |
|
||||
| `ruleId` | Policy rule that produced this node |
|
||||
| `parentIds` | Nodes this depends on |
|
||||
| `evidenceRefs` | Links to supporting evidence |
|
||||
| `delta` | Score contribution at this step |
|
||||
| `total` | Cumulative score |
|
||||
| `nodeHash` | Content-addressed hash |
|
||||
|
||||
The **proof root hash** is computed by hashing all leaf nodes' hashes in deterministic order.
|
||||
|
||||
### 4A.4 Score Replay API
|
||||
|
||||
```
|
||||
POST /api/v1/scanner/scans/{id}/score/replay
|
||||
{ overrides?: { concelierSnapshotHash?, excititorSnapshotHash?, latticePolicyHash? } }
|
||||
→ { scoreProof, rootHash, proofBundleUri }
|
||||
```
|
||||
|
||||
Use cases:
|
||||
* **Feed updates**: Re-score when Concelier publishes new advisories
|
||||
* **Policy changes**: See impact of policy modifications
|
||||
* **Audit**: Reproduce historical scores for compliance
|
||||
|
||||
### 4A.5 Proof Bundle Format
|
||||
|
||||
Proof bundles are self-contained ZIP archives:
|
||||
|
||||
```
|
||||
proof-bundle.zip/
|
||||
├── manifest.json # Canonical scan manifest
|
||||
├── manifest.dsse.json # DSSE signature
|
||||
├── score_proof.json # ProofNode[] array
|
||||
├── proof_root.dsse.json # DSSE of proof root
|
||||
└── meta.json # Timestamps, versions
|
||||
```
|
||||
|
||||
**Storage**: `scanner.proof_bundle` table + RustFS for bundle files.
|
||||
|
||||
---
|
||||
|
||||
## 4B) Reachability Analysis
|
||||
|
||||
### 4B.1 Overview
|
||||
|
||||
Reachability Analysis determines whether vulnerable code is actually reachable from application entrypoints, reducing false positives by filtering unreachable vulnerabilities.
|
||||
|
||||
### 4B.2 Call Graph Ingestion
|
||||
|
||||
Language-specific workers extract call graphs:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "stella.callgraph.v1",
|
||||
"language": "dotnet",
|
||||
"nodes": [...], // Function definitions
|
||||
"edges": [...], // Call relationships
|
||||
"entrypoints": [...] // HTTP routes, gRPC, etc.
|
||||
}
|
||||
```
|
||||
|
||||
**Supported languages**: .NET, Java, Node.js, Python, Go, Rust
|
||||
|
||||
### 4B.3 Reachability Statuses
|
||||
|
||||
| Status | Confidence | Description |
|
||||
|--------|------------|-------------|
|
||||
| `UNREACHABLE` | High | No path from entrypoints to vulnerable code |
|
||||
| `POSSIBLY_REACHABLE` | Medium | Path exists with heuristic edges |
|
||||
| `REACHABLE_STATIC` | High | Static analysis proves path exists |
|
||||
| `REACHABLE_PROVEN` | Very High | Runtime evidence confirms |
|
||||
| `UNKNOWN` | Low | Insufficient data |
|
||||
|
||||
### 4B.4 Reachability API
|
||||
|
||||
```
|
||||
POST /api/v1/scanner/scans/{id}/callgraphs
|
||||
CallGraph → { callGraphDigest, status }
|
||||
|
||||
POST /api/v1/scanner/scans/{id}/reachability/compute
|
||||
→ { jobId, status }
|
||||
|
||||
GET /api/v1/scanner/scans/{id}/reachability/findings
|
||||
→ { findings[], summary }
|
||||
|
||||
GET /api/v1/scanner/scans/{id}/reachability/explain?cve=...&purl=...
|
||||
→ { status, confidence, shortestPath[], whyReachable[] }
|
||||
```
|
||||
|
||||
### 4B.5 Integration with Score Proofs
|
||||
|
||||
Reachability evidence is included in proof bundles:
|
||||
* Reachability status per CVE/PURL is a scoring input
|
||||
* Path evidence is referenced in proof nodes
|
||||
* Graph attestations (DSSE) link to score proofs
|
||||
|
||||
**Storage**: `scanner.cg_node`, `scanner.cg_edge`, `scanner.entrypoint` tables.
|
||||
|
||||
---
|
||||
|
||||
## 4C) Unknowns Registry
|
||||
|
||||
### 4C.1 Overview
|
||||
|
||||
The Unknowns Registry tracks items that could not be fully classified due to missing evidence, enabling prioritized triage.
|
||||
|
||||
### 4C.2 Unknown Reasons
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| `missing_vex` | No VEX statement for vulnerability |
|
||||
| `ambiguous_indirect_call` | Indirect call target unresolved |
|
||||
| `incomplete_sbom` | SBOM missing component data |
|
||||
| `missing_advisory` | No advisory data for CVE |
|
||||
| `conflicting_evidence` | Multiple conflicting data sources |
|
||||
|
||||
### 4C.3 2-Factor Ranking Model
|
||||
|
||||
Unknowns are ranked by:
|
||||
|
||||
```
|
||||
score = 0.60 × blast + 0.30 × scarcity + 0.30 × pressure + containment_deduction
|
||||
```
|
||||
|
||||
| Factor | Weight | Components |
|
||||
|--------|--------|------------|
|
||||
| Blast Radius | 0.60 | Dependents, network exposure, privilege |
|
||||
| Evidence Scarcity | 0.30 | Missing data severity |
|
||||
| Exploit Pressure | 0.30 | EPSS, KEV status |
|
||||
| Containment | -0.20 | Seccomp, read-only FS |
|
||||
|
||||
### 4C.4 Band Assignment
|
||||
|
||||
| Band | Score Range | SLA |
|
||||
|------|-------------|-----|
|
||||
| HOT | ≥ 0.70 | 24 hours |
|
||||
| WARM | 0.40 - 0.69 | 7 days |
|
||||
| COLD | < 0.40 | 30 days |
|
||||
|
||||
### 4C.5 Unknowns API
|
||||
|
||||
```
|
||||
GET /api/v1/unknowns
|
||||
?band=HOT&sort=score → { items[], pagination }
|
||||
|
||||
GET /api/v1/unknowns/{id}
|
||||
→ { id, reasons, blastRadius, score, scoreBreakdown }
|
||||
|
||||
GET /api/v1/unknowns/{id}/proof
|
||||
→ { nodes[], rootHash }
|
||||
|
||||
POST /api/v1/unknowns/{id}/escalate
|
||||
→ { rescanJobId, status }
|
||||
|
||||
POST /api/v1/unknowns/{id}/resolve
|
||||
{ resolution, justification } → { resolvedAt }
|
||||
```
|
||||
|
||||
**Storage**: `policy.unknowns` table with ranking metadata.
|
||||
|
||||
---
|
||||
|
||||
## 5) Runtime enforcement (Zastava)
|
||||
|
||||
* **Observer:** inventories running containers, checks image signatures, SBOM presence (referrers), detects drift (entrypoint chain divergence), flags unapproved images.
|
||||
|
||||
Reference in New Issue
Block a user