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.
|
||||
|
||||
616
docs/airgap/score-proofs-reachability-airgap-runbook.md
Normal file
616
docs/airgap/score-proofs-reachability-airgap-runbook.md
Normal file
@@ -0,0 +1,616 @@
|
||||
# Air-Gap Operations Runbook for Score Proofs & Reachability
|
||||
|
||||
> **Version**: 1.0.0
|
||||
> **Sprint**: 3500.0004.0004
|
||||
> **Last Updated**: 2025-12-20
|
||||
|
||||
This runbook covers air-gapped operations for Score Proofs and Reachability features, including offline kit deployment, proof verification, and bundle management.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#1-overview)
|
||||
2. [Offline Kit Deployment](#2-offline-kit-deployment)
|
||||
3. [Score Proofs in Air-Gap Mode](#3-score-proofs-in-air-gap-mode)
|
||||
4. [Reachability in Air-Gap Mode](#4-reachability-in-air-gap-mode)
|
||||
5. [Bundle Import Operations](#5-bundle-import-operations)
|
||||
6. [Proof Verification Offline](#6-proof-verification-offline)
|
||||
7. [Troubleshooting](#7-troubleshooting)
|
||||
8. [Monitoring & Alerting](#8-monitoring--alerting)
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### Air-Gap Modes
|
||||
|
||||
| Mode | Network | Use Case |
|
||||
|------|---------|----------|
|
||||
| **Sealed** | No external connectivity | Classified environments |
|
||||
| **Constrained** | Limited egress (allowlist) | Regulated networks |
|
||||
| **Hybrid** | Selective connectivity | Standard enterprise |
|
||||
|
||||
### Score Proofs Air-Gap Capabilities
|
||||
|
||||
| Feature | Sealed | Constrained | Hybrid |
|
||||
|---------|--------|-------------|--------|
|
||||
| Score computation | ✅ | ✅ | ✅ |
|
||||
| Score replay | ✅ | ✅ | ✅ |
|
||||
| Proof generation | ✅ | ✅ | ✅ |
|
||||
| Proof verification | ✅ | ✅ | ✅ |
|
||||
| Rekor logging | ❌ | 🔶 (optional) | ✅ |
|
||||
| Feed updates | Bundle | Bundle | Online |
|
||||
|
||||
### Reachability Air-Gap Capabilities
|
||||
|
||||
| Feature | Sealed | Constrained | Hybrid |
|
||||
|---------|--------|-------------|--------|
|
||||
| Call graph upload | ✅ | ✅ | ✅ |
|
||||
| Reachability compute | ✅ | ✅ | ✅ |
|
||||
| Explain queries | ✅ | ✅ | ✅ |
|
||||
| Symbol resolution | Bundle | Bundle | Online |
|
||||
|
||||
---
|
||||
|
||||
## 2. Offline Kit Deployment
|
||||
|
||||
### 2.1 Offline Kit Contents
|
||||
|
||||
The offline kit contains everything needed for air-gapped Score Proofs and Reachability:
|
||||
|
||||
```
|
||||
offline-kit/
|
||||
├── manifests/
|
||||
│ ├── kit-manifest.json # Kit metadata and versions
|
||||
│ ├── feed-manifest.json # Advisory feed snapshot
|
||||
│ └── vex-manifest.json # VEX data snapshot
|
||||
├── feeds/
|
||||
│ ├── concelier/ # Advisory feed data
|
||||
│ │ ├── advisories.ndjson
|
||||
│ │ └── snapshot.dsse.json
|
||||
│ └── excititor/ # VEX data
|
||||
│ ├── vex-statements.ndjson
|
||||
│ └── snapshot.dsse.json
|
||||
├── policies/
|
||||
│ ├── scoring-policy.yaml
|
||||
│ └── policy.dsse.json
|
||||
├── trust/
|
||||
│ ├── trust-anchors.json # Public keys for verification
|
||||
│ └── time-anchor.json # Time attestation
|
||||
├── symbols/
|
||||
│ └── symbol-index.db # Symbol resolution database
|
||||
└── tools/
|
||||
├── stella-cli # CLI binary
|
||||
└── verify-kit.sh # Kit verification script
|
||||
```
|
||||
|
||||
### 2.2 Verify Kit Integrity
|
||||
|
||||
Before deployment, always verify the offline kit:
|
||||
|
||||
```bash
|
||||
# Verify kit signature
|
||||
stella airgap verify-kit --kit /path/to/offline-kit
|
||||
|
||||
# Output:
|
||||
# Kit manifest: VALID
|
||||
# Feed snapshot: VALID (sha256:feed123...)
|
||||
# VEX snapshot: VALID (sha256:vex456...)
|
||||
# Policy: VALID (sha256:policy789...)
|
||||
# Trust anchors: VALID (3 anchors)
|
||||
# Time anchor: VALID (expires: 2025-12-31T00:00:00Z)
|
||||
#
|
||||
# Kit verification: PASSED
|
||||
|
||||
# Verify individual components
|
||||
stella airgap verify --file feeds/concelier/snapshot.dsse.json
|
||||
stella airgap verify --file feeds/excititor/snapshot.dsse.json
|
||||
stella airgap verify --file policies/policy.dsse.json
|
||||
```
|
||||
|
||||
### 2.3 Deploy Offline Kit
|
||||
|
||||
```bash
|
||||
# Deploy kit (sealed mode)
|
||||
stella airgap deploy --kit /path/to/offline-kit --mode sealed
|
||||
|
||||
# Deploy kit (constrained mode with limited egress)
|
||||
stella airgap deploy --kit /path/to/offline-kit \
|
||||
--mode constrained \
|
||||
--egress-allowlist https://rekor.sigstore.dev
|
||||
|
||||
# Verify deployment
|
||||
stella airgap status
|
||||
|
||||
# Output:
|
||||
# Mode: sealed
|
||||
# Kit version: 2025.12.20
|
||||
# Feed snapshot: sha256:feed123... (2025-12-20)
|
||||
# VEX snapshot: sha256:vex456... (2025-12-20)
|
||||
# Policy: sha256:policy789... (v1.2.3)
|
||||
# Trust anchors: 3 active
|
||||
# Time anchor: Valid until 2025-12-31
|
||||
# Staleness: OK (0 days)
|
||||
```
|
||||
|
||||
### 2.4 Kit Updates
|
||||
|
||||
```bash
|
||||
# Check for kit updates (requires external access or new media)
|
||||
stella airgap check-update --current-kit /path/to/current-kit
|
||||
|
||||
# Import new kit
|
||||
stella airgap import-kit --kit /path/to/new-kit --validate
|
||||
|
||||
# Rollback to previous kit
|
||||
stella airgap rollback --generation previous
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Score Proofs in Air-Gap Mode
|
||||
|
||||
### 3.1 Create Scan (Air-Gap)
|
||||
|
||||
```bash
|
||||
# Create scan referencing offline kit snapshots
|
||||
stella scan create --artifact sha256:abc123... \
|
||||
--airgap \
|
||||
--feed-snapshot sha256:feed123... \
|
||||
--vex-snapshot sha256:vex456... \
|
||||
--policy-snapshot sha256:policy789...
|
||||
|
||||
# Or auto-detect from deployed kit
|
||||
stella scan create --artifact sha256:abc123... --use-offline-kit
|
||||
```
|
||||
|
||||
### 3.2 Score Replay (Air-Gap)
|
||||
|
||||
```bash
|
||||
# Replay with offline kit snapshots
|
||||
stella score replay --scan-id $SCAN_ID --offline
|
||||
|
||||
# Replay with specific bundle
|
||||
stella score replay --scan-id $SCAN_ID \
|
||||
--offline \
|
||||
--bundle /path/to/proof-bundle.zip
|
||||
|
||||
# Compare with different kit versions
|
||||
stella score replay --scan-id $SCAN_ID \
|
||||
--offline \
|
||||
--feed-snapshot sha256:newfeed... \
|
||||
--diff
|
||||
```
|
||||
|
||||
### 3.3 Generate Proof Bundle (Air-Gap)
|
||||
|
||||
```bash
|
||||
# Generate proof bundle for export
|
||||
stella proof export --scan-id $SCAN_ID \
|
||||
--include-manifest \
|
||||
--include-chain \
|
||||
--output proof-bundle.zip
|
||||
|
||||
# Proof bundle contents (air-gap safe):
|
||||
# - manifest.json (canonical)
|
||||
# - manifest.dsse.json
|
||||
# - score_proof.json
|
||||
# - proof_root.dsse.json
|
||||
# - meta.json
|
||||
# - NO external references
|
||||
|
||||
# Generate portable bundle
|
||||
stella proof export --scan-id $SCAN_ID \
|
||||
--portable \
|
||||
--include-trust-anchors \
|
||||
--output portable-proof.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Reachability in Air-Gap Mode
|
||||
|
||||
### 4.1 Call Graph Operations (Air-Gap)
|
||||
|
||||
```bash
|
||||
# Upload call graph (works identically)
|
||||
stella scan graph upload --scan-id $SCAN_ID --file callgraph.json
|
||||
|
||||
# Call graph processing is fully local
|
||||
# No external network required
|
||||
```
|
||||
|
||||
### 4.2 Compute Reachability (Air-Gap)
|
||||
|
||||
```bash
|
||||
# Compute reachability (fully offline)
|
||||
stella reachability compute --scan-id $SCAN_ID --offline
|
||||
|
||||
# Symbol resolution uses offline database
|
||||
stella reachability compute --scan-id $SCAN_ID \
|
||||
--offline \
|
||||
--symbol-db /path/to/offline-kit/symbols/symbol-index.db
|
||||
```
|
||||
|
||||
### 4.3 Explain Queries (Air-Gap)
|
||||
|
||||
```bash
|
||||
# Explain queries work offline
|
||||
stella reachability explain --scan-id $SCAN_ID \
|
||||
--cve CVE-2024-1234 \
|
||||
--purl "pkg:npm/lodash@4.17.20" \
|
||||
--offline
|
||||
|
||||
# Export explanations for external review
|
||||
stella reachability explain-all --scan-id $SCAN_ID \
|
||||
--output explanations.json \
|
||||
--offline
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Bundle Import Operations
|
||||
|
||||
### 5.1 Import Feed Updates
|
||||
|
||||
```bash
|
||||
# Verify feed bundle before import
|
||||
stella airgap verify --bundle feed-update.zip
|
||||
|
||||
# Dry-run import
|
||||
stella airgap import --bundle feed-update.zip \
|
||||
--type feed \
|
||||
--dry-run
|
||||
|
||||
# Import feed bundle
|
||||
stella airgap import --bundle feed-update.zip \
|
||||
--type feed \
|
||||
--generation 2025.12.21
|
||||
|
||||
# Verify import
|
||||
stella airgap verify-import --generation 2025.12.21
|
||||
```
|
||||
|
||||
### 5.2 Import VEX Updates
|
||||
|
||||
```bash
|
||||
# Import VEX bundle
|
||||
stella airgap import --bundle vex-update.zip \
|
||||
--type vex \
|
||||
--generation 2025.12.21
|
||||
|
||||
# Verify VEX statements
|
||||
stella airgap vex-status
|
||||
|
||||
# Output:
|
||||
# VEX statements: 15,432
|
||||
# Last update: 2025-12-21
|
||||
# Generation: 2025.12.21
|
||||
# Signature: VALID
|
||||
```
|
||||
|
||||
### 5.3 Import Trust Anchors
|
||||
|
||||
```bash
|
||||
# Import new trust anchor (requires approval)
|
||||
stella airgap import-anchor --file new-anchor.json \
|
||||
--reason "Key rotation Q4 2025" \
|
||||
--approver admin@example.com
|
||||
|
||||
# Verify anchor chain
|
||||
stella airgap verify-anchors
|
||||
|
||||
# List active anchors
|
||||
stella airgap anchors list
|
||||
```
|
||||
|
||||
### 5.4 Import Checklist
|
||||
|
||||
**Pre-Import**:
|
||||
- [ ] Verify bundle signature (DSSE)
|
||||
- [ ] Verify bundle hash matches manifest
|
||||
- [ ] Confirm sealed/constrained mode is set
|
||||
- [ ] Backup current generation
|
||||
|
||||
**Import**:
|
||||
- [ ] Run dry-run import
|
||||
- [ ] Apply import
|
||||
- [ ] Verify import succeeded
|
||||
|
||||
**Post-Import**:
|
||||
- [ ] Verify timeline event emitted
|
||||
- [ ] Update staleness dashboard
|
||||
- [ ] Archive import manifest
|
||||
- [ ] Update audit log
|
||||
|
||||
---
|
||||
|
||||
## 6. Proof Verification Offline
|
||||
|
||||
### 6.1 Verify Proof Bundle (Full Offline)
|
||||
|
||||
```bash
|
||||
# Verify proof bundle without any network access
|
||||
stella proof verify --bundle proof-bundle.zip \
|
||||
--offline \
|
||||
--trust-anchor /path/to/trust-anchors.json
|
||||
|
||||
# Verification checks (offline):
|
||||
# ✅ Signature valid (DSSE)
|
||||
# ✅ Content-addressed ID matches
|
||||
# ✅ Merkle path valid
|
||||
# ⏭️ Rekor inclusion (SKIPPED - offline mode)
|
||||
# ✅ Time anchor valid
|
||||
```
|
||||
|
||||
### 6.2 Verify with Portable Bundle
|
||||
|
||||
```bash
|
||||
# Portable bundles include trust anchors
|
||||
stella proof verify --bundle portable-proof.zip \
|
||||
--offline \
|
||||
--self-contained
|
||||
|
||||
# Output:
|
||||
# Using embedded trust anchors
|
||||
# Signature verification: PASS
|
||||
# ID recomputation: PASS
|
||||
# Merkle path: PASS
|
||||
# Time anchor: VALID
|
||||
# Overall: VERIFIED
|
||||
```
|
||||
|
||||
### 6.3 Batch Verification
|
||||
|
||||
```bash
|
||||
# Verify multiple bundles
|
||||
stella proof verify-batch --dir /path/to/bundles/ \
|
||||
--offline \
|
||||
--trust-anchor /path/to/trust-anchors.json \
|
||||
--output verification-report.json
|
||||
|
||||
# Generate verification report
|
||||
cat verification-report.json | jq '.summary'
|
||||
# Output:
|
||||
# {
|
||||
# "total": 100,
|
||||
# "verified": 98,
|
||||
# "failed": 2,
|
||||
# "skipped": 0
|
||||
# }
|
||||
```
|
||||
|
||||
### 6.4 Verification Without CLI
|
||||
|
||||
For environments without the CLI, manual verification is possible:
|
||||
|
||||
```bash
|
||||
# 1. Extract bundle
|
||||
unzip proof-bundle.zip -d ./verify/
|
||||
|
||||
# 2. Verify DSSE signature (using openssl)
|
||||
# Extract payload from DSSE envelope
|
||||
cat ./verify/manifest.dsse.json | jq -r '.payload' | base64 -d > payload.json
|
||||
|
||||
# Verify signature
|
||||
cat ./verify/manifest.dsse.json | jq -r '.signatures[0].sig' | base64 -d > signature.bin
|
||||
openssl dgst -sha256 -verify trust-anchor-pubkey.pem -signature signature.bin payload.json
|
||||
|
||||
# 3. Verify content-addressed ID
|
||||
# Compute canonical hash
|
||||
cat ./verify/manifest.json | jq -cS . | sha256sum
|
||||
# Compare with manifestHash in bundle
|
||||
|
||||
# 4. Verify merkle path
|
||||
# (See docs/airgap/proof-chain-verification.md for algorithm)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Troubleshooting
|
||||
|
||||
### 7.1 Kit Verification Failed
|
||||
|
||||
**Symptom**: `stella airgap verify-kit` fails.
|
||||
|
||||
**Diagnosis**:
|
||||
|
||||
```bash
|
||||
# Check specific component
|
||||
stella airgap verify-kit --verbose --component feeds
|
||||
|
||||
# Common errors:
|
||||
# - "Signature verification failed": Key mismatch
|
||||
# - "Hash mismatch": Bundle corrupted during transfer
|
||||
# - "Time anchor expired": Anchor needs refresh
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
|
||||
| Error | Cause | Resolution |
|
||||
|-------|-------|------------|
|
||||
| Signature failed | Wrong trust anchor | Import correct anchor |
|
||||
| Hash mismatch | Corruption | Re-transfer bundle |
|
||||
| Time anchor expired | Clock drift or expired | Import new time anchor |
|
||||
|
||||
### 7.2 Staleness Alert
|
||||
|
||||
**Symptom**: Staleness warning/alert.
|
||||
|
||||
**Diagnosis**:
|
||||
|
||||
```bash
|
||||
# Check staleness status
|
||||
stella airgap staleness-status
|
||||
|
||||
# Output:
|
||||
# Feed age: 5 days (threshold: 7 days)
|
||||
# VEX age: 3 days (threshold: 7 days)
|
||||
# Policy age: 30 days (threshold: 90 days)
|
||||
# Status: AMBER (approaching threshold)
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
|
||||
```bash
|
||||
# Import updated bundles
|
||||
stella airgap import --bundle /path/to/latest-feed.zip --type feed
|
||||
|
||||
# If bundles unavailable and breach imminent:
|
||||
# - Raise amber alert (5-7 days)
|
||||
# - If >7 days, raise red alert and halt new ingests
|
||||
# - Request emergency bundle via secure channel
|
||||
```
|
||||
|
||||
### 7.3 Proof Verification Fails Offline
|
||||
|
||||
**Symptom**: Proof verification fails in sealed mode.
|
||||
|
||||
**Diagnosis**:
|
||||
|
||||
```bash
|
||||
# Check verification error
|
||||
stella proof verify --bundle proof.zip --offline --verbose
|
||||
|
||||
# Common errors:
|
||||
# - "Trust anchor not found": Missing anchor in offline kit
|
||||
# - "Time anchor expired": Time validation failed
|
||||
# - "Unsupported algorithm": Key algorithm not supported
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
|
||||
```bash
|
||||
# For missing trust anchor:
|
||||
# Import the required anchor
|
||||
stella airgap import-anchor --file required-anchor.json
|
||||
|
||||
# For expired time anchor:
|
||||
# Import new time anchor
|
||||
stella airgap import-time-anchor --file new-time-anchor.json
|
||||
|
||||
# For algorithm issues:
|
||||
# Regenerate proof with supported algorithm
|
||||
stella proof regenerate --scan-id $SCAN_ID --algorithm ECDSA-P256
|
||||
```
|
||||
|
||||
### 7.4 Symbol Resolution Fails
|
||||
|
||||
**Symptom**: Reachability shows "symbol not found" errors.
|
||||
|
||||
**Diagnosis**:
|
||||
|
||||
```bash
|
||||
# Check symbol database status
|
||||
stella airgap symbols-status
|
||||
|
||||
# Output:
|
||||
# Symbol DB: /path/to/symbols/symbol-index.db
|
||||
# Version: 2025.12.15
|
||||
# Entries: 5,234,567
|
||||
# Coverage: Java, .NET, Python
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
|
||||
```bash
|
||||
# Import updated symbol database
|
||||
stella airgap import --bundle symbol-update.zip --type symbols
|
||||
|
||||
# Recompute reachability with new symbols
|
||||
stella reachability compute --scan-id $SCAN_ID --offline --force
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Monitoring & Alerting
|
||||
|
||||
### 8.1 Key Metrics (Air-Gap)
|
||||
|
||||
| Metric | Description | Alert Threshold |
|
||||
|--------|-------------|-----------------|
|
||||
| `airgap_staleness_days` | Days since last bundle import | > 5 (amber), > 7 (red) |
|
||||
| `airgap_time_anchor_validity_days` | Days until time anchor expires | < 7 |
|
||||
| `airgap_verification_failures` | Offline verification failures | > 0 |
|
||||
| `airgap_import_failures` | Bundle import failures | > 0 |
|
||||
|
||||
### 8.2 Alerting Rules
|
||||
|
||||
```yaml
|
||||
groups:
|
||||
- name: airgap-operations
|
||||
rules:
|
||||
- alert: AirgapStalenessAmber
|
||||
expr: airgap_staleness_days > 5
|
||||
for: 1h
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Air-gap feed staleness approaching threshold"
|
||||
|
||||
- alert: AirgapStalenessRed
|
||||
expr: airgap_staleness_days > 7
|
||||
for: 1h
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Air-gap feed staleness breach - halt new ingests"
|
||||
|
||||
- alert: AirgapTimeAnchorExpiring
|
||||
expr: airgap_time_anchor_validity_days < 7
|
||||
for: 1h
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Time anchor expiring in {{ $value }} days"
|
||||
|
||||
- alert: AirgapVerificationFailure
|
||||
expr: increase(airgap_verification_failures_total[1h]) > 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Air-gap verification failures detected"
|
||||
```
|
||||
|
||||
### 8.3 Audit Requirements
|
||||
|
||||
For air-gapped environments, maintain strict audit trails:
|
||||
|
||||
```bash
|
||||
# Record every import
|
||||
{
|
||||
"timestamp": "2025-12-20T10:00:00Z",
|
||||
"action": "import",
|
||||
"bundleType": "feed",
|
||||
"bundleHash": "sha256:feed123...",
|
||||
"generation": "2025.12.20",
|
||||
"actor": "operator@example.com",
|
||||
"mode": "sealed",
|
||||
"verification": "PASS"
|
||||
}
|
||||
|
||||
# Daily audit log export
|
||||
stella airgap audit-export --date today --output audit-$(date +%Y%m%d).json
|
||||
|
||||
# Verify audit log integrity
|
||||
stella airgap audit-verify --log audit-20251220.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Air-Gap Overview](./overview.md)
|
||||
- [Offline Bundle Format](./offline-bundle-format.md)
|
||||
- [Proof Chain Verification](./proof-chain-verification.md)
|
||||
- [Time Anchor Schema](./time-anchor-schema.md)
|
||||
- [Score Proofs Runbook](../operations/score-proofs-runbook.md)
|
||||
- [Reachability Runbook](../operations/reachability-runbook.md)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-12-20
|
||||
**Version**: 1.0.0
|
||||
**Sprint**: 3500.0004.0004
|
||||
558
docs/cli/reachability-cli-reference.md
Normal file
558
docs/cli/reachability-cli-reference.md
Normal file
@@ -0,0 +1,558 @@
|
||||
# Reachability CLI Reference
|
||||
|
||||
**Sprint:** SPRINT_3500_0004_0004
|
||||
**Version:** 1.0.0
|
||||
|
||||
## Overview
|
||||
|
||||
The Reachability CLI commands enable call graph management, reachability computation, and explain queries. All commands support air-gapped operation.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### stella reachability
|
||||
|
||||
Manage reachability analysis.
|
||||
|
||||
```bash
|
||||
stella reachability <SUBCOMMAND> [OPTIONS]
|
||||
```
|
||||
|
||||
#### Subcommands
|
||||
|
||||
| Subcommand | Description |
|
||||
|------------|-------------|
|
||||
| `compute` | Trigger reachability computation |
|
||||
| `findings` | List reachability findings |
|
||||
| `explain` | Explain reachability verdict |
|
||||
| `explain-all` | Export all explanations |
|
||||
| `summary` | Show reachability summary |
|
||||
| `job-status` | Check computation job status |
|
||||
| `job-logs` | View job logs |
|
||||
| `job-cancel` | Cancel running job |
|
||||
|
||||
---
|
||||
|
||||
### stella reachability compute
|
||||
|
||||
Trigger reachability computation for a scan.
|
||||
|
||||
```bash
|
||||
stella reachability compute [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--max-depth <N>` | Maximum path length to explore | 10 |
|
||||
| `--indirect-resolution <MODE>` | Handle indirect calls: `conservative`, `aggressive`, `skip` | `conservative` |
|
||||
| `--timeout <DURATION>` | Maximum computation time | 300s |
|
||||
| `--parallel` | Enable parallel BFS | `true` |
|
||||
| `--include-runtime` | Merge runtime evidence | `true` |
|
||||
| `--offline` | Run in offline mode | `false` |
|
||||
| `--symbol-db <PATH>` | Symbol resolution database | System default |
|
||||
| `--deterministic` | Enable deterministic mode | `true` |
|
||||
| `--seed <BASE64>` | Random seed for determinism | Auto |
|
||||
| `--graph-digest <HASH>` | Use specific call graph version | Latest |
|
||||
| `--partition-by <KEY>` | Partition analysis: `artifact`, `entrypoint` | — |
|
||||
| `--force` | Force recomputation | `false` |
|
||||
| `--wait` | Wait for completion | `false` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Basic computation
|
||||
stella reachability compute --scan-id $SCAN_ID
|
||||
|
||||
# With custom options
|
||||
stella reachability compute --scan-id $SCAN_ID \
|
||||
--max-depth 20 \
|
||||
--timeout 600s \
|
||||
--indirect-resolution conservative
|
||||
|
||||
# Wait for completion
|
||||
stella reachability compute --scan-id $SCAN_ID --wait
|
||||
|
||||
# Offline computation
|
||||
stella reachability compute --scan-id $SCAN_ID --offline
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella reachability findings
|
||||
|
||||
List reachability findings for a scan.
|
||||
|
||||
```bash
|
||||
stella reachability findings [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--status <STATUS>` | Filter by status (comma-separated) | All |
|
||||
| `--cve <ID>` | Filter by CVE ID | — |
|
||||
| `--purl <PURL>` | Filter by package URL | — |
|
||||
| `--min-confidence <N>` | Minimum confidence (0-1) | 0 |
|
||||
| `--output <PATH>` | Output file path | stdout |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `table`, `sarif` | `table` |
|
||||
|
||||
#### Status Values
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| `UNREACHABLE` | No path found |
|
||||
| `POSSIBLY_REACHABLE` | Path with heuristic edges |
|
||||
| `REACHABLE_STATIC` | Statically proven path |
|
||||
| `REACHABLE_PROVEN` | Runtime confirmed |
|
||||
| `UNKNOWN` | Insufficient data |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# List all findings
|
||||
stella reachability findings --scan-id $SCAN_ID
|
||||
|
||||
# Filter by status
|
||||
stella reachability findings --scan-id $SCAN_ID \
|
||||
--status REACHABLE_STATIC,REACHABLE_PROVEN
|
||||
|
||||
# Export as SARIF for CI
|
||||
stella reachability findings --scan-id $SCAN_ID \
|
||||
--status REACHABLE_STATIC,REACHABLE_PROVEN \
|
||||
--output-format sarif \
|
||||
--output findings.sarif
|
||||
|
||||
# JSON output
|
||||
stella reachability findings --scan-id $SCAN_ID --output-format json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella reachability explain
|
||||
|
||||
Explain a reachability verdict.
|
||||
|
||||
```bash
|
||||
stella reachability explain [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--cve <ID>` | CVE ID | Required |
|
||||
| `--purl <PURL>` | Package URL | Required |
|
||||
| `--all-paths` | Show all paths, not just shortest | `false` |
|
||||
| `--max-paths <N>` | Maximum paths to show | 5 |
|
||||
| `--verbose` | Show detailed explanation | `false` |
|
||||
| `--offline` | Run in offline mode | `false` |
|
||||
| `--output <PATH>` | Output file path | stdout |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `text` | `text` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Explain single finding
|
||||
stella reachability explain --scan-id $SCAN_ID \
|
||||
--cve CVE-2024-1234 \
|
||||
--purl "pkg:npm/lodash@4.17.20"
|
||||
|
||||
# Show all paths
|
||||
stella reachability explain --scan-id $SCAN_ID \
|
||||
--cve CVE-2024-1234 \
|
||||
--purl "pkg:npm/lodash@4.17.20" \
|
||||
--all-paths
|
||||
|
||||
# JSON output
|
||||
stella reachability explain --scan-id $SCAN_ID \
|
||||
--cve CVE-2024-1234 \
|
||||
--purl "pkg:npm/lodash@4.17.20" \
|
||||
--output-format json
|
||||
```
|
||||
|
||||
#### Output Example
|
||||
|
||||
```
|
||||
Status: REACHABLE_STATIC
|
||||
Confidence: 0.70
|
||||
|
||||
Shortest Path (depth=3):
|
||||
[0] MyApp.Controllers.OrdersController::Get(Guid)
|
||||
Entrypoint: HTTP GET /api/orders/{id}
|
||||
[1] MyApp.Services.OrderService::Process(Order)
|
||||
Edge: static (direct_call)
|
||||
[2] Lodash.merge(Object, Object) [VULNERABLE]
|
||||
Edge: static (direct_call)
|
||||
|
||||
Why Reachable:
|
||||
- Static call path exists from HTTP entrypoint /api/orders/{id}
|
||||
- All edges are statically proven (no heuristics)
|
||||
- Vulnerable function Lodash.merge() is directly invoked
|
||||
|
||||
Confidence Factors:
|
||||
staticPathExists: +0.50
|
||||
noHeuristicEdges: +0.20
|
||||
runtimeConfirmed: +0.00
|
||||
|
||||
Alternative Paths: 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella reachability explain-all
|
||||
|
||||
Export all reachability explanations.
|
||||
|
||||
```bash
|
||||
stella reachability explain-all [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--status <STATUS>` | Filter by status | All |
|
||||
| `--output <PATH>` | Output file path | Required |
|
||||
| `--offline` | Run in offline mode | `false` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Export all explanations
|
||||
stella reachability explain-all --scan-id $SCAN_ID --output explanations.json
|
||||
|
||||
# Export only reachable findings
|
||||
stella reachability explain-all --scan-id $SCAN_ID \
|
||||
--status REACHABLE_STATIC,REACHABLE_PROVEN \
|
||||
--output reachable-explanations.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella reachability summary
|
||||
|
||||
Show reachability summary for a scan.
|
||||
|
||||
```bash
|
||||
stella reachability summary [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `table` | `table` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Show summary
|
||||
stella reachability summary --scan-id $SCAN_ID
|
||||
|
||||
# Output:
|
||||
# Total vulnerabilities: 45
|
||||
# Unreachable: 38 (84%)
|
||||
# Possibly reachable: 4 (9%)
|
||||
# Reachable (static): 2 (4%)
|
||||
# Reachable (proven): 1 (2%)
|
||||
# Unknown: 0 (0%)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella reachability job-status
|
||||
|
||||
Check computation job status.
|
||||
|
||||
```bash
|
||||
stella reachability job-status [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--job-id <ID>` | Job ID | Required |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
stella reachability job-status --job-id reachability-job-001
|
||||
|
||||
# Output:
|
||||
# Status: running
|
||||
# Progress: 67% (8,234 / 12,345 nodes visited)
|
||||
# Started: 2025-12-20T10:00:00Z
|
||||
# Estimated completion: 2025-12-20T10:02:30Z
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Call Graph Commands
|
||||
|
||||
### stella scan graph
|
||||
|
||||
Manage call graphs.
|
||||
|
||||
```bash
|
||||
stella scan graph <SUBCOMMAND> [OPTIONS]
|
||||
```
|
||||
|
||||
#### Subcommands
|
||||
|
||||
| Subcommand | Description |
|
||||
|------------|-------------|
|
||||
| `upload` | Upload call graph |
|
||||
| `summary` | Show call graph summary |
|
||||
| `entrypoints` | List entrypoints |
|
||||
| `export` | Export call graph |
|
||||
| `validate` | Validate call graph |
|
||||
| `visualize` | Generate visualization |
|
||||
| `convert` | Convert graph format |
|
||||
| `partition` | Partition large graph |
|
||||
| `merge` | Merge multiple graphs |
|
||||
|
||||
---
|
||||
|
||||
### stella scan graph upload
|
||||
|
||||
Upload a call graph to a scan.
|
||||
|
||||
```bash
|
||||
stella scan graph upload [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--file <PATH>` | Call graph file | Required |
|
||||
| `--format <FMT>` | Format: `json`, `ndjson` | Auto-detect |
|
||||
| `--streaming` | Use streaming upload | `false` |
|
||||
| `--framework <NAME>` | Framework hint | Auto-detect |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Basic upload
|
||||
stella scan graph upload --scan-id $SCAN_ID --file callgraph.json
|
||||
|
||||
# Streaming upload (large graphs)
|
||||
stella scan graph upload --scan-id $SCAN_ID \
|
||||
--file callgraph.ndjson \
|
||||
--format ndjson \
|
||||
--streaming
|
||||
|
||||
# With framework hint
|
||||
stella scan graph upload --scan-id $SCAN_ID \
|
||||
--file callgraph.json \
|
||||
--framework aspnetcore
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella scan graph summary
|
||||
|
||||
Show call graph summary.
|
||||
|
||||
```bash
|
||||
stella scan graph summary [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
stella scan graph summary --scan-id $SCAN_ID
|
||||
|
||||
# Output:
|
||||
# Nodes: 12,345
|
||||
# Edges: 56,789
|
||||
# Entrypoints: 42
|
||||
# Languages: [dotnet, java]
|
||||
# Size: 15.2 MB
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella scan graph entrypoints
|
||||
|
||||
List detected entrypoints.
|
||||
|
||||
```bash
|
||||
stella scan graph entrypoints [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--verbose` | Show detailed info | `false` |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `table` | `table` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# List entrypoints
|
||||
stella scan graph entrypoints --scan-id $SCAN_ID
|
||||
|
||||
# Output:
|
||||
# Kind | Route | Framework | Node
|
||||
# ─────────┼─────────────────────┼─────────────┼────────────────
|
||||
# http | GET /api/orders | aspnetcore | OrdersController::Get
|
||||
# http | POST /api/orders | aspnetcore | OrdersController::Create
|
||||
# grpc | OrderService.Get | grpc-dotnet | OrderService::GetOrder
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella scan graph validate
|
||||
|
||||
Validate call graph structure.
|
||||
|
||||
```bash
|
||||
stella scan graph validate [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Validate uploaded graph | — |
|
||||
| `--file <PATH>` | Validate local file | — |
|
||||
| `--strict` | Enable strict validation | `false` |
|
||||
|
||||
#### Validation Checks
|
||||
|
||||
- All edge targets exist as nodes
|
||||
- Entrypoints reference valid nodes
|
||||
- No orphan nodes
|
||||
- No cycles in entrypoint definitions
|
||||
- Schema compliance
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Validate uploaded graph
|
||||
stella scan graph validate --scan-id $SCAN_ID
|
||||
|
||||
# Validate before upload
|
||||
stella scan graph validate --file callgraph.json --strict
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella scan graph visualize
|
||||
|
||||
Generate call graph visualization.
|
||||
|
||||
```bash
|
||||
stella scan graph visualize [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--node <ID>` | Center on specific node | — |
|
||||
| `--depth <N>` | Visualization depth | 3 |
|
||||
| `--output <PATH>` | Output file (SVG/PNG/DOT) | Required |
|
||||
| `--format <FMT>` | Format: `svg`, `png`, `dot` | `svg` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Visualize subgraph
|
||||
stella scan graph visualize --scan-id $SCAN_ID \
|
||||
--node sha256:node123... \
|
||||
--depth 3 \
|
||||
--output subgraph.svg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Options
|
||||
|
||||
### Authentication
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--token <TOKEN>` | OAuth bearer token |
|
||||
| `--token-file <PATH>` | File containing token |
|
||||
| `--profile <NAME>` | Use named profile |
|
||||
|
||||
### Output
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--quiet` | Suppress non-error output |
|
||||
| `--verbose` | Enable verbose output |
|
||||
| `--debug` | Enable debug logging |
|
||||
| `--no-color` | Disable colored output |
|
||||
|
||||
### Connection
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--endpoint <URL>` | Scanner API endpoint |
|
||||
| `--timeout <DURATION>` | Request timeout |
|
||||
| `--insecure` | Skip TLS verification |
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `STELLA_TOKEN` | OAuth token |
|
||||
| `STELLA_ENDPOINT` | API endpoint |
|
||||
| `STELLA_PROFILE` | Profile name |
|
||||
| `STELLA_OFFLINE` | Offline mode |
|
||||
| `STELLA_SYMBOL_DB` | Symbol database path |
|
||||
|
||||
---
|
||||
|
||||
## Exit Codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | Success |
|
||||
| 1 | General error |
|
||||
| 2 | Invalid arguments |
|
||||
| 3 | Authentication failed |
|
||||
| 4 | Resource not found |
|
||||
| 5 | Computation failed |
|
||||
| 6 | Network error |
|
||||
| 10 | Timeout |
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Score Proofs CLI Reference](./score-proofs-cli-reference.md)
|
||||
- [Unknowns CLI Reference](./unknowns-cli-reference.md)
|
||||
- [Reachability API Reference](../api/score-proofs-reachability-api-reference.md)
|
||||
- [Reachability Runbook](../operations/reachability-runbook.md)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-12-20
|
||||
**Version**: 1.0.0
|
||||
**Sprint**: 3500.0004.0004
|
||||
450
docs/cli/score-proofs-cli-reference.md
Normal file
450
docs/cli/score-proofs-cli-reference.md
Normal file
@@ -0,0 +1,450 @@
|
||||
# Score Proofs CLI Reference
|
||||
|
||||
**Sprint:** SPRINT_3500_0004_0004
|
||||
**Version:** 1.0.0
|
||||
|
||||
## Overview
|
||||
|
||||
The Score Proofs CLI commands enable score computation, replay, proof verification, and proof bundle management. All commands support air-gapped operation.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### stella score
|
||||
|
||||
Compute or replay vulnerability scores.
|
||||
|
||||
```bash
|
||||
stella score <SUBCOMMAND> [OPTIONS]
|
||||
```
|
||||
|
||||
#### Subcommands
|
||||
|
||||
| Subcommand | Description |
|
||||
|------------|-------------|
|
||||
| `compute` | Compute scores for a scan |
|
||||
| `replay` | Replay score computation with different inputs |
|
||||
| `show` | Display score details for a scan |
|
||||
| `diff` | Compare scores between runs |
|
||||
| `manifest` | View/export scan manifest |
|
||||
| `inputs` | List scoring inputs |
|
||||
|
||||
---
|
||||
|
||||
### stella score compute
|
||||
|
||||
Compute vulnerability scores for a scan.
|
||||
|
||||
```bash
|
||||
stella score compute [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID to compute scores for | Required |
|
||||
| `--deterministic` | Enable deterministic mode | `true` |
|
||||
| `--seed <BASE64>` | Random seed for determinism | Auto-generated |
|
||||
| `--output <PATH>` | Output file path | stdout |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `table` | `table` |
|
||||
| `--include-proof` | Include proof ledger in output | `false` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Compute scores
|
||||
stella score compute --scan-id $SCAN_ID
|
||||
|
||||
# Compute with proof output
|
||||
stella score compute --scan-id $SCAN_ID --include-proof --output-format json
|
||||
|
||||
# Compute in deterministic mode with fixed seed
|
||||
stella score compute --scan-id $SCAN_ID --deterministic --seed "AQIDBA=="
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella score replay
|
||||
|
||||
Replay score computation with updated feeds or policies.
|
||||
|
||||
```bash
|
||||
stella score replay [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID to replay | Required |
|
||||
| `--feed-snapshot <HASH>` | Override feed snapshot hash | Current |
|
||||
| `--vex-snapshot <HASH>` | Override VEX snapshot hash | Current |
|
||||
| `--policy-snapshot <HASH>` | Override policy hash | Current |
|
||||
| `--use-original-snapshots` | Use exact original snapshots | `false` |
|
||||
| `--diff` | Show diff from original | `false` |
|
||||
| `--skip-unchanged` | Skip if no input changes | `false` |
|
||||
| `--offline` | Run in offline mode | `false` |
|
||||
| `--bundle <PATH>` | Use offline bundle for replay | — |
|
||||
| `--output <PATH>` | Output file path | stdout |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `table` | `table` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Replay with current feeds
|
||||
stella score replay --scan-id $SCAN_ID
|
||||
|
||||
# Replay with specific feed snapshot
|
||||
stella score replay --scan-id $SCAN_ID --feed-snapshot sha256:newfeed...
|
||||
|
||||
# Replay and compare with original
|
||||
stella score replay --scan-id $SCAN_ID --diff
|
||||
|
||||
# Replay with original snapshots (exact reproduction)
|
||||
stella score replay --scan-id $SCAN_ID --use-original-snapshots
|
||||
|
||||
# Offline replay
|
||||
stella score replay --scan-id $SCAN_ID --offline --bundle /path/to/bundle.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella score show
|
||||
|
||||
Display score details for a scan.
|
||||
|
||||
```bash
|
||||
stella score show [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--verbose` | Show detailed breakdown | `false` |
|
||||
| `--include-evidence` | Include evidence references | `false` |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `table` | `table` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Show score summary
|
||||
stella score show --scan-id $SCAN_ID
|
||||
|
||||
# Show detailed breakdown
|
||||
stella score show --scan-id $SCAN_ID --verbose
|
||||
|
||||
# JSON output
|
||||
stella score show --scan-id $SCAN_ID --output-format json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella score diff
|
||||
|
||||
Compare scores between two runs.
|
||||
|
||||
```bash
|
||||
stella score diff [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID to compare | Required |
|
||||
| `--original` | Compare with original score | `false` |
|
||||
| `--replayed` | Compare with most recent replay | `false` |
|
||||
| `--base <RUN_ID>` | Base run ID for comparison | — |
|
||||
| `--target <RUN_ID>` | Target run ID for comparison | — |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `table` | `table` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Compare original vs replayed
|
||||
stella score diff --scan-id $SCAN_ID --original --replayed
|
||||
|
||||
# Compare two specific runs
|
||||
stella score diff --scan-id $SCAN_ID --base run-001 --target run-002
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella score manifest
|
||||
|
||||
View or export scan manifest.
|
||||
|
||||
```bash
|
||||
stella score manifest [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--output <PATH>` | Output file path | stdout |
|
||||
| `--include-dsse` | Include DSSE envelope | `false` |
|
||||
| `--verify` | Verify DSSE signature | `false` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# View manifest
|
||||
stella score manifest --scan-id $SCAN_ID
|
||||
|
||||
# Export with DSSE
|
||||
stella score manifest --scan-id $SCAN_ID --include-dsse --output manifest.json
|
||||
|
||||
# Verify manifest signature
|
||||
stella score manifest --scan-id $SCAN_ID --verify
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Proof Commands
|
||||
|
||||
### stella proof
|
||||
|
||||
Manage proof bundles.
|
||||
|
||||
```bash
|
||||
stella proof <SUBCOMMAND> [OPTIONS]
|
||||
```
|
||||
|
||||
#### Subcommands
|
||||
|
||||
| Subcommand | Description |
|
||||
|------------|-------------|
|
||||
| `verify` | Verify a proof bundle |
|
||||
| `download` | Download proof bundle |
|
||||
| `export` | Export proof bundle |
|
||||
| `inspect` | Inspect proof bundle contents |
|
||||
| `status` | Check proof status |
|
||||
| `list` | List proofs for a scan |
|
||||
| `retrieve` | Retrieve from cold storage |
|
||||
|
||||
---
|
||||
|
||||
### stella proof verify
|
||||
|
||||
Verify a proof bundle.
|
||||
|
||||
```bash
|
||||
stella proof verify [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--bundle-id <HASH>` | Proof bundle ID (sha256:...) | — |
|
||||
| `--bundle <PATH>` | Local proof bundle file | — |
|
||||
| `--offline` | Skip Rekor verification | `false` |
|
||||
| `--skip-rekor` | Alias for --offline | `false` |
|
||||
| `--check-rekor` | Force Rekor verification | `false` |
|
||||
| `--trust-anchor <PATH>` | Trust anchor file | System default |
|
||||
| `--public-key <PATH>` | Public key file | — |
|
||||
| `--self-contained` | Use embedded trust anchors | `false` |
|
||||
| `--verbose` | Show detailed verification | `false` |
|
||||
| `--check <CHECK>` | Verify specific check only | All |
|
||||
|
||||
#### Verification Checks
|
||||
|
||||
| Check | Description |
|
||||
|-------|-------------|
|
||||
| `signatureValid` | DSSE signature verification |
|
||||
| `idRecomputed` | Content-addressed ID match |
|
||||
| `merklePathValid` | Merkle tree construction |
|
||||
| `rekorInclusion` | Transparency log entry |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Verify online
|
||||
stella proof verify --bundle-id sha256:proof123...
|
||||
|
||||
# Verify offline
|
||||
stella proof verify --bundle proof.zip --offline
|
||||
|
||||
# Verify with specific trust anchor
|
||||
stella proof verify --bundle proof.zip --offline --trust-anchor anchors.json
|
||||
|
||||
# Verify specific check
|
||||
stella proof verify --bundle-id sha256:proof123... --check signatureValid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella proof download
|
||||
|
||||
Download proof bundle.
|
||||
|
||||
```bash
|
||||
stella proof download [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--root-hash <HASH>` | Specific proof root hash | Latest |
|
||||
| `--output <PATH>` | Output file path | `proof-{scanId}.zip` |
|
||||
| `--all` | Download all proofs for scan | `false` |
|
||||
| `--output-dir <PATH>` | Output directory (with --all) | `.` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Download latest proof
|
||||
stella proof download --scan-id $SCAN_ID --output proof.zip
|
||||
|
||||
# Download specific proof
|
||||
stella proof download --scan-id $SCAN_ID --root-hash sha256:proof123... --output proof.zip
|
||||
|
||||
# Download all proofs
|
||||
stella proof download --scan-id $SCAN_ID --all --output-dir ./proofs/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella proof export
|
||||
|
||||
Export proof bundle with additional data.
|
||||
|
||||
```bash
|
||||
stella proof export [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Scan ID | Required |
|
||||
| `--portable` | Create self-contained portable bundle | `false` |
|
||||
| `--include-manifest` | Include scan manifest | `true` |
|
||||
| `--include-chain` | Include full proof chain | `false` |
|
||||
| `--include-trust-anchors` | Include trust anchor keys | `false` |
|
||||
| `--output <PATH>` | Output file path | Required |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Export standard bundle
|
||||
stella proof export --scan-id $SCAN_ID --output proof-bundle.zip
|
||||
|
||||
# Export portable bundle (for offline verification)
|
||||
stella proof export --scan-id $SCAN_ID --portable --include-trust-anchors --output portable.zip
|
||||
|
||||
# Export with full chain
|
||||
stella proof export --scan-id $SCAN_ID --include-chain --output full-bundle.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella proof inspect
|
||||
|
||||
Inspect proof bundle contents.
|
||||
|
||||
```bash
|
||||
stella proof inspect [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--bundle <PATH>` | Proof bundle file | Required |
|
||||
| `--output-dir <PATH>` | Extract to directory | — |
|
||||
| `--show-manifest` | Display manifest | `false` |
|
||||
| `--show-proof` | Display proof nodes | `false` |
|
||||
| `--show-meta` | Display metadata | `false` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# List bundle contents
|
||||
stella proof inspect --bundle proof.zip
|
||||
|
||||
# Extract and inspect
|
||||
stella proof inspect --bundle proof.zip --output-dir ./inspection/
|
||||
|
||||
# Show manifest
|
||||
stella proof inspect --bundle proof.zip --show-manifest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Options
|
||||
|
||||
### Authentication
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--token <TOKEN>` | OAuth bearer token |
|
||||
| `--token-file <PATH>` | File containing token |
|
||||
| `--profile <NAME>` | Use named profile |
|
||||
|
||||
### Output
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--quiet` | Suppress non-error output |
|
||||
| `--verbose` | Enable verbose output |
|
||||
| `--debug` | Enable debug logging |
|
||||
| `--no-color` | Disable colored output |
|
||||
|
||||
### Connection
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--endpoint <URL>` | Scanner API endpoint |
|
||||
| `--timeout <DURATION>` | Request timeout (e.g., 30s, 5m) |
|
||||
| `--insecure` | Skip TLS verification (dev only) |
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Equivalent Option |
|
||||
|----------|-------------|-------------------|
|
||||
| `STELLA_TOKEN` | OAuth token | `--token` |
|
||||
| `STELLA_ENDPOINT` | API endpoint | `--endpoint` |
|
||||
| `STELLA_PROFILE` | Profile name | `--profile` |
|
||||
| `STELLA_OFFLINE` | Offline mode | `--offline` |
|
||||
| `STELLA_TRUST_ANCHOR` | Trust anchor path | `--trust-anchor` |
|
||||
|
||||
---
|
||||
|
||||
## Exit Codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | Success |
|
||||
| 1 | General error |
|
||||
| 2 | Invalid arguments |
|
||||
| 3 | Authentication failed |
|
||||
| 4 | Resource not found |
|
||||
| 5 | Verification failed |
|
||||
| 6 | Network error |
|
||||
| 10 | Timeout |
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Reachability CLI Reference](./reachability-cli-reference.md)
|
||||
- [Unknowns CLI Reference](./unknowns-cli-reference.md)
|
||||
- [Score Proofs API Reference](../api/score-proofs-reachability-api-reference.md)
|
||||
- [Score Proofs Runbook](../operations/score-proofs-runbook.md)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-12-20
|
||||
**Version**: 1.0.0
|
||||
**Sprint**: 3500.0004.0004
|
||||
532
docs/cli/unknowns-cli-reference.md
Normal file
532
docs/cli/unknowns-cli-reference.md
Normal file
@@ -0,0 +1,532 @@
|
||||
# Unknowns CLI Reference
|
||||
|
||||
**Sprint:** SPRINT_3500_0004_0004
|
||||
**Version:** 1.0.0
|
||||
|
||||
## Overview
|
||||
|
||||
The Unknowns CLI commands manage components that cannot be analyzed due to missing data, unrecognized formats, or resolution failures. These commands support triage workflows, escalation, and resolution tracking.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### stella unknowns
|
||||
|
||||
Manage unknowns registry.
|
||||
|
||||
```bash
|
||||
stella unknowns <SUBCOMMAND> [OPTIONS]
|
||||
```
|
||||
|
||||
#### Subcommands
|
||||
|
||||
| Subcommand | Description |
|
||||
|------------|-------------|
|
||||
| `list` | List unknowns |
|
||||
| `show` | Show unknown details |
|
||||
| `summary` | Show unknowns summary |
|
||||
| `escalate` | Escalate unknown |
|
||||
| `resolve` | Mark unknown resolved |
|
||||
| `suppress` | Suppress unknown |
|
||||
| `bulk-triage` | Bulk triage unknowns |
|
||||
| `export` | Export unknowns |
|
||||
| `import` | Import unknown resolutions |
|
||||
|
||||
---
|
||||
|
||||
### stella unknowns list
|
||||
|
||||
List unknowns for a scan or workspace.
|
||||
|
||||
```bash
|
||||
stella unknowns list [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Filter by scan ID | — |
|
||||
| `--workspace-id <ID>` | Filter by workspace ID | — |
|
||||
| `--status <STATUS>` | Filter by status | All |
|
||||
| `--category <CAT>` | Filter by category | All |
|
||||
| `--priority <PRI>` | Filter by priority (1-10) | All |
|
||||
| `--min-score <N>` | Minimum 2-factor score | 0 |
|
||||
| `--max-age <DURATION>` | Maximum age | — |
|
||||
| `--purl <PATTERN>` | Filter by PURL pattern | — |
|
||||
| `--output <PATH>` | Output file path | stdout |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `table`, `csv` | `table` |
|
||||
| `--limit <N>` | Maximum results | 100 |
|
||||
| `--offset <N>` | Pagination offset | 0 |
|
||||
| `--sort <FIELD>` | Sort field | `priority` |
|
||||
| `--order <DIR>` | Sort direction: `asc`, `desc` | `desc` |
|
||||
|
||||
#### Status Values
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| `pending` | Awaiting triage |
|
||||
| `escalated` | Escalated for manual review |
|
||||
| `suppressed` | Suppressed (accepted risk) |
|
||||
| `resolved` | Resolved |
|
||||
|
||||
#### Category Values
|
||||
|
||||
| Category | Description |
|
||||
|----------|-------------|
|
||||
| `unmapped_purl` | No CPE/OVAL mapping |
|
||||
| `checksum_miss` | Binary checksum not in DB |
|
||||
| `language_gap` | Unsupported language |
|
||||
| `parsing_failure` | Manifest parsing failed |
|
||||
| `network_timeout` | Feed unavailable |
|
||||
| `unrecognized_format` | Unknown format |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# List all pending unknowns
|
||||
stella unknowns list --status pending
|
||||
|
||||
# List high-priority unknowns
|
||||
stella unknowns list --min-score 7
|
||||
|
||||
# List by category
|
||||
stella unknowns list --category unmapped_purl
|
||||
|
||||
# Export to CSV
|
||||
stella unknowns list --scan-id $SCAN_ID --output-format csv --output unknowns.csv
|
||||
|
||||
# Filter by PURL pattern
|
||||
stella unknowns list --purl "pkg:npm/*"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella unknowns show
|
||||
|
||||
Show details of a specific unknown.
|
||||
|
||||
```bash
|
||||
stella unknowns show [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--id <ID>` | Unknown ID | Required |
|
||||
| `--verbose` | Show extended details | `false` |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `text` | `text` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Show unknown details
|
||||
stella unknowns show --id unknown-001
|
||||
|
||||
# Output:
|
||||
# ID: unknown-001
|
||||
# PURL: pkg:npm/left-pad@1.3.0
|
||||
# Category: unmapped_purl
|
||||
# Status: pending
|
||||
# Priority: 6
|
||||
# Score: 7.2 (vuln: 3, impact: 4.2)
|
||||
# Created: 2025-12-20T10:00:00Z
|
||||
# Scans Affected: 5
|
||||
# Reason: No CVE/advisory mapping exists for this package
|
||||
|
||||
# Verbose output
|
||||
stella unknowns show --id unknown-001 --verbose
|
||||
|
||||
# JSON output
|
||||
stella unknowns show --id unknown-001 --output-format json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella unknowns summary
|
||||
|
||||
Show unknowns summary statistics.
|
||||
|
||||
```bash
|
||||
stella unknowns summary [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Filter by scan ID | — |
|
||||
| `--workspace-id <ID>` | Filter by workspace ID | — |
|
||||
| `--output-format <FMT>` | Format: `json`, `yaml`, `table` | `table` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Summary for workspace
|
||||
stella unknowns summary --workspace-id $WS_ID
|
||||
|
||||
# Output:
|
||||
# Total unknowns: 127
|
||||
#
|
||||
# By Status:
|
||||
# pending: 89
|
||||
# escalated: 15
|
||||
# suppressed: 12
|
||||
# resolved: 11
|
||||
#
|
||||
# By Category:
|
||||
# unmapped_purl: 67
|
||||
# checksum_miss: 34
|
||||
# language_gap: 18
|
||||
# parsing_failure: 8
|
||||
#
|
||||
# Priority Distribution:
|
||||
# High (8-10): 12
|
||||
# Medium (5-7): 45
|
||||
# Low (1-4): 70
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella unknowns escalate
|
||||
|
||||
Escalate an unknown for manual review.
|
||||
|
||||
```bash
|
||||
stella unknowns escalate [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--id <ID>` | Unknown ID | Required |
|
||||
| `--reason <TEXT>` | Escalation reason | — |
|
||||
| `--assignee <USER>` | Assign to user/team | — |
|
||||
| `--severity <LEVEL>` | Severity: `low`, `medium`, `high`, `critical` | `medium` |
|
||||
| `--due-date <DATE>` | Due date (ISO 8601) | — |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Basic escalation
|
||||
stella unknowns escalate --id unknown-001 --reason "Potential supply chain risk"
|
||||
|
||||
# Escalate with assignment
|
||||
stella unknowns escalate --id unknown-001 \
|
||||
--reason "Missing mapping for critical dependency" \
|
||||
--assignee security-team \
|
||||
--severity high \
|
||||
--due-date 2025-12-27
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella unknowns resolve
|
||||
|
||||
Mark an unknown as resolved.
|
||||
|
||||
```bash
|
||||
stella unknowns resolve [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--id <ID>` | Unknown ID | Required |
|
||||
| `--resolution <TYPE>` | Resolution type | Required |
|
||||
| `--comment <TEXT>` | Resolution comment | — |
|
||||
| `--mapping <JSON>` | Custom mapping data | — |
|
||||
| `--evidence <PATH>` | Evidence file | — |
|
||||
|
||||
#### Resolution Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `mapped` | Package/CVE mapping added |
|
||||
| `not_applicable` | Not applicable to context |
|
||||
| `false_positive` | Detection was incorrect |
|
||||
| `accepted_risk` | Risk accepted |
|
||||
| `replaced` | Component replaced |
|
||||
| `removed` | Component removed |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Resolve with mapping
|
||||
stella unknowns resolve --id unknown-001 \
|
||||
--resolution mapped \
|
||||
--comment "Added CPE mapping to internal DB"
|
||||
|
||||
# Resolve as accepted risk
|
||||
stella unknowns resolve --id unknown-001 \
|
||||
--resolution accepted_risk \
|
||||
--comment "Internal component, no external exposure"
|
||||
|
||||
# Resolve with evidence
|
||||
stella unknowns resolve --id unknown-001 \
|
||||
--resolution not_applicable \
|
||||
--evidence ./analysis-report.pdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella unknowns suppress
|
||||
|
||||
Suppress an unknown (accept risk).
|
||||
|
||||
```bash
|
||||
stella unknowns suppress [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--id <ID>` | Unknown ID | Required |
|
||||
| `--reason <TEXT>` | Suppression reason | Required |
|
||||
| `--expires <DATE>` | Expiration date | — |
|
||||
| `--scope <SCOPE>` | Scope: `scan`, `workspace`, `global` | `scan` |
|
||||
| `--approver <USER>` | Approver name/email | — |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Suppress with expiration
|
||||
stella unknowns suppress --id unknown-001 \
|
||||
--reason "Internal tooling, no risk exposure" \
|
||||
--expires 2026-01-01
|
||||
|
||||
# Workspace-wide suppression
|
||||
stella unknowns suppress --id unknown-001 \
|
||||
--reason "Deprecated component, scheduled for removal" \
|
||||
--scope workspace \
|
||||
--approver security@example.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella unknowns bulk-triage
|
||||
|
||||
Bulk triage multiple unknowns.
|
||||
|
||||
```bash
|
||||
stella unknowns bulk-triage [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--file <PATH>` | Triage decisions file (JSON/YAML) | Required |
|
||||
| `--dry-run` | Preview changes | `false` |
|
||||
| `--continue-on-error` | Continue on individual failures | `false` |
|
||||
|
||||
#### Input File Format
|
||||
|
||||
```json
|
||||
{
|
||||
"decisions": [
|
||||
{
|
||||
"id": "unknown-001",
|
||||
"action": "resolve",
|
||||
"resolution": "mapped",
|
||||
"comment": "Added mapping"
|
||||
},
|
||||
{
|
||||
"id": "unknown-002",
|
||||
"action": "suppress",
|
||||
"reason": "Accepted risk",
|
||||
"expires": "2026-01-01"
|
||||
},
|
||||
{
|
||||
"id": "unknown-003",
|
||||
"action": "escalate",
|
||||
"reason": "Needs security review",
|
||||
"assignee": "security-team"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Bulk triage with preview
|
||||
stella unknowns bulk-triage --file triage-decisions.json --dry-run
|
||||
|
||||
# Apply bulk triage
|
||||
stella unknowns bulk-triage --file triage-decisions.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella unknowns export
|
||||
|
||||
Export unknowns data.
|
||||
|
||||
```bash
|
||||
stella unknowns export [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--scan-id <ID>` | Filter by scan ID | — |
|
||||
| `--workspace-id <ID>` | Filter by workspace ID | — |
|
||||
| `--status <STATUS>` | Filter by status | All |
|
||||
| `--output <PATH>` | Output file path | Required |
|
||||
| `--format <FMT>` | Format: `json`, `yaml`, `csv`, `ndjson` | `json` |
|
||||
| `--include-history` | Include resolution history | `false` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Export all unknowns
|
||||
stella unknowns export --workspace-id $WS_ID --output unknowns.json
|
||||
|
||||
# Export pending as CSV
|
||||
stella unknowns export --status pending --output pending.csv --format csv
|
||||
|
||||
# Export with history
|
||||
stella unknowns export --scan-id $SCAN_ID \
|
||||
--output unknowns-history.json \
|
||||
--include-history
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### stella unknowns import
|
||||
|
||||
Import unknown resolutions.
|
||||
|
||||
```bash
|
||||
stella unknowns import [OPTIONS]
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `--file <PATH>` | Resolutions file | Required |
|
||||
| `--format <FMT>` | Format: `json`, `yaml`, `csv` | Auto-detect |
|
||||
| `--dry-run` | Preview import | `false` |
|
||||
| `--conflict <MODE>` | Conflict handling: `skip`, `update`, `error` | `skip` |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
# Import resolutions
|
||||
stella unknowns import --file resolutions.json
|
||||
|
||||
# Preview import
|
||||
stella unknowns import --file resolutions.json --dry-run
|
||||
|
||||
# Update existing
|
||||
stella unknowns import --file resolutions.json --conflict update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Options
|
||||
|
||||
### Authentication
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--token <TOKEN>` | OAuth bearer token |
|
||||
| `--token-file <PATH>` | File containing token |
|
||||
| `--profile <NAME>` | Use named profile |
|
||||
|
||||
### Output
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--quiet` | Suppress non-error output |
|
||||
| `--verbose` | Enable verbose output |
|
||||
| `--debug` | Enable debug logging |
|
||||
| `--no-color` | Disable colored output |
|
||||
|
||||
### Connection
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--endpoint <URL>` | Scanner API endpoint |
|
||||
| `--timeout <DURATION>` | Request timeout |
|
||||
| `--insecure` | Skip TLS verification |
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `STELLA_TOKEN` | OAuth token |
|
||||
| `STELLA_ENDPOINT` | API endpoint |
|
||||
| `STELLA_PROFILE` | Profile name |
|
||||
| `STELLA_WORKSPACE` | Default workspace ID |
|
||||
|
||||
---
|
||||
|
||||
## Exit Codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 0 | Success |
|
||||
| 1 | General error |
|
||||
| 2 | Invalid arguments |
|
||||
| 3 | Authentication failed |
|
||||
| 4 | Resource not found |
|
||||
| 5 | Operation failed |
|
||||
| 6 | Network error |
|
||||
|
||||
---
|
||||
|
||||
## Workflows
|
||||
|
||||
### Daily Triage Workflow
|
||||
|
||||
```bash
|
||||
# 1. Check summary
|
||||
stella unknowns summary --workspace-id $WS_ID
|
||||
|
||||
# 2. List high-priority pending
|
||||
stella unknowns list --status pending --min-score 7
|
||||
|
||||
# 3. Review and escalate critical items
|
||||
stella unknowns escalate --id unknown-001 \
|
||||
--reason "Security review needed" \
|
||||
--severity high
|
||||
|
||||
# 4. Bulk resolve known patterns
|
||||
stella unknowns bulk-triage --file daily-resolutions.json
|
||||
```
|
||||
|
||||
### Weekly Report Export
|
||||
|
||||
```bash
|
||||
# Export all unknowns with history
|
||||
stella unknowns export \
|
||||
--workspace-id $WS_ID \
|
||||
--include-history \
|
||||
--output weekly-unknowns-$(date +%Y%m%d).json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Score Proofs CLI Reference](./score-proofs-cli-reference.md)
|
||||
- [Reachability CLI Reference](./reachability-cli-reference.md)
|
||||
- [Unknowns API Reference](../api/score-proofs-reachability-api-reference.md)
|
||||
- [Unknowns Queue Runbook](../operations/unknowns-queue-runbook.md)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-12-20
|
||||
**Version**: 1.0.0
|
||||
**Sprint**: 3500.0004.0004
|
||||
@@ -42,18 +42,18 @@ Complete API reference documentation for all new endpoints.
|
||||
|
||||
**Assignee**: Docs Team
|
||||
**Story Points**: 5
|
||||
**Status**: DOING
|
||||
**Status**: DONE
|
||||
|
||||
**Description**:
|
||||
Create operational runbooks for Score Proofs and Reachability features.
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] Score replay runbook
|
||||
- [ ] Proof verification runbook
|
||||
- [x] Score replay runbook (`docs/operations/score-replay-runbook.md`)
|
||||
- [x] Proof verification runbook (`docs/operations/proof-verification-runbook.md`)
|
||||
- [x] Reachability troubleshooting runbook (`docs/operations/reachability-runbook.md`)
|
||||
- [x] Unknowns queue management runbook (`docs/operations/unknowns-queue-runbook.md`)
|
||||
- [ ] Air-gap operations runbook
|
||||
- [x] Escalation procedures (included in both runbooks)
|
||||
- [x] Air-gap operations runbook (`docs/operations/airgap-operations-runbook.md`)
|
||||
- [x] Escalation procedures (included in all runbooks)
|
||||
|
||||
---
|
||||
|
||||
@@ -174,12 +174,12 @@ Complete handoff to operations and support teams.
|
||||
|---|---------|--------|------------|--------|-----------------|
|
||||
| 1 | T1 | DONE | — | Agent | API Reference Documentation |
|
||||
| 2 | T2 | DONE | — | Agent | Operations Runbooks |
|
||||
| 3 | T3 | DOING | — | Agent | Architecture Documentation |
|
||||
| 4 | T4 | TODO | — | Docs Team | CLI Reference Guide |
|
||||
| 5 | T5 | TODO | T1-T4 | Docs Team | Training Materials |
|
||||
| 6 | T6 | TODO | T1-T5 | Docs Team | Release Notes |
|
||||
| 7 | T7 | TODO | T1 | Docs Team | OpenAPI Specification Update |
|
||||
| 8 | T8 | TODO | T1-T7 | Project Mgmt | Handoff Checklist |
|
||||
| 3 | T3 | DONE | — | Agent | Architecture Documentation |
|
||||
| 4 | T4 | DONE | — | Agent | CLI Reference Guide |
|
||||
| 5 | T5 | DOING | T1-T4 | Agent | Training Materials |
|
||||
| 6 | T6 | TODO | T1-T5 | Agent | Release Notes |
|
||||
| 7 | T7 | TODO | T1 | Agent | OpenAPI Specification Update |
|
||||
| 8 | T8 | TODO | T1-T7 | Agent | Handoff Checklist |
|
||||
|
||||
---
|
||||
|
||||
@@ -188,6 +188,11 @@ Complete handoff to operations and support teams.
|
||||
| Date (UTC) | Update | Owner |
|
||||
|------------|--------|-------|
|
||||
| 2025-12-20 | Sprint file created. | Agent |
|
||||
| 2025-12-20 | T1 DONE: Created docs/api/score-proofs-reachability-api-reference.md | Agent |
|
||||
| 2025-12-20 | T2 DONE: Created 4 runbooks (score-proofs, reachability, unknowns, airgap) | Agent |
|
||||
| 2025-12-20 | T3 DONE: Updated HIGH_LEVEL_ARCHITECTURE.md with sections 4A/4B/4C | Agent |
|
||||
| 2025-12-20 | T4 DONE: Created 3 CLI references (score-proofs, reachability, unknowns) | Agent |
|
||||
| 2025-12-20 | T5 DOING: Starting training materials | Agent |
|
||||
|
||||
---
|
||||
|
||||
@@ -201,4 +206,4 @@ Complete handoff to operations and support teams.
|
||||
|
||||
---
|
||||
|
||||
**Sprint Status**: TODO (0/8 tasks done)
|
||||
**Sprint Status**: IN PROGRESS (4/8 tasks done)
|
||||
|
||||
688
docs/operations/airgap-operations-runbook.md
Normal file
688
docs/operations/airgap-operations-runbook.md
Normal file
@@ -0,0 +1,688 @@
|
||||
# Air-Gap Operations Runbook
|
||||
|
||||
> **Version**: 1.0.0
|
||||
> **Sprint**: 3500.0004.0004
|
||||
> **Last Updated**: 2025-12-20
|
||||
|
||||
This runbook covers operational procedures for running StellaOps in air-gapped (offline) environments, including offline kit management, feed updates, and isolated verification workflows.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#1-overview)
|
||||
2. [Offline Kit Management](#2-offline-kit-management)
|
||||
3. [Feed Updates](#3-feed-updates)
|
||||
4. [Scanning in Air-Gap Mode](#4-scanning-in-air-gap-mode)
|
||||
5. [Verification in Air-Gap Mode](#5-verification-in-air-gap-mode)
|
||||
6. [Troubleshooting](#6-troubleshooting)
|
||||
7. [Monitoring & Health Checks](#7-monitoring--health-checks)
|
||||
8. [Escalation Procedures](#8-escalation-procedures)
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### What is Air-Gap Mode?
|
||||
|
||||
Air-gap mode allows StellaOps to operate in environments with no external network connectivity. This is required for:
|
||||
|
||||
- Classified or sensitive environments
|
||||
- High-security facilities
|
||||
- Regulatory compliance (certain industries)
|
||||
- Disaster recovery scenarios
|
||||
|
||||
### Air-Gap Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Connected Environment │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Feed Sync │───►│ Bundle │───►│ Offline Kit │ │
|
||||
│ │ Service │ │ Generator │ │ (.tar.gz) │ │
|
||||
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
|
||||
└─────────────────────────────────────────────────┼───────────┘
|
||||
│ Physical
|
||||
│ Transfer
|
||||
┌─────────────────────────────────────────────────┼───────────┐
|
||||
│ Air-Gapped Environment │ │
|
||||
│ ┌──────────────┐ ┌─────────────┐ ┌──────▼──────┐ │
|
||||
│ │ StellaOps │◄───│ Offline │◄───│ Import │ │
|
||||
│ │ Scanner │ │ Data Store │ │ Service │ │
|
||||
│ └──────────────┘ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Offline Kit Contents
|
||||
|
||||
| Component | Description | Update Frequency |
|
||||
|-----------|-------------|------------------|
|
||||
| Vulnerability Database | NVD, OSV, vendor advisories | Daily/Weekly |
|
||||
| Advisory Feeds | CVE details, EPSS scores | Daily |
|
||||
| Trust Bundles | CA certificates, signing keys | Quarterly |
|
||||
| Rules Engine | Scoring rules and policies | Monthly |
|
||||
| Offline Binaries | CLI tools, extractors | Per release |
|
||||
|
||||
---
|
||||
|
||||
## 2. Offline Kit Management
|
||||
|
||||
### 2.1 Generating an Offline Kit
|
||||
|
||||
On the connected system:
|
||||
|
||||
```bash
|
||||
# Generate full offline kit
|
||||
stella offline-kit create \
|
||||
--output /path/to/offline-kit.tar.gz \
|
||||
--include-all
|
||||
|
||||
# Generate minimal kit (feeds only)
|
||||
stella offline-kit create \
|
||||
--output /path/to/offline-kit-feeds.tar.gz \
|
||||
--feeds-only
|
||||
|
||||
# Generate with specific components
|
||||
stella offline-kit create \
|
||||
--output /path/to/offline-kit.tar.gz \
|
||||
--include vuln-db \
|
||||
--include advisories \
|
||||
--include trust-bundles \
|
||||
--include rules
|
||||
```
|
||||
|
||||
### 2.2 Kit Manifest
|
||||
|
||||
Each kit includes a manifest for verification:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"createdAt": "2025-01-15T00:00:00Z",
|
||||
"expiresAt": "2025-02-15T00:00:00Z",
|
||||
"components": {
|
||||
"vulnerability-database": {
|
||||
"hash": "sha256:abc123...",
|
||||
"size": 1073741824,
|
||||
"records": 245000
|
||||
},
|
||||
"advisory-feeds": {
|
||||
"hash": "sha256:def456...",
|
||||
"size": 536870912,
|
||||
"lastUpdate": "2025-01-15T00:00:00Z"
|
||||
},
|
||||
"trust-bundles": {
|
||||
"hash": "sha256:ghi789...",
|
||||
"size": 65536,
|
||||
"certificates": 12
|
||||
},
|
||||
"signing-keys": {
|
||||
"hash": "sha256:jkl012...",
|
||||
"keyIds": ["key-001", "key-002"]
|
||||
}
|
||||
},
|
||||
"signature": "base64-signature..."
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 Transferring to Air-Gapped Environment
|
||||
|
||||
#### Physical Media Transfer
|
||||
|
||||
```bash
|
||||
# On connected system - write to media
|
||||
cp offline-kit.tar.gz /media/secure-usb/
|
||||
sha256sum offline-kit.tar.gz > /media/secure-usb/offline-kit.tar.gz.sha256
|
||||
|
||||
# On air-gapped system - verify and import
|
||||
cd /media/secure-usb/
|
||||
sha256sum -c offline-kit.tar.gz.sha256
|
||||
stella offline-kit import --kit offline-kit.tar.gz
|
||||
```
|
||||
|
||||
#### Secure File Transfer (if available)
|
||||
|
||||
```bash
|
||||
# Using data diode or one-way transfer
|
||||
scp offline-kit.tar.gz airgap-gateway:/incoming/
|
||||
```
|
||||
|
||||
### 2.4 Installing Offline Kit
|
||||
|
||||
```bash
|
||||
# Import and install kit
|
||||
stella offline-kit import \
|
||||
--kit /path/to/offline-kit.tar.gz \
|
||||
--verify \
|
||||
--install
|
||||
|
||||
# Verify installation
|
||||
stella offline-kit status
|
||||
|
||||
# List installed components
|
||||
stella offline-kit list
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
Offline Kit Status
|
||||
══════════════════════════════════════════
|
||||
Mode: AIR-GAP
|
||||
Kit Version: 1.0.0
|
||||
Installed At: 2025-01-15T10:30:00Z
|
||||
Expires At: 2025-02-15T00:00:00Z
|
||||
|
||||
Components:
|
||||
✓ vulnerability-database 2025-01-15 245,000 records
|
||||
✓ advisory-feeds 2025-01-15 Active
|
||||
✓ trust-bundles 2025-01-15 12 certificates
|
||||
✓ signing-keys 2025-01-15 2 keys
|
||||
✓ rules-engine 2025-01-15 v2.3.0
|
||||
|
||||
Health: HEALTHY
|
||||
Days Until Expiry: 31
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Feed Updates
|
||||
|
||||
### 3.1 Update Workflow
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ Weekly Feed Update Process │
|
||||
├────────────────────────────────────────────────────────────┤
|
||||
│ Day 1: Generate kit on connected system │
|
||||
│ Day 2: Security review and approval │
|
||||
│ Day 3: Transfer to air-gapped environment │
|
||||
│ Day 4: Import and verify │
|
||||
│ Day 5: Activate new feeds │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 Generating Delta Updates
|
||||
|
||||
For faster updates, generate delta kits:
|
||||
|
||||
```bash
|
||||
# On connected system
|
||||
stella offline-kit create \
|
||||
--output delta-kit.tar.gz \
|
||||
--delta-from 2025-01-08 \
|
||||
--delta-to 2025-01-15
|
||||
|
||||
# Delta kit is smaller, contains only changes
|
||||
```
|
||||
|
||||
### 3.3 Applying Updates
|
||||
|
||||
```bash
|
||||
# Import delta update
|
||||
stella offline-kit import \
|
||||
--kit delta-kit.tar.gz \
|
||||
--delta \
|
||||
--verify
|
||||
|
||||
# Verify feed freshness
|
||||
stella feeds status
|
||||
```
|
||||
|
||||
### 3.4 Rollback Procedure
|
||||
|
||||
If an update causes issues:
|
||||
|
||||
```bash
|
||||
# List available snapshots
|
||||
stella offline-kit snapshots
|
||||
|
||||
# Rollback to previous version
|
||||
stella offline-kit rollback --to 2025-01-08
|
||||
|
||||
# Verify rollback
|
||||
stella feeds status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Scanning in Air-Gap Mode
|
||||
|
||||
### 4.1 Enabling Air-Gap Mode
|
||||
|
||||
```bash
|
||||
# Enable air-gap mode
|
||||
stella config set mode air-gap
|
||||
|
||||
# Verify mode
|
||||
stella config get mode
|
||||
# Output: air-gap
|
||||
```
|
||||
|
||||
### 4.2 Running Scans
|
||||
|
||||
```bash
|
||||
# Scan a local image (no registry pull)
|
||||
stella scan image --local /path/to/image.tar
|
||||
|
||||
# Scan from local registry
|
||||
stella scan image localhost:5000/myapp:v1.0
|
||||
|
||||
# Scan with offline feeds explicitly
|
||||
stella scan image myapp:v1.0 --offline-feeds
|
||||
```
|
||||
|
||||
### 4.3 Local Image Preparation
|
||||
|
||||
For images that need to be scanned:
|
||||
|
||||
```bash
|
||||
# On connected system - save image
|
||||
docker save myapp:v1.0 -o myapp-v1.0.tar
|
||||
sha256sum myapp-v1.0.tar > myapp-v1.0.tar.sha256
|
||||
|
||||
# Transfer to air-gapped system
|
||||
# ... physical transfer ...
|
||||
|
||||
# On air-gapped system - verify and load
|
||||
sha256sum -c myapp-v1.0.tar.sha256
|
||||
docker load -i myapp-v1.0.tar
|
||||
stella scan image myapp:v1.0 --local
|
||||
```
|
||||
|
||||
### 4.4 SBOM Generation
|
||||
|
||||
```bash
|
||||
# Generate SBOM for local image
|
||||
stella sbom generate --image myapp:v1.0 --output sbom.json
|
||||
|
||||
# Scan existing SBOM
|
||||
stella scan sbom --file sbom.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Verification in Air-Gap Mode
|
||||
|
||||
### 5.1 Offline Proof Verification
|
||||
|
||||
```bash
|
||||
# Verify proof bundle offline
|
||||
stella proof verify --bundle bundle.tar.gz --offline
|
||||
|
||||
# Verify with explicit trust store
|
||||
stella proof verify --bundle bundle.tar.gz \
|
||||
--offline \
|
||||
--trust-store /etc/stellaops/offline/trust-roots.json
|
||||
```
|
||||
|
||||
### 5.2 Preparing Trust Store
|
||||
|
||||
Before air-gapped deployment:
|
||||
|
||||
```bash
|
||||
# On connected system - export trust configuration
|
||||
stella trust export \
|
||||
--output trust-roots.json \
|
||||
--include-ca \
|
||||
--include-signing-keys
|
||||
|
||||
# Transfer to air-gapped system
|
||||
# ... physical transfer ...
|
||||
|
||||
# On air-gapped system - import trust
|
||||
stella trust import --file trust-roots.json
|
||||
```
|
||||
|
||||
### 5.3 Score Replay Offline
|
||||
|
||||
```bash
|
||||
# Replay score using offline data
|
||||
stella score replay --scan $SCAN_ID --offline
|
||||
|
||||
# Replay with frozen time
|
||||
stella score replay --scan $SCAN_ID \
|
||||
--offline \
|
||||
--freeze 2025-01-15T00:00:00Z
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
|
||||
### 6.1 Kit Import Fails
|
||||
|
||||
**Symptoms**: `Failed to import offline kit`
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Verify kit integrity:
|
||||
```bash
|
||||
sha256sum offline-kit.tar.gz
|
||||
# Compare with manifest
|
||||
```
|
||||
|
||||
2. Check kit signature:
|
||||
```bash
|
||||
stella offline-kit verify --kit offline-kit.tar.gz
|
||||
```
|
||||
|
||||
3. Check disk space:
|
||||
```bash
|
||||
df -h /var/lib/stellaops/
|
||||
```
|
||||
|
||||
**Common Causes**:
|
||||
|
||||
| Cause | Resolution |
|
||||
|-------|------------|
|
||||
| Corrupted transfer | Re-transfer, verify checksum |
|
||||
| Invalid signature | Regenerate kit with valid signing key |
|
||||
| Insufficient space | Free disk space or expand volume |
|
||||
| Expired kit | Generate fresh kit |
|
||||
|
||||
### 6.2 Stale Feed Data
|
||||
|
||||
**Symptoms**: Scans report old vulnerabilities, miss new CVEs
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Check feed age:
|
||||
```bash
|
||||
stella feeds status
|
||||
```
|
||||
|
||||
2. Verify last update:
|
||||
```bash
|
||||
stella offline-kit status | grep "Installed At"
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
- Generate and import fresh offline kit
|
||||
- Establish regular update schedule
|
||||
- Set up expiry alerts
|
||||
|
||||
### 6.3 Trust Verification Fails
|
||||
|
||||
**Symptoms**: `Certificate chain verification failed` in offline mode
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Check trust store:
|
||||
```bash
|
||||
stella trust list
|
||||
```
|
||||
|
||||
2. Verify CA bundle:
|
||||
```bash
|
||||
openssl verify -CAfile /etc/stellaops/offline/ca-bundle.pem \
|
||||
/path/to/certificate.pem
|
||||
```
|
||||
|
||||
3. Check for expired roots:
|
||||
```bash
|
||||
stella trust check-expiry
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
- Update trust bundle in offline kit
|
||||
- Import new CA certificates
|
||||
- Rotate expired signing keys
|
||||
|
||||
### 6.4 Network Access Attempted
|
||||
|
||||
**Symptoms**: Air-gapped system attempts network connection
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Check mode configuration:
|
||||
```bash
|
||||
stella config get mode
|
||||
```
|
||||
|
||||
2. Audit network attempts:
|
||||
```bash
|
||||
journalctl -u stellaops | grep -i "network\|connect\|http"
|
||||
```
|
||||
|
||||
3. Verify no external URLs in config:
|
||||
```bash
|
||||
grep -r "http" /etc/stellaops/
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
- Ensure `mode: air-gap` in configuration
|
||||
- Remove any hardcoded URLs
|
||||
- Block outbound traffic at firewall level
|
||||
|
||||
---
|
||||
|
||||
## 7. Monitoring & Health Checks
|
||||
|
||||
### 7.1 Air-Gap Health Checks
|
||||
|
||||
```bash
|
||||
# Run comprehensive health check
|
||||
stella health check --air-gap
|
||||
|
||||
# Output
|
||||
Air-Gap Health Check
|
||||
══════════════════════════════════════════
|
||||
✓ Mode: air-gap
|
||||
✓ Feed Freshness: 7 days old (OK)
|
||||
✓ Trust Store: Valid
|
||||
✓ Signing Keys: 2 active
|
||||
✓ Disk Space: 45% used
|
||||
✓ Database: Healthy
|
||||
⚠ Kit Expiry: 24 days remaining
|
||||
|
||||
Overall: HEALTHY (1 warning)
|
||||
```
|
||||
|
||||
### 7.2 Automated Monitoring Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# /etc/stellaops/scripts/airgap-health.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Check feed age
|
||||
FEED_AGE=$(stella feeds age --days)
|
||||
if [ "$FEED_AGE" -gt 14 ]; then
|
||||
echo "CRITICAL: Feeds are $FEED_AGE days old"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Check kit expiry
|
||||
DAYS_LEFT=$(stella offline-kit days-until-expiry)
|
||||
if [ "$DAYS_LEFT" -lt 7 ]; then
|
||||
echo "WARNING: Kit expires in $DAYS_LEFT days"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check trust store
|
||||
if ! stella trust verify --quiet; then
|
||||
echo "CRITICAL: Trust store verification failed"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "OK: Air-gap health check passed"
|
||||
exit 0
|
||||
```
|
||||
|
||||
### 7.3 Metrics for Air-Gap
|
||||
|
||||
| Metric | Description | Alert Threshold |
|
||||
|--------|-------------|-----------------|
|
||||
| `offline_kit_age_days` | Days since kit import | > 14 days |
|
||||
| `offline_kit_expiry_days` | Days until kit expires | < 7 days |
|
||||
| `feed_freshness_days` | Age of vulnerability feeds | > 7 days |
|
||||
| `trust_store_valid` | Trust store validity (0/1) | = 0 |
|
||||
| `disk_usage_percent` | Data store disk usage | > 80% |
|
||||
|
||||
### 7.4 Alert Configuration
|
||||
|
||||
```yaml
|
||||
# /etc/stellaops/alerts/airgap.yaml
|
||||
alerts:
|
||||
- name: offline_kit_expiring
|
||||
condition: offline_kit_expiry_days < 7
|
||||
severity: warning
|
||||
message: "Offline kit expires in {{ .Value }} days"
|
||||
|
||||
- name: offline_kit_expired
|
||||
condition: offline_kit_expiry_days <= 0
|
||||
severity: critical
|
||||
message: "Offline kit has expired"
|
||||
|
||||
- name: feeds_stale
|
||||
condition: feed_freshness_days > 14
|
||||
severity: warning
|
||||
message: "Vulnerability feeds are {{ .Value }} days old"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Escalation Procedures
|
||||
|
||||
### 8.1 Escalation Matrix
|
||||
|
||||
| Severity | Condition | Response Time | Action |
|
||||
|----------|-----------|---------------|--------|
|
||||
| P1 - Critical | Offline kit expired | 4 hours | Emergency kit transfer |
|
||||
| P1 - Critical | Trust store invalid | 4 hours | Restore from backup |
|
||||
| P2 - High | Feeds > 14 days old | 24 hours | Schedule kit update |
|
||||
| P3 - Medium | Kit expiring in < 7 days | 48 hours | Plan kit update |
|
||||
| P4 - Low | Minor health check warnings | Next maintenance | Review and address |
|
||||
|
||||
### 8.2 Emergency Kit Update Process
|
||||
|
||||
When kit expires before scheduled update:
|
||||
|
||||
1. **On Connected System** (0-2 hours):
|
||||
```bash
|
||||
stella offline-kit create --output emergency-kit.tar.gz --include-all
|
||||
```
|
||||
|
||||
2. **Security Review** (2-4 hours):
|
||||
- Verify kit signature
|
||||
- Check for known vulnerabilities in kit
|
||||
- Get approval for transfer
|
||||
|
||||
3. **Transfer** (4-6 hours):
|
||||
- Physical media preparation
|
||||
- Chain of custody documentation
|
||||
- Transfer to air-gapped environment
|
||||
|
||||
4. **Import** (6-8 hours):
|
||||
```bash
|
||||
stella offline-kit import --kit emergency-kit.tar.gz --verify --install
|
||||
```
|
||||
|
||||
### 8.3 Contacts
|
||||
|
||||
| Role | Contact | Availability |
|
||||
|------|---------|--------------|
|
||||
| Air-Gap Operations | airgap-ops@stellaops.io | Business hours |
|
||||
| Security Team | security@stellaops.io | Business hours |
|
||||
| Platform On-Call | platform-oncall@stellaops.io | 24/7 |
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Configuration Reference
|
||||
|
||||
### Air-Gap Mode Configuration
|
||||
|
||||
```yaml
|
||||
# /etc/stellaops/config.yaml
|
||||
mode: air-gap
|
||||
|
||||
offline:
|
||||
dataDir: /var/lib/stellaops/offline
|
||||
feedsDir: /var/lib/stellaops/offline/feeds
|
||||
trustStore: /etc/stellaops/offline/trust-roots.json
|
||||
caBundle: /etc/stellaops/offline/ca-bundle.pem
|
||||
|
||||
# Disable all external network calls
|
||||
disableNetworking: true
|
||||
|
||||
# Kit expiry settings
|
||||
kit:
|
||||
expiryWarningDays: 7
|
||||
maxAgeDays: 30
|
||||
|
||||
# Feed freshness settings
|
||||
feeds:
|
||||
maxAgeDays: 14
|
||||
warnAgeDays: 7
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# Air-gap specific
|
||||
export STELLAOPS_MODE=air-gap
|
||||
export STELLAOPS_OFFLINE_DATA_DIR=/var/lib/stellaops/offline
|
||||
export STELLAOPS_DISABLE_NETWORKING=true
|
||||
|
||||
# Trust configuration
|
||||
export STELLAOPS_TRUST_STORE=/etc/stellaops/offline/trust-roots.json
|
||||
export STELLAOPS_CA_BUNDLE=/etc/stellaops/offline/ca-bundle.pem
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appendix B: CLI Quick Reference
|
||||
|
||||
```bash
|
||||
# Offline Kit Commands
|
||||
stella offline-kit create --output <path> # Generate kit
|
||||
stella offline-kit import --kit <path> # Import kit
|
||||
stella offline-kit status # Show status
|
||||
stella offline-kit verify --kit <path> # Verify kit
|
||||
stella offline-kit rollback --to <date> # Rollback feeds
|
||||
|
||||
# Feed Commands (Air-Gap)
|
||||
stella feeds status # Show feed status
|
||||
stella feeds age --days # Get feed age
|
||||
|
||||
# Trust Commands
|
||||
stella trust list # List trust roots
|
||||
stella trust import --file <path> # Import trust config
|
||||
stella trust verify # Verify trust store
|
||||
stella trust export --output <path> # Export trust config
|
||||
|
||||
# Scanning (Air-Gap)
|
||||
stella scan image --local <path> # Scan local image
|
||||
stella scan sbom --file <path> # Scan SBOM file
|
||||
|
||||
# Verification (Air-Gap)
|
||||
stella proof verify --bundle <path> --offline # Offline verification
|
||||
stella score replay --scan <id> --offline # Offline replay
|
||||
|
||||
# Health
|
||||
stella health check --air-gap # Air-gap health check
|
||||
stella config get mode # Check current mode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appendix C: Update Schedule Template
|
||||
|
||||
| Week | Activity | Owner | Notes |
|
||||
|------|----------|-------|-------|
|
||||
| 1 | Generate kit | Connected Ops | Monday |
|
||||
| 1 | Security review | Security Team | Tuesday |
|
||||
| 1 | Approval | Security Lead | Wednesday |
|
||||
| 2 | Transfer | Air-Gap Ops | Monday |
|
||||
| 2 | Import & verify | Air-Gap Ops | Tuesday |
|
||||
| 2 | Activate | Air-Gap Ops | Wednesday |
|
||||
| 2 | Validate scans | QA Team | Thursday |
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
| Version | Date | Author | Changes |
|
||||
|---------|------|--------|---------|
|
||||
| 1.0.0 | 2025-12-20 | Agent | Initial release |
|
||||
630
docs/operations/proof-verification-runbook.md
Normal file
630
docs/operations/proof-verification-runbook.md
Normal file
@@ -0,0 +1,630 @@
|
||||
# Proof Verification Operations Runbook
|
||||
|
||||
> **Version**: 1.0.0
|
||||
> **Sprint**: 3500.0004.0004
|
||||
> **Last Updated**: 2025-12-20
|
||||
|
||||
This runbook covers operational procedures for Proof Verification, including DSSE signature validation, Merkle tree verification, transparency log checks, and offline verification workflows.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#1-overview)
|
||||
2. [Verification Operations](#2-verification-operations)
|
||||
3. [Offline Verification](#3-offline-verification)
|
||||
4. [Transparency Log Integration](#4-transparency-log-integration)
|
||||
5. [Troubleshooting](#5-troubleshooting)
|
||||
6. [Monitoring & Alerting](#6-monitoring--alerting)
|
||||
7. [Escalation Procedures](#7-escalation-procedures)
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### What is Proof Verification?
|
||||
|
||||
Proof Verification is the process of cryptographically validating that a scan result has not been tampered with and was produced by an authorized StellaOps instance. It involves:
|
||||
|
||||
- **DSSE Signature Verification**: Validate the signing envelope
|
||||
- **Merkle Tree Verification**: Confirm the root hash matches the proof
|
||||
- **Certificate Chain Validation**: Verify the signing certificate
|
||||
- **Transparency Log Check**: Optional Rekor/Sigstore verification
|
||||
|
||||
### Verification Components
|
||||
|
||||
| Component | Purpose | Verification Type |
|
||||
|-----------|---------|-------------------|
|
||||
| DSSE Envelope | Contains signed payload | Signature validation |
|
||||
| Merkle Proof | Cryptographic proof of inclusion | Hash verification |
|
||||
| Certificate | Signing identity | Chain validation |
|
||||
| Rekor Entry | Transparency log record | Log inclusion proof |
|
||||
|
||||
### Trust Model
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Trust Hierarchy │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Root CA (Offline) │
|
||||
│ └── Intermediate CA │
|
||||
│ └── Signing Certificate (Scanner Instance) │
|
||||
│ └── DSSE Envelope │
|
||||
│ └── Proof Bundle │
|
||||
│ └── Manifest + Score │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Verification Operations
|
||||
|
||||
### 2.1 Basic Proof Verification
|
||||
|
||||
#### Via CLI
|
||||
|
||||
```bash
|
||||
# Verify a proof bundle file
|
||||
stella proof verify --bundle bundle.tar.gz
|
||||
|
||||
# Verify with verbose output
|
||||
stella proof verify --bundle bundle.tar.gz --verbose
|
||||
|
||||
# Verify and output as JSON
|
||||
stella proof verify --bundle bundle.tar.gz --output json
|
||||
```
|
||||
|
||||
#### Expected Output (Success)
|
||||
|
||||
```
|
||||
Proof Verification Result
|
||||
══════════════════════════════════════════
|
||||
✓ DSSE Signature VALID
|
||||
✓ Merkle Root VALID
|
||||
✓ Certificate Chain VALID
|
||||
✓ Not Expired VALID
|
||||
──────────────────────────────────────────
|
||||
Overall: VERIFIED
|
||||
|
||||
Root Hash: sha256:abc123...
|
||||
Signed By: scanner-prod-01.stellaops.local
|
||||
Signed At: 2025-01-15T10:30:00Z
|
||||
Valid Until: 2026-01-15T10:30:00Z
|
||||
```
|
||||
|
||||
#### Expected Output (Failure)
|
||||
|
||||
```
|
||||
Proof Verification Result
|
||||
══════════════════════════════════════════
|
||||
✓ DSSE Signature VALID
|
||||
✗ Merkle Root INVALID
|
||||
✓ Certificate Chain VALID
|
||||
✓ Not Expired VALID
|
||||
──────────────────────────────────────────
|
||||
Overall: FAILED
|
||||
|
||||
Error: Merkle root mismatch
|
||||
Expected: sha256:abc123...
|
||||
Actual: sha256:def456...
|
||||
```
|
||||
|
||||
### 2.2 Verification via API
|
||||
|
||||
```bash
|
||||
# Verify by scan ID
|
||||
curl -X POST "https://scanner.stellaops.local/api/v1/scanner/scans/$SCAN_ID/proofs/$ROOT_HASH/verify" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json"
|
||||
|
||||
# Response
|
||||
{
|
||||
"valid": true,
|
||||
"rootHash": "sha256:abc123...",
|
||||
"checks": [
|
||||
{"name": "dsse_signature", "passed": true, "message": "Signature valid"},
|
||||
{"name": "merkle_root", "passed": true, "message": "Root hash matches"},
|
||||
{"name": "certificate_chain", "passed": true, "message": "Chain valid"},
|
||||
{"name": "not_expired", "passed": true, "message": "Certificate not expired"}
|
||||
],
|
||||
"verifiedAt": "2025-01-16T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 Viewing Merkle Spine
|
||||
|
||||
The Merkle spine shows the path from leaf nodes to the root:
|
||||
|
||||
```bash
|
||||
stella proof spine --bundle bundle.tar.gz
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Merkle Tree Spine
|
||||
══════════════════════════════════════════
|
||||
Root: sha256:abc123...
|
||||
├── sha256:node1... (sbom_hash)
|
||||
├── sha256:node2... (rules_hash)
|
||||
├── sha256:node3... (policy_hash)
|
||||
└── sha256:node4... (feed_hash)
|
||||
|
||||
Depth: 3
|
||||
Leaves: 4
|
||||
Algorithm: SHA-256
|
||||
```
|
||||
|
||||
### 2.4 Certificate Inspection
|
||||
|
||||
```bash
|
||||
# Extract and inspect certificate
|
||||
tar -xzf bundle.tar.gz
|
||||
openssl x509 -in bundle/certificate.pem -noout -text
|
||||
|
||||
# Check validity period
|
||||
openssl x509 -in bundle/certificate.pem -noout -dates
|
||||
|
||||
# Verify against CA bundle
|
||||
openssl verify -CAfile /etc/stellaops/ca-bundle.pem bundle/certificate.pem
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Offline Verification
|
||||
|
||||
### 3.1 When to Use Offline Verification
|
||||
|
||||
- Air-gapped environments
|
||||
- Network-restricted systems
|
||||
- Compliance audits without API access
|
||||
- Disaster recovery scenarios
|
||||
|
||||
### 3.2 Prerequisites for Offline Verification
|
||||
|
||||
Required files:
|
||||
- Proof bundle (`.tar.gz`)
|
||||
- CA certificate bundle (`ca-bundle.pem`)
|
||||
- Trust root configuration (`trust-roots.json`)
|
||||
|
||||
```bash
|
||||
# Prepare offline verification kit
|
||||
stella proof offline-kit create \
|
||||
--output /path/to/offline-kit/ \
|
||||
--include-ca \
|
||||
--include-trust-roots
|
||||
```
|
||||
|
||||
Kit contents:
|
||||
```
|
||||
offline-kit/
|
||||
├── ca-bundle.pem # Certificate authority chain
|
||||
├── trust-roots.json # Trusted signing keys
|
||||
├── verify.sh # Standalone verification script
|
||||
└── README.md # Instructions
|
||||
```
|
||||
|
||||
### 3.3 Running Offline Verification
|
||||
|
||||
```bash
|
||||
# Using CLI with offline flag
|
||||
stella proof verify --bundle bundle.tar.gz --offline
|
||||
|
||||
# Using standalone script
|
||||
./verify.sh bundle.tar.gz
|
||||
|
||||
# Manual verification with OpenSSL
|
||||
./verify.sh bundle.tar.gz --ca-bundle ./ca-bundle.pem
|
||||
```
|
||||
|
||||
### 3.4 Offline Verification Checks
|
||||
|
||||
| Check | Online | Offline | Notes |
|
||||
|-------|--------|---------|-------|
|
||||
| DSSE Signature | ✓ | ✓ | Local crypto |
|
||||
| Merkle Root | ✓ | ✓ | Local hash computation |
|
||||
| Certificate Chain | ✓ | ✓ | Requires CA bundle |
|
||||
| Certificate Revocation | ✓ | ✗ | Needs CRL/OCSP |
|
||||
| Rekor Transparency | ✓ | ✗ | Needs network |
|
||||
|
||||
### 3.5 Air-Gap Considerations
|
||||
|
||||
For fully air-gapped environments:
|
||||
|
||||
1. **Pre-stage CA bundle**:
|
||||
```bash
|
||||
# On connected system
|
||||
stella ca export --output ca-bundle.pem
|
||||
|
||||
# Transfer to air-gapped system
|
||||
scp ca-bundle.pem airgap:/etc/stellaops/
|
||||
```
|
||||
|
||||
2. **Pre-stage CRL (optional)**:
|
||||
```bash
|
||||
# Download latest CRL
|
||||
curl -o crl.pem https://ca.stellaops.io/crl/latest.pem
|
||||
|
||||
# Transfer and use
|
||||
stella proof verify --bundle bundle.tar.gz --offline --crl crl.pem
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Transparency Log Integration
|
||||
|
||||
### 4.1 Rekor Overview
|
||||
|
||||
StellaOps optionally publishes proof attestations to Sigstore Rekor for immutable transparency logging.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Transparency Flow │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Proof Bundle │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ DSSE Envelope ──────► Rekor ──────► Inclusion Proof │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ Local Verify Log Entry ID │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 Checking Rekor Entry
|
||||
|
||||
```bash
|
||||
# Verify with Rekor check
|
||||
stella proof verify --bundle bundle.tar.gz --check-rekor
|
||||
|
||||
# Get Rekor entry details
|
||||
stella proof rekor-entry --bundle bundle.tar.gz
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Rekor Entry
|
||||
══════════════════════════════════════════
|
||||
Log Index: 12345678
|
||||
Entry UUID: 24296fb24b8ad77a...
|
||||
Log ID: c0d23d6ad406973f...
|
||||
Integrated: 2025-01-15T10:30:05Z
|
||||
|
||||
Inclusion Proof:
|
||||
Root Hash: sha256:rekor-root...
|
||||
Tree Size: 98765432
|
||||
Hashes: [sha256:a1b2..., sha256:c3d4...]
|
||||
|
||||
Verification: ✓ INCLUDED
|
||||
```
|
||||
|
||||
### 4.3 Manual Rekor Verification
|
||||
|
||||
```bash
|
||||
# Using rekor-cli
|
||||
rekor-cli verify --artifact bundle.tar.gz \
|
||||
--signature bundle/dsse-envelope.json \
|
||||
--public-key bundle/certificate.pem
|
||||
|
||||
# Search for entries
|
||||
rekor-cli search --sha sha256:abc123...
|
||||
```
|
||||
|
||||
### 4.4 When Rekor is Unavailable
|
||||
|
||||
If Rekor is temporarily unavailable:
|
||||
|
||||
1. Verification still succeeds for DSSE and Merkle checks
|
||||
2. Rekor check is marked as "SKIPPED"
|
||||
3. Re-verify later when Rekor is available
|
||||
|
||||
```bash
|
||||
# Skip Rekor check
|
||||
stella proof verify --bundle bundle.tar.gz --skip-rekor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Troubleshooting
|
||||
|
||||
### 5.1 DSSE Signature Invalid
|
||||
|
||||
**Symptoms**: `DSSE signature verification failed`
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Extract and inspect envelope:
|
||||
```bash
|
||||
tar -xzf bundle.tar.gz
|
||||
cat bundle/dsse-envelope.json | jq .
|
||||
```
|
||||
|
||||
2. Check payload type:
|
||||
```bash
|
||||
cat bundle/dsse-envelope.json | jq -r '.payloadType'
|
||||
# Expected: application/vnd.stellaops.proof+json
|
||||
```
|
||||
|
||||
3. Verify signature format:
|
||||
```bash
|
||||
cat bundle/dsse-envelope.json | jq '.signatures[0].sig' | base64 -d | xxd | head
|
||||
```
|
||||
|
||||
**Common Causes**:
|
||||
|
||||
| Cause | Resolution |
|
||||
|-------|------------|
|
||||
| Corrupted bundle | Re-download from API |
|
||||
| Wrong public key | Check trust roots configuration |
|
||||
| Signature algorithm mismatch | Verify ECDSA-P256 or RSA support |
|
||||
| Encoding issue | Check Base64 encoding |
|
||||
|
||||
### 5.2 Merkle Root Mismatch
|
||||
|
||||
**Symptoms**: `Merkle root does not match expected value`
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Recompute Merkle root locally:
|
||||
```bash
|
||||
stella proof compute-root --bundle bundle.tar.gz
|
||||
```
|
||||
|
||||
2. Compare manifest hashes:
|
||||
```bash
|
||||
cat bundle/manifest.json | jq '.hashes'
|
||||
```
|
||||
|
||||
3. Check for trailing whitespace or encoding:
|
||||
```bash
|
||||
sha256sum bundle/manifest.json
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
- Bundle may have been modified after signing
|
||||
- Re-export bundle from source system
|
||||
- If legitimate change, re-sign bundle
|
||||
|
||||
### 5.3 Certificate Chain Validation Failed
|
||||
|
||||
**Symptoms**: `Certificate chain verification failed`
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Check certificate expiry:
|
||||
```bash
|
||||
openssl x509 -in bundle/certificate.pem -noout -dates
|
||||
```
|
||||
|
||||
2. Verify chain:
|
||||
```bash
|
||||
openssl verify -verbose -CAfile /etc/stellaops/ca-bundle.pem bundle/certificate.pem
|
||||
```
|
||||
|
||||
3. Check for missing intermediates:
|
||||
```bash
|
||||
openssl x509 -in bundle/certificate.pem -noout -issuer
|
||||
```
|
||||
|
||||
**Common Errors**:
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `certificate has expired` | Cert past validity | Re-sign with valid cert |
|
||||
| `unable to get issuer certificate` | Missing intermediate | Update CA bundle |
|
||||
| `certificate revoked` | Key compromised | Use new signing key |
|
||||
| `self-signed certificate` | Wrong trust root | Import correct CA |
|
||||
|
||||
### 5.4 Bundle Extraction Fails
|
||||
|
||||
**Symptoms**: `Failed to extract bundle` or `Invalid archive format`
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Check file type:
|
||||
```bash
|
||||
file bundle.tar.gz
|
||||
```
|
||||
|
||||
2. Test archive integrity:
|
||||
```bash
|
||||
gzip -t bundle.tar.gz
|
||||
tar -tzf bundle.tar.gz
|
||||
```
|
||||
|
||||
3. Check for truncation:
|
||||
```bash
|
||||
ls -la bundle.tar.gz
|
||||
# Compare with expected size from API
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
- Re-download if corrupted
|
||||
- Check network transfer (use checksums)
|
||||
- Verify sufficient disk space
|
||||
|
||||
---
|
||||
|
||||
## 6. Monitoring & Alerting
|
||||
|
||||
### 6.1 Key Metrics
|
||||
|
||||
| Metric | Description | Alert Threshold |
|
||||
|--------|-------------|-----------------|
|
||||
| `proof_verification_total` | Total verifications | Baseline |
|
||||
| `proof_verification_failures` | Failed verifications | > 5/hour |
|
||||
| `proof_verification_duration_ms` | Verification latency | p99 > 5s |
|
||||
| `certificate_expiry_days` | Days until cert expiry | < 30 days |
|
||||
| `rekor_verification_failures` | Rekor check failures | > 0 (warning) |
|
||||
|
||||
### 6.2 Grafana Queries
|
||||
|
||||
```promql
|
||||
# Verification success rate
|
||||
sum(rate(proof_verification_success_total[1h])) /
|
||||
sum(rate(proof_verification_total[1h])) * 100
|
||||
|
||||
# Verification latency
|
||||
histogram_quantile(0.99, rate(proof_verification_duration_ms_bucket[5m]))
|
||||
|
||||
# Certificate expiry countdown
|
||||
min(certificate_expiry_days) by (certificate_id)
|
||||
|
||||
# Failures by type
|
||||
sum by (failure_reason) (rate(proof_verification_failures_total[1h]))
|
||||
```
|
||||
|
||||
### 6.3 Alert Rules
|
||||
|
||||
```yaml
|
||||
groups:
|
||||
- name: proof-verification
|
||||
rules:
|
||||
- alert: ProofVerificationFailuresHigh
|
||||
expr: rate(proof_verification_failures_total[1h]) > 5
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: High proof verification failure rate
|
||||
|
||||
- alert: SigningCertificateExpiringSoon
|
||||
expr: certificate_expiry_days < 30
|
||||
for: 1h
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: Signing certificate expires in {{ $value }} days
|
||||
|
||||
- alert: SigningCertificateExpired
|
||||
expr: certificate_expiry_days <= 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: Signing certificate has expired
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Escalation Procedures
|
||||
|
||||
### 7.1 Escalation Matrix
|
||||
|
||||
| Severity | Condition | Response Time | Escalate To |
|
||||
|----------|-----------|---------------|-------------|
|
||||
| P1 - Critical | Signing certificate expired | Immediate | Security Team + Platform Lead |
|
||||
| P1 - Critical | Mass verification failures | 15 minutes | Platform Team |
|
||||
| P2 - High | Rekor unavailable | 1 hour | Platform Team |
|
||||
| P3 - Medium | Single verification failure | 4 hours | Support Queue |
|
||||
| P4 - Low | Certificate expiring (>7 days) | Next sprint | Security Team |
|
||||
|
||||
### 7.2 P1: Certificate Expired Response
|
||||
|
||||
1. **Immediate Actions** (0-15 min):
|
||||
- Stop accepting new scans (if signing required)
|
||||
- Notify stakeholders
|
||||
- Begin emergency certificate rotation
|
||||
|
||||
2. **Certificate Rotation** (15-60 min):
|
||||
```bash
|
||||
# Generate new certificate
|
||||
stella signer cert rotate --emergency
|
||||
|
||||
# Verify new certificate
|
||||
stella signer cert show --current
|
||||
|
||||
# Resume operations
|
||||
stella signer status
|
||||
```
|
||||
|
||||
3. **Post-Incident**:
|
||||
- Implement certificate expiry monitoring
|
||||
- Schedule proactive rotations
|
||||
- Update runbooks
|
||||
|
||||
### 7.3 Contacts
|
||||
|
||||
| Role | Contact | Availability |
|
||||
|------|---------|--------------|
|
||||
| Security Team | security@stellaops.io | Business hours |
|
||||
| Platform On-Call | platform-oncall@stellaops.io | 24/7 |
|
||||
| Attestor Team | attestor-team@stellaops.io | Business hours |
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: DSSE Envelope Format
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.stellaops.proof+json",
|
||||
"payload": "<base64-encoded-proof>",
|
||||
"signatures": [
|
||||
{
|
||||
"keyid": "sha256:signing-key-fingerprint",
|
||||
"sig": "<base64-encoded-signature>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Payload Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"_type": "https://stellaops.io/proof/v1",
|
||||
"subject": [
|
||||
{
|
||||
"name": "scan-123",
|
||||
"digest": {
|
||||
"sha256": "abc123..."
|
||||
}
|
||||
}
|
||||
],
|
||||
"predicateType": "https://stellaops.io/attestation/score/v1",
|
||||
"predicate": {
|
||||
"manifest": {
|
||||
"sbomHash": "sha256:...",
|
||||
"rulesHash": "sha256:...",
|
||||
"policyHash": "sha256:...",
|
||||
"feedHash": "sha256:..."
|
||||
},
|
||||
"score": 7.5,
|
||||
"rootHash": "sha256:...",
|
||||
"timestamp": "2025-01-15T10:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appendix B: CLI Quick Reference
|
||||
|
||||
```bash
|
||||
# Verification Commands
|
||||
stella proof verify --bundle <path> # Verify bundle
|
||||
stella proof verify --bundle <path> --offline # Offline verification
|
||||
stella proof verify --bundle <path> --verbose # Detailed output
|
||||
stella proof verify --bundle <path> --check-rekor # Include Rekor check
|
||||
|
||||
# Inspection Commands
|
||||
stella proof spine --bundle <path> # Show Merkle tree
|
||||
stella proof show --bundle <path> # Show bundle contents
|
||||
stella proof rekor-entry --bundle <path> # Show Rekor entry
|
||||
|
||||
# Offline Kit
|
||||
stella proof offline-kit create --output <dir> # Create offline kit
|
||||
stella proof offline-kit verify --kit <dir> --bundle <path> # Use kit
|
||||
|
||||
# Certificate Commands
|
||||
stella signer cert show # Show current cert
|
||||
stella signer cert rotate # Rotate certificate
|
||||
stella signer cert export --output <path> # Export public cert
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
| Version | Date | Author | Changes |
|
||||
|---------|------|--------|---------|
|
||||
| 1.0.0 | 2025-12-20 | Agent | Initial release |
|
||||
518
docs/operations/score-replay-runbook.md
Normal file
518
docs/operations/score-replay-runbook.md
Normal file
@@ -0,0 +1,518 @@
|
||||
# Score Replay Operations Runbook
|
||||
|
||||
> **Version**: 1.0.0
|
||||
> **Sprint**: 3500.0004.0004
|
||||
> **Last Updated**: 2025-12-20
|
||||
|
||||
This runbook covers operational procedures for Score Replay, including deterministic score computation verification, proof bundle validation, and troubleshooting replay discrepancies.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#1-overview)
|
||||
2. [Score Replay Operations](#2-score-replay-operations)
|
||||
3. [Determinism Verification](#3-determinism-verification)
|
||||
4. [Proof Bundle Management](#4-proof-bundle-management)
|
||||
5. [Troubleshooting](#5-troubleshooting)
|
||||
6. [Monitoring & Alerting](#6-monitoring--alerting)
|
||||
7. [Escalation Procedures](#7-escalation-procedures)
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### What is Score Replay?
|
||||
|
||||
Score Replay is the ability to re-execute a vulnerability score computation using the exact same inputs (SBOM, rules, policies, feeds) that were used in the original scan. This provides:
|
||||
|
||||
- **Auditability**: Prove that a score was computed correctly
|
||||
- **Determinism verification**: Confirm that identical inputs produce identical outputs
|
||||
- **Compliance evidence**: Generate proof bundles for regulatory requirements
|
||||
- **Dispute resolution**: Verify contested scan results
|
||||
|
||||
### Key Concepts
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Manifest** | Content-addressed record of all scoring inputs (SBOM hash, rules hash, policy hash, feed hash) |
|
||||
| **Proof Bundle** | Signed attestation containing manifest, score, and Merkle proof |
|
||||
| **Root Hash** | Merkle tree root computed from all input hashes |
|
||||
| **DSSE Envelope** | Dead Simple Signing Envelope containing the signed proof |
|
||||
| **Freeze Timestamp** | Optional timestamp to replay scoring at a specific point in time |
|
||||
|
||||
### Architecture Components
|
||||
|
||||
| Component | Purpose | Location |
|
||||
|-----------|---------|----------|
|
||||
| Score Engine | Computes vulnerability scores | Scanner Worker |
|
||||
| Manifest Store | Persists scoring manifests | `scanner.manifest` table |
|
||||
| Proof Chain | Generates Merkle proofs | Attestor library |
|
||||
| Signer | Signs proof bundles (DSSE) | Signer service |
|
||||
|
||||
---
|
||||
|
||||
## 2. Score Replay Operations
|
||||
|
||||
### 2.1 Triggering a Score Replay
|
||||
|
||||
#### Via CLI
|
||||
|
||||
```bash
|
||||
# Basic replay
|
||||
stella score replay --scan <scan-id>
|
||||
|
||||
# Replay with specific manifest
|
||||
stella score replay --scan <scan-id> --manifest-hash sha256:abc123...
|
||||
|
||||
# Replay with frozen timestamp (for determinism testing)
|
||||
stella score replay --scan <scan-id> --freeze 2025-01-15T00:00:00Z
|
||||
|
||||
# Output as JSON
|
||||
stella score replay --scan <scan-id> --output json
|
||||
```
|
||||
|
||||
#### Via API
|
||||
|
||||
```bash
|
||||
# POST /api/v1/scanner/score/{scanId}/replay
|
||||
curl -X POST "https://scanner.stellaops.local/api/v1/scanner/score/scan-123/replay" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"manifestHash": "sha256:abc123...",
|
||||
"freezeTimestamp": "2025-01-15T00:00:00Z"
|
||||
}'
|
||||
```
|
||||
|
||||
#### Expected Response
|
||||
|
||||
```json
|
||||
{
|
||||
"scanId": "scan-123",
|
||||
"score": 7.5,
|
||||
"rootHash": "sha256:def456...",
|
||||
"bundleUri": "/api/v1/scanner/scans/scan-123/proofs/sha256:def456...",
|
||||
"manifestHash": "sha256:abc123...",
|
||||
"replayedAt": "2025-01-16T10:30:00Z",
|
||||
"deterministic": true
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Retrieving Proof Bundles
|
||||
|
||||
#### Via CLI
|
||||
|
||||
```bash
|
||||
# Get bundle for a scan
|
||||
stella score bundle --scan <scan-id>
|
||||
|
||||
# Download bundle to file
|
||||
stella score bundle --scan <scan-id> --output bundle.tar.gz
|
||||
```
|
||||
|
||||
#### Via API
|
||||
|
||||
```bash
|
||||
# GET /api/v1/scanner/score/{scanId}/bundle
|
||||
curl "https://scanner.stellaops.local/api/v1/scanner/score/scan-123/bundle" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-o bundle.tar.gz
|
||||
```
|
||||
|
||||
### 2.3 Verifying Score Integrity
|
||||
|
||||
#### Via CLI
|
||||
|
||||
```bash
|
||||
# Verify against expected root hash
|
||||
stella score verify --scan <scan-id> --root-hash sha256:def456...
|
||||
|
||||
# Verify downloaded bundle
|
||||
stella proof verify --bundle bundle.tar.gz
|
||||
```
|
||||
|
||||
#### Via API
|
||||
|
||||
```bash
|
||||
# POST /api/v1/scanner/score/{scanId}/verify
|
||||
curl -X POST "https://scanner.stellaops.local/api/v1/scanner/score/scan-123/verify" \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"expectedRootHash": "sha256:def456..."}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Determinism Verification
|
||||
|
||||
### 3.1 What Affects Determinism?
|
||||
|
||||
Score computation is deterministic when:
|
||||
|
||||
| Input | Requirement |
|
||||
|-------|-------------|
|
||||
| SBOM | Identical content (same hash) |
|
||||
| Rules | Same rule version and configuration |
|
||||
| Policy | Same policy document |
|
||||
| Feeds | Same feed snapshot (freeze timestamp) |
|
||||
| Ordering | Findings sorted deterministically |
|
||||
|
||||
### 3.2 Running Determinism Checks
|
||||
|
||||
```bash
|
||||
# Run replay twice and compare
|
||||
REPLAY1=$(stella score replay --scan $SCAN_ID --output json)
|
||||
REPLAY2=$(stella score replay --scan $SCAN_ID --output json)
|
||||
|
||||
# Extract root hashes
|
||||
HASH1=$(echo $REPLAY1 | jq -r '.rootHash')
|
||||
HASH2=$(echo $REPLAY2 | jq -r '.rootHash')
|
||||
|
||||
# Compare
|
||||
if [ "$HASH1" = "$HASH2" ]; then
|
||||
echo "✓ Determinism verified: $HASH1"
|
||||
else
|
||||
echo "✗ Non-deterministic! $HASH1 != $HASH2"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 3.3 Common Determinism Issues
|
||||
|
||||
| Issue | Cause | Resolution |
|
||||
|-------|-------|------------|
|
||||
| Different root hash | Feed data changed between replays | Use `--freeze` timestamp |
|
||||
| Score drift | Rule version mismatch | Pin rules version in manifest |
|
||||
| Ordering differences | Non-stable sort in findings | Check Scanner version (fixed in v2.1+) |
|
||||
| Timestamp in output | Current time in computation | Ensure frozen time mode |
|
||||
|
||||
### 3.4 Feed Freeze for Reproducibility
|
||||
|
||||
```bash
|
||||
# Replay with feed state frozen to original scan time
|
||||
stella score replay --scan $SCAN_ID \
|
||||
--freeze $(stella scan show $SCAN_ID --output json | jq -r '.scannedAt')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Proof Bundle Management
|
||||
|
||||
### 4.1 Bundle Contents
|
||||
|
||||
A proof bundle (`.tar.gz`) contains:
|
||||
|
||||
```
|
||||
bundle/
|
||||
├── manifest.json # Input hashes and metadata
|
||||
├── score.json # Computed score and findings summary
|
||||
├── merkle-proof.json # Merkle tree with inclusion proofs
|
||||
├── dsse-envelope.json # Signed attestation (DSSE format)
|
||||
└── certificate.pem # Signing certificate (optional)
|
||||
```
|
||||
|
||||
### 4.2 Inspecting Bundles
|
||||
|
||||
```bash
|
||||
# Extract and view manifest
|
||||
tar -xzf bundle.tar.gz
|
||||
cat bundle/manifest.json | jq .
|
||||
|
||||
# Verify DSSE signature
|
||||
stella proof verify --bundle bundle.tar.gz --verbose
|
||||
|
||||
# Check Merkle proof
|
||||
stella proof spine --bundle bundle.tar.gz
|
||||
```
|
||||
|
||||
### 4.3 Bundle Retention Policy
|
||||
|
||||
| Environment | Retention | Notes |
|
||||
|-------------|-----------|-------|
|
||||
| Production | 7 years | Regulatory compliance |
|
||||
| Staging | 90 days | Testing purposes |
|
||||
| Development | 30 days | Cleanup automatically |
|
||||
|
||||
### 4.4 Archiving Bundles
|
||||
|
||||
```bash
|
||||
# Export bundle to long-term storage
|
||||
stella score bundle --scan $SCAN_ID --output /archive/proofs/$SCAN_ID.tar.gz
|
||||
|
||||
# Bulk export for compliance audit
|
||||
stella score bundle-export \
|
||||
--since 2024-01-01 \
|
||||
--until 2024-12-31 \
|
||||
--output /archive/2024-proofs/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Troubleshooting
|
||||
|
||||
### 5.1 Replay Returns Different Score
|
||||
|
||||
**Symptoms**: Replayed score differs from original scan score.
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Check manifest integrity:
|
||||
```bash
|
||||
stella scan show $SCAN_ID --output json | jq '.manifest'
|
||||
```
|
||||
|
||||
2. Verify feed state:
|
||||
```bash
|
||||
# Compare feed hashes
|
||||
stella score replay --scan $SCAN_ID --freeze $ORIGINAL_TIME --output json | jq '.manifestHash'
|
||||
```
|
||||
|
||||
3. Check for rule updates:
|
||||
```bash
|
||||
stella rules show --version --output json
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
- Use `--freeze` timestamp matching original scan
|
||||
- Pin rule versions in policy
|
||||
- Regenerate manifest if inputs changed legitimately
|
||||
|
||||
### 5.2 Proof Verification Fails
|
||||
|
||||
**Symptoms**: `stella proof verify` returns validation errors.
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Check DSSE signature:
|
||||
```bash
|
||||
stella proof verify --bundle bundle.tar.gz --verbose 2>&1 | grep -i signature
|
||||
```
|
||||
|
||||
2. Verify certificate validity:
|
||||
```bash
|
||||
openssl x509 -in bundle/certificate.pem -noout -dates
|
||||
```
|
||||
|
||||
3. Check Merkle proof:
|
||||
```bash
|
||||
stella proof spine --bundle bundle.tar.gz --verify
|
||||
```
|
||||
|
||||
**Common Errors**:
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `SIGNATURE_INVALID` | Bundle tampered or wrong key | Re-download bundle |
|
||||
| `CERTIFICATE_EXPIRED` | Signing cert expired | Check signing key rotation |
|
||||
| `MERKLE_MISMATCH` | Root hash doesn't match | Verify correct bundle version |
|
||||
| `MANIFEST_MISSING` | Incomplete bundle | Re-export from API |
|
||||
|
||||
### 5.3 Replay Timeout
|
||||
|
||||
**Symptoms**: Replay request times out or takes too long.
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Check scan size:
|
||||
```bash
|
||||
stella scan show $SCAN_ID --output json | jq '.findingsCount'
|
||||
```
|
||||
|
||||
2. Monitor replay progress:
|
||||
```bash
|
||||
stella score replay --scan $SCAN_ID --verbose
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
- For large scans (>10k findings), increase timeout
|
||||
- Check Scanner Worker health
|
||||
- Consider async replay for very large scans
|
||||
|
||||
### 5.4 Missing Manifest
|
||||
|
||||
**Symptoms**: `Manifest not found` error on replay.
|
||||
|
||||
**Diagnostic Steps**:
|
||||
|
||||
1. Verify scan exists:
|
||||
```bash
|
||||
stella scan show $SCAN_ID
|
||||
```
|
||||
|
||||
2. Check manifest table:
|
||||
```sql
|
||||
SELECT * FROM scanner.manifest WHERE scan_id = 'scan-123';
|
||||
```
|
||||
|
||||
**Resolution**:
|
||||
- Manifest may have been purged (check retention policy)
|
||||
- Restore from backup if available
|
||||
- Re-run scan if original inputs available
|
||||
|
||||
---
|
||||
|
||||
## 6. Monitoring & Alerting
|
||||
|
||||
### 6.1 Key Metrics
|
||||
|
||||
| Metric | Description | Alert Threshold |
|
||||
|--------|-------------|-----------------|
|
||||
| `score_replay_duration_ms` | Time to complete replay | p99 > 30s |
|
||||
| `score_replay_determinism_failures` | Non-deterministic replays | > 0 |
|
||||
| `proof_verification_failures` | Failed verifications | > 5/hour |
|
||||
| `manifest_storage_size_bytes` | Manifest table size | > 100GB |
|
||||
|
||||
### 6.2 Grafana Dashboard Queries
|
||||
|
||||
```promql
|
||||
# Replay latency
|
||||
histogram_quantile(0.99,
|
||||
rate(score_replay_duration_ms_bucket[5m])
|
||||
)
|
||||
|
||||
# Determinism failure rate
|
||||
rate(score_replay_determinism_failures_total[1h])
|
||||
|
||||
# Proof verification success rate
|
||||
sum(rate(proof_verification_success_total[1h])) /
|
||||
sum(rate(proof_verification_total[1h]))
|
||||
```
|
||||
|
||||
### 6.3 Alert Rules
|
||||
|
||||
```yaml
|
||||
groups:
|
||||
- name: score-replay
|
||||
rules:
|
||||
- alert: ScoreReplayLatencyHigh
|
||||
expr: histogram_quantile(0.99, rate(score_replay_duration_ms_bucket[5m])) > 30000
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: Score replay latency exceeds 30s at p99
|
||||
|
||||
- alert: DeterminismFailure
|
||||
expr: increase(score_replay_determinism_failures_total[1h]) > 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: Non-deterministic score replay detected
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Escalation Procedures
|
||||
|
||||
### 7.1 Escalation Matrix
|
||||
|
||||
| Severity | Condition | Response Time | Escalate To |
|
||||
|----------|-----------|---------------|-------------|
|
||||
| P1 - Critical | Determinism failure in production | 15 minutes | Platform Team Lead |
|
||||
| P2 - High | Proof verification failures > 10/hour | 1 hour | Scanner Team |
|
||||
| P3 - Medium | Replay latency degradation | 4 hours | Scanner Team |
|
||||
| P4 - Low | Single replay failure | Next business day | Support Queue |
|
||||
|
||||
### 7.2 P1: Determinism Failure Response
|
||||
|
||||
1. **Immediate Actions** (0-15 min):
|
||||
- Capture affected scan IDs
|
||||
- Preserve original manifest data
|
||||
- Check for recent deployments
|
||||
|
||||
2. **Investigation** (15-60 min):
|
||||
- Compare input hashes between replays
|
||||
- Check feed synchronization status
|
||||
- Review rule engine logs
|
||||
|
||||
3. **Remediation**:
|
||||
- Roll back if deployment-related
|
||||
- Freeze feeds if data drift
|
||||
- Hotfix if code bug identified
|
||||
|
||||
### 7.3 Contacts
|
||||
|
||||
| Role | Contact | Availability |
|
||||
|------|---------|--------------|
|
||||
| Scanner Team Lead | scanner-lead@stellaops.io | Business hours |
|
||||
| Platform On-Call | platform-oncall@stellaops.io | 24/7 |
|
||||
| Security Team | security@stellaops.io | Business hours |
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: SQL Queries
|
||||
|
||||
### Check Manifest History
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
scan_id,
|
||||
manifest_hash,
|
||||
sbom_hash,
|
||||
rules_hash,
|
||||
policy_hash,
|
||||
feed_hash,
|
||||
created_at
|
||||
FROM scanner.manifest
|
||||
WHERE scan_id = 'scan-123'
|
||||
ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
### Find Non-Deterministic Replays
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
scan_id,
|
||||
COUNT(DISTINCT root_hash) as unique_hashes,
|
||||
MIN(replayed_at) as first_replay,
|
||||
MAX(replayed_at) as last_replay
|
||||
FROM scanner.replay_log
|
||||
GROUP BY scan_id
|
||||
HAVING COUNT(DISTINCT root_hash) > 1;
|
||||
```
|
||||
|
||||
### Proof Bundle Statistics
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
DATE_TRUNC('day', created_at) as day,
|
||||
COUNT(*) as bundles_created,
|
||||
AVG(bundle_size_bytes) as avg_size,
|
||||
SUM(bundle_size_bytes) as total_size
|
||||
FROM scanner.proof_bundle
|
||||
WHERE created_at > NOW() - INTERVAL '30 days'
|
||||
GROUP BY DATE_TRUNC('day', created_at)
|
||||
ORDER BY day DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appendix B: CLI Quick Reference
|
||||
|
||||
```bash
|
||||
# Score Replay Commands
|
||||
stella score replay --scan <id> # Replay score computation
|
||||
stella score replay --scan <id> --freeze <ts> # Replay with frozen time
|
||||
stella score bundle --scan <id> # Get proof bundle
|
||||
stella score verify --scan <id> --root-hash <hash> # Verify score
|
||||
|
||||
# Proof Commands
|
||||
stella proof verify --bundle <path> # Verify bundle file
|
||||
stella proof verify --bundle <path> --offline # Offline verification
|
||||
stella proof spine --bundle <path> # Show Merkle spine
|
||||
|
||||
# Output Formats
|
||||
--output json # JSON output
|
||||
--output table # Table output (default)
|
||||
--output yaml # YAML output
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
| Version | Date | Author | Changes |
|
||||
|---------|------|--------|---------|
|
||||
| 1.0.0 | 2025-12-20 | Agent | Initial release |
|
||||
378
docs/training/score-proofs-concept-guide.md
Normal file
378
docs/training/score-proofs-concept-guide.md
Normal file
@@ -0,0 +1,378 @@
|
||||
# Score Proofs Concept Guide
|
||||
|
||||
**Sprint:** SPRINT_3500_0004_0004
|
||||
**Audience:** Developers, Security Engineers, DevOps
|
||||
|
||||
## Introduction
|
||||
|
||||
Score Proofs provide cryptographic evidence that vulnerability scores can be independently verified and reproduced. This guide explains the concepts, architecture, and use cases for Score Proofs in StellaOps.
|
||||
|
||||
---
|
||||
|
||||
## What are Score Proofs?
|
||||
|
||||
### The Problem
|
||||
|
||||
Traditional vulnerability scanners produce scores, but:
|
||||
- **Non-reproducible**: Re-running a scan may yield different results
|
||||
- **Opaque**: No visibility into how scores were computed
|
||||
- **Untraceable**: No audit trail linking scores to inputs
|
||||
- **Time-sensitive**: Advisory data changes constantly
|
||||
|
||||
### The Solution
|
||||
|
||||
Score Proofs address these issues by:
|
||||
|
||||
1. **Content-addressing all inputs**: Every piece of data is identified by its cryptographic hash
|
||||
2. **Recording computation parameters**: Algorithm versions, timestamps, configuration
|
||||
3. **Creating verifiable attestations**: DSSE-signed bundles that can be independently verified
|
||||
4. **Enabling deterministic replay**: Same inputs always produce same outputs
|
||||
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### 1. Scan Manifest
|
||||
|
||||
A **Scan Manifest** is the immutable record of everything that went into a scan:
|
||||
|
||||
```yaml
|
||||
manifest:
|
||||
scanId: "scan-12345"
|
||||
digest: "sha256:abc123..." # Content hash of the manifest itself
|
||||
timestamp: "2025-12-20T10:00:00Z"
|
||||
inputs:
|
||||
sbom:
|
||||
digest: "sha256:def456..."
|
||||
format: "cyclonedx-1.6"
|
||||
advisoryFeeds:
|
||||
- feedId: "nvd"
|
||||
digest: "sha256:ghi789..."
|
||||
asOf: "2025-12-20T00:00:00Z"
|
||||
- feedId: "ghsa"
|
||||
digest: "sha256:jkl012..."
|
||||
asOf: "2025-12-20T00:00:00Z"
|
||||
callGraph:
|
||||
digest: "sha256:mno345..."
|
||||
nodes: 12345
|
||||
vexDocuments:
|
||||
- digest: "sha256:pqr678..."
|
||||
|
||||
configuration:
|
||||
scoringAlgorithm: "cvss-4.0"
|
||||
reachabilityEnabled: true
|
||||
unknownsHandling: "flag"
|
||||
|
||||
environment:
|
||||
scannerVersion: "1.0.0"
|
||||
feedVersions:
|
||||
nvd: "2025.12.20"
|
||||
ghsa: "2025.12.20"
|
||||
```
|
||||
|
||||
**Key Properties:**
|
||||
- Every input has a content hash (digest)
|
||||
- Configuration is explicitly recorded
|
||||
- Environment versions are captured
|
||||
- The manifest itself has a digest
|
||||
|
||||
### 2. Proof Bundle
|
||||
|
||||
A **Proof Bundle** packages the manifest with cryptographic attestations:
|
||||
|
||||
```
|
||||
proof-bundle/
|
||||
├── manifest.json # The scan manifest
|
||||
├── attestations/
|
||||
│ ├── manifest.dsse # DSSE signature over manifest
|
||||
│ ├── sbom.dsse # SBOM attestation
|
||||
│ └── findings.dsse # Findings attestation
|
||||
├── inputs/ # Optional: actual input data
|
||||
│ ├── sbom.json
|
||||
│ └── callgraph.ndjson
|
||||
└── bundle.sig # Bundle signature
|
||||
```
|
||||
|
||||
### 3. Deterministic Replay
|
||||
|
||||
**Replay** is the process of re-executing a scan using the exact inputs from a manifest:
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||||
│ Proof Bundle │────▶│ Replay Engine │────▶│ New Findings │
|
||||
│ (manifest + │ │ (same algo) │ │ (must match) │
|
||||
│ inputs) │ │ │ │ │
|
||||
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
**Guarantees:**
|
||||
- Same inputs → Same outputs (byte-identical)
|
||||
- Different inputs → Different outputs (with diff report)
|
||||
- Missing inputs → Validation failure
|
||||
|
||||
### 4. Proof Ledger
|
||||
|
||||
The **Proof Ledger** is an append-only chain of proof records:
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Proof 1 │────▶│ Proof 2 │────▶│ Proof 3 │
|
||||
│ │ │ │ │ │
|
||||
│ prevHash: ∅ │ │ prevHash: P1 │ │ prevHash: P2 │
|
||||
│ manifest: M1 │ │ manifest: M2 │ │ manifest: M3 │
|
||||
│ hash: P1 │ │ hash: P2 │ │ hash: P3 │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
This creates an **audit trail** where:
|
||||
- Each proof links to its predecessor
|
||||
- Tampering breaks the chain
|
||||
- History is verifiable
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
### Step 1: Scan Execution
|
||||
|
||||
During a scan, the system:
|
||||
1. Collects all inputs (SBOM, advisories, call graph, VEX)
|
||||
2. Computes digests for each input
|
||||
3. Records configuration and environment
|
||||
4. Executes the scoring algorithm
|
||||
5. Generates findings with provenance
|
||||
|
||||
### Step 2: Manifest Creation
|
||||
|
||||
After scoring:
|
||||
1. All input digests are assembled into a manifest
|
||||
2. Findings are attached with their own digests
|
||||
3. The manifest is signed using DSSE
|
||||
|
||||
### Step 3: Proof Registration
|
||||
|
||||
The proof is registered:
|
||||
1. Appended to the proof ledger (with chain link)
|
||||
2. Optionally anchored to Sigstore Rekor (transparency log)
|
||||
3. Stored in content-addressed storage
|
||||
|
||||
### Step 4: Verification
|
||||
|
||||
To verify a proof:
|
||||
1. Retrieve the proof bundle
|
||||
2. Verify all signatures
|
||||
3. Check chain integrity (prev_hash)
|
||||
4. Optionally replay the computation
|
||||
5. Compare outputs
|
||||
|
||||
---
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Audit Compliance
|
||||
|
||||
**Scenario**: An auditor asks "How did you arrive at this vulnerability score?"
|
||||
|
||||
**With Score Proofs**:
|
||||
```bash
|
||||
# Show the proof
|
||||
stella proof inspect --scan-id $SCAN_ID
|
||||
|
||||
# Auditor can verify independently
|
||||
stella proof verify --scan-id $SCAN_ID
|
||||
```
|
||||
|
||||
The auditor sees exactly which advisories, SBOM version, and VEX documents were used.
|
||||
|
||||
### 2. Dispute Resolution
|
||||
|
||||
**Scenario**: A vendor disputes a finding, claiming it was fixed.
|
||||
|
||||
**With Score Proofs**:
|
||||
```bash
|
||||
# Replay with the original inputs
|
||||
stella score replay --scan-id $SCAN_ID
|
||||
|
||||
# Compare with current data
|
||||
stella score diff --scan-id $SCAN_ID --compare-latest
|
||||
```
|
||||
|
||||
The diff shows what changed and why.
|
||||
|
||||
### 3. Regulatory Evidence
|
||||
|
||||
**Scenario**: A regulator requires proof that security scans were performed.
|
||||
|
||||
**With Score Proofs**:
|
||||
```bash
|
||||
# Export evidence bundle
|
||||
stella proof export --scan-id $SCAN_ID --output evidence.zip
|
||||
|
||||
# Contains signed attestations, timestamps, and chain links
|
||||
```
|
||||
|
||||
### 4. CI/CD Integration
|
||||
|
||||
**Scenario**: Ensure pipeline decisions are traceable.
|
||||
|
||||
**With Score Proofs**:
|
||||
```yaml
|
||||
# In CI pipeline
|
||||
- name: Scan with proofs
|
||||
run: |
|
||||
stella scan run --image $IMAGE --proof-mode full
|
||||
stella proof export --scan-id $SCAN_ID --output proof.zip
|
||||
|
||||
- name: Upload evidence
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: security-proof
|
||||
path: proof.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Air-Gap Considerations
|
||||
|
||||
Score Proofs work offline with some preparation:
|
||||
|
||||
### Offline Kit Contents
|
||||
|
||||
```
|
||||
offline-kit/
|
||||
├── feeds/ # Frozen advisory feeds
|
||||
├── trust/ # Trust anchors (public keys)
|
||||
├── time-anchor/ # Trusted timestamp
|
||||
└── config/ # Offline configuration
|
||||
```
|
||||
|
||||
### Key Differences
|
||||
|
||||
| Feature | Online Mode | Offline Mode |
|
||||
|---------|-------------|--------------|
|
||||
| Advisory feeds | Real-time | Frozen snapshot |
|
||||
| Time source | NTP/Sigstore | Time anchor |
|
||||
| Transparency | Rekor | Local ledger |
|
||||
| Key rotation | Dynamic | Pre-provisioned |
|
||||
|
||||
### Offline Workflow
|
||||
|
||||
```bash
|
||||
# Prepare offline kit
|
||||
stella airgap prepare --feeds nvd,ghsa --output offline-kit/
|
||||
|
||||
# Transfer to air-gapped system
|
||||
# ... (physical media transfer)
|
||||
|
||||
# Run offline scan
|
||||
stella scan run --offline --kit offline-kit/ --image $IMAGE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Score Proofs System │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Scanner │──▶│ Manifest │──▶│ Signer │ │
|
||||
│ │ Engine │ │ Generator │ │ (DSSE) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Input │ │ Proof │ │ Transparency│ │
|
||||
│ │ Store │ │ Ledger │ │ Log │ │
|
||||
│ │ (CAS) │ │ (Append) │ │ (Rekor) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
│ Replay Engine │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Bundle │──▶│ Replay │──▶│ Diff │ │
|
||||
│ │ Loader │ │ Executor │ │ Engine │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Enable Proofs
|
||||
|
||||
```bash
|
||||
# In scanner configuration
|
||||
stella config set proof.mode full
|
||||
stella config set proof.transparency enabled
|
||||
```
|
||||
|
||||
### 2. Archive Proof Bundles
|
||||
|
||||
Store proof bundles alongside your build artifacts:
|
||||
- Same retention period as releases
|
||||
- Include in backup procedures
|
||||
- Index for searchability
|
||||
|
||||
### 3. Verify Periodically
|
||||
|
||||
Don't just create proofs—verify them:
|
||||
```bash
|
||||
# Weekly verification job
|
||||
stella proof verify --since "7 days ago" --output report.json
|
||||
```
|
||||
|
||||
### 4. Plan for Offline Scenarios
|
||||
|
||||
Even if you operate online, prepare offline capability:
|
||||
- Maintain offline kits
|
||||
- Test offline workflows quarterly
|
||||
- Document offline procedures
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Replay produces different results"
|
||||
|
||||
**Possible causes:**
|
||||
1. Missing input data (check bundle completeness)
|
||||
2. Algorithm version mismatch
|
||||
3. Non-deterministic configuration
|
||||
|
||||
**Resolution:**
|
||||
```bash
|
||||
stella proof inspect --scan-id $SCAN_ID --check-inputs
|
||||
```
|
||||
|
||||
### "Signature verification failed"
|
||||
|
||||
**Possible causes:**
|
||||
1. Key rotation (old key not trusted)
|
||||
2. Bundle tampering
|
||||
3. Corrupted download
|
||||
|
||||
**Resolution:**
|
||||
```bash
|
||||
stella proof verify --scan-id $SCAN_ID --verbose
|
||||
# Check trust anchors
|
||||
stella trust list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Score Proofs CLI Reference](../cli/score-proofs-cli-reference.md)
|
||||
- [Score Proofs API Reference](../api/score-proofs-reachability-api-reference.md)
|
||||
- [Score Proofs Runbook](../operations/score-proofs-runbook.md)
|
||||
- [Air-Gap Runbook](../airgap/score-proofs-reachability-airgap-runbook.md)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-12-20
|
||||
**Version**: 1.0.0
|
||||
**Sprint**: 3500.0004.0004
|
||||
362
src/Web/StellaOps.Web/src/app/core/api/proof.client.ts
Normal file
362
src/Web/StellaOps.Web/src/app/core/api/proof.client.ts
Normal file
@@ -0,0 +1,362 @@
|
||||
/**
|
||||
* Proof and Manifest API clients for Sprint 3500.0004.0002 - T6.
|
||||
* Provides services for scan manifests, proof bundles, and score replay.
|
||||
*/
|
||||
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
import { inject, Injectable, InjectionToken } from '@angular/core';
|
||||
import { Observable, of, delay, throwError } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { AppConfigService } from '../config/app-config.service';
|
||||
import {
|
||||
ScanManifest,
|
||||
ManifestHashEntry,
|
||||
MerkleTree,
|
||||
MerkleTreeNode,
|
||||
ProofBundle,
|
||||
ProofVerificationResult,
|
||||
ScoreReplayRequest,
|
||||
ScoreReplayResult,
|
||||
ScoreBreakdown,
|
||||
DsseSignature,
|
||||
} from './proof.models';
|
||||
|
||||
// ============================================================================
|
||||
// Injection Tokens
|
||||
// ============================================================================
|
||||
|
||||
export const MANIFEST_API = new InjectionToken<ManifestApi>('MANIFEST_API');
|
||||
export const PROOF_BUNDLE_API = new InjectionToken<ProofBundleApi>('PROOF_BUNDLE_API');
|
||||
export const SCORE_REPLAY_API = new InjectionToken<ScoreReplayApi>('SCORE_REPLAY_API');
|
||||
|
||||
// ============================================================================
|
||||
// API Interfaces
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* API interface for scan manifest operations.
|
||||
*/
|
||||
export interface ManifestApi {
|
||||
getManifest(scanId: string): Observable<ScanManifest>;
|
||||
getMerkleTree(scanId: string): Observable<MerkleTree>;
|
||||
}
|
||||
|
||||
/**
|
||||
* API interface for proof bundle operations.
|
||||
*/
|
||||
export interface ProofBundleApi {
|
||||
getProofBundle(scanId: string): Observable<ProofBundle>;
|
||||
verifyProofBundle(bundleId: string): Observable<ProofVerificationResult>;
|
||||
downloadProofBundle(bundleId: string): Observable<Blob>;
|
||||
}
|
||||
|
||||
/**
|
||||
* API interface for score replay operations.
|
||||
*/
|
||||
export interface ScoreReplayApi {
|
||||
triggerReplay(request: ScoreReplayRequest): Observable<ScoreReplayResult>;
|
||||
getReplayStatus(replayId: string): Observable<ScoreReplayResult>;
|
||||
getScoreHistory(scanId: string): Observable<readonly ScoreBreakdown[]>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mock Data Fixtures
|
||||
// ============================================================================
|
||||
|
||||
const mockManifestHashes: readonly ManifestHashEntry[] = [
|
||||
{ label: 'SBOM Document', algorithm: 'sha256', value: 'a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456', source: 'sbom' },
|
||||
{ label: 'Layer 1', algorithm: 'sha256', value: 'b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef1234567a', source: 'layer' },
|
||||
{ label: 'Layer 2', algorithm: 'sha256', value: 'c3d4e5f6789012345678901234567890abcdef1234567890abcdef1234567ab2', source: 'layer' },
|
||||
{ label: 'Layer 3', algorithm: 'sha256', value: 'd4e5f6789012345678901234567890abcdef1234567890abcdef1234567ab2c3', source: 'layer' },
|
||||
{ label: 'Config', algorithm: 'sha256', value: 'e5f6789012345678901234567890abcdef1234567890abcdef1234567ab2c3d4', source: 'config' },
|
||||
{ label: 'Composition Manifest', algorithm: 'sha256', value: 'f6789012345678901234567890abcdef1234567890abcdef1234567ab2c3d4e5', source: 'composition' },
|
||||
];
|
||||
|
||||
function buildMockMerkleTree(): MerkleTree {
|
||||
const leaves: MerkleTreeNode[] = mockManifestHashes.map((h, i) => ({
|
||||
nodeId: `leaf-${i}`,
|
||||
hash: h.value,
|
||||
label: h.label,
|
||||
isLeaf: true,
|
||||
isRoot: false,
|
||||
level: 0,
|
||||
position: i,
|
||||
}));
|
||||
|
||||
// Build internal nodes (simplified binary tree)
|
||||
const level1: MerkleTreeNode[] = [
|
||||
{ nodeId: 'node-1-0', hash: 'int1a2b3c4d5e6f...', isLeaf: false, isRoot: false, level: 1, position: 0, children: [leaves[0], leaves[1]] },
|
||||
{ nodeId: 'node-1-1', hash: 'int2b3c4d5e6f7...', isLeaf: false, isRoot: false, level: 1, position: 1, children: [leaves[2], leaves[3]] },
|
||||
{ nodeId: 'node-1-2', hash: 'int3c4d5e6f789...', isLeaf: false, isRoot: false, level: 1, position: 2, children: [leaves[4], leaves[5]] },
|
||||
];
|
||||
|
||||
const level2: MerkleTreeNode[] = [
|
||||
{ nodeId: 'node-2-0', hash: 'int4d5e6f78901...', isLeaf: false, isRoot: false, level: 2, position: 0, children: [level1[0], level1[1]] },
|
||||
{ nodeId: 'node-2-1', hash: 'int5e6f7890123...', isLeaf: false, isRoot: false, level: 2, position: 1, children: [level1[2]] },
|
||||
];
|
||||
|
||||
const root: MerkleTreeNode = {
|
||||
nodeId: 'root',
|
||||
hash: 'root123456789abcdef...',
|
||||
label: 'Merkle Root',
|
||||
isLeaf: false,
|
||||
isRoot: true,
|
||||
level: 3,
|
||||
position: 0,
|
||||
children: level2,
|
||||
};
|
||||
|
||||
return { root, leafCount: leaves.length, depth: 4 };
|
||||
}
|
||||
|
||||
const mockManifest: ScanManifest = {
|
||||
manifestId: 'manifest-001',
|
||||
scanId: 'scan-abc123',
|
||||
imageDigest: 'sha256:abc123def456789012345678901234567890abcdef1234567890abcdef123456',
|
||||
createdAt: '2025-12-20T10:00:00Z',
|
||||
hashes: mockManifestHashes,
|
||||
merkleRoot: 'sha256:root123456789abcdef1234567890abcdef1234567890abcdef1234567890',
|
||||
compositionManifestUri: 'oci://registry.stellaops.io/prod/myimage@sha256:abc123/_composition.json',
|
||||
};
|
||||
|
||||
const mockSignatures: readonly DsseSignature[] = [
|
||||
{
|
||||
keyId: 'awskms:///arn:aws:kms:us-east-1:123456789:key/key-id',
|
||||
algorithm: 'ECDSA-P256',
|
||||
status: 'valid',
|
||||
signedAt: '2025-12-20T10:05:00Z',
|
||||
issuer: 'StellaOps Scanner',
|
||||
},
|
||||
];
|
||||
|
||||
const mockProofBundle: ProofBundle = {
|
||||
bundleId: 'bundle-001',
|
||||
scanId: 'scan-abc123',
|
||||
createdAt: '2025-12-20T10:05:00Z',
|
||||
merkleRoot: 'sha256:root123456789abcdef1234567890abcdef1234567890abcdef1234567890',
|
||||
dsseEnvelope: 'eyJ0eXBlIjoiYXBwbGljYXRpb24vdm5kLmRzc2UuZW52ZWxvcGUrand...', // Base64 mock
|
||||
signatures: mockSignatures,
|
||||
rekorEntry: {
|
||||
logId: 'c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d',
|
||||
logIndex: 12345678,
|
||||
integratedTime: '2025-12-20T10:06:00Z',
|
||||
logUrl: 'https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77a...',
|
||||
bodyHash: 'sha256:body123456789...',
|
||||
},
|
||||
verificationStatus: 'verified',
|
||||
downloadUrl: '/api/v1/scanner/scans/scan-abc123/proofs/bundle-001/download',
|
||||
};
|
||||
|
||||
const mockScoreBreakdown: ScoreBreakdown = {
|
||||
totalScore: 85.5,
|
||||
components: [
|
||||
{ name: 'Vulnerability', weight: 0.4, rawScore: 75.0, weightedScore: 30.0, details: '3 medium, 12 low vulnerabilities' },
|
||||
{ name: 'License', weight: 0.2, rawScore: 100.0, weightedScore: 20.0, details: 'All licenses approved' },
|
||||
{ name: 'Determinism', weight: 0.2, rawScore: 100.0, weightedScore: 20.0, details: 'Merkle root verified' },
|
||||
{ name: 'Provenance', weight: 0.1, rawScore: 90.0, weightedScore: 9.0, details: 'SLSA Level 2' },
|
||||
{ name: 'Entropy', weight: 0.1, rawScore: 65.0, weightedScore: 6.5, details: '8% opaque ratio' },
|
||||
],
|
||||
computedAt: '2025-12-20T10:10:00Z',
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Mock Service Implementations
|
||||
// ============================================================================
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MockManifestApi implements ManifestApi {
|
||||
getManifest(scanId: string): Observable<ScanManifest> {
|
||||
return of({ ...mockManifest, scanId }).pipe(delay(200));
|
||||
}
|
||||
|
||||
getMerkleTree(scanId: string): Observable<MerkleTree> {
|
||||
return of(buildMockMerkleTree()).pipe(delay(300));
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MockProofBundleApi implements ProofBundleApi {
|
||||
getProofBundle(scanId: string): Observable<ProofBundle> {
|
||||
return of({ ...mockProofBundle, scanId }).pipe(delay(250));
|
||||
}
|
||||
|
||||
verifyProofBundle(bundleId: string): Observable<ProofVerificationResult> {
|
||||
return of({
|
||||
bundleId,
|
||||
verified: true,
|
||||
merkleRootValid: true,
|
||||
signatureValid: true,
|
||||
rekorInclusionValid: true,
|
||||
verifiedAt: new Date().toISOString(),
|
||||
}).pipe(delay(500));
|
||||
}
|
||||
|
||||
downloadProofBundle(bundleId: string): Observable<Blob> {
|
||||
const mockData = new Blob(['mock-proof-bundle-content'], { type: 'application/gzip' });
|
||||
return of(mockData).pipe(delay(100));
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MockScoreReplayApi implements ScoreReplayApi {
|
||||
triggerReplay(request: ScoreReplayRequest): Observable<ScoreReplayResult> {
|
||||
const replayId = `replay-${Date.now()}`;
|
||||
return of({
|
||||
replayId,
|
||||
scanId: request.scanId,
|
||||
status: 'completed' as const,
|
||||
startedAt: new Date(Date.now() - 5000).toISOString(),
|
||||
completedAt: new Date().toISOString(),
|
||||
originalScore: mockScoreBreakdown,
|
||||
replayedScore: {
|
||||
...mockScoreBreakdown,
|
||||
totalScore: 86.0,
|
||||
computedAt: new Date().toISOString(),
|
||||
},
|
||||
drifts: [
|
||||
{
|
||||
componentName: 'Vulnerability',
|
||||
originalScore: 75.0,
|
||||
replayedScore: 76.25,
|
||||
delta: 1.25,
|
||||
driftPercent: 1.67,
|
||||
significant: false,
|
||||
},
|
||||
],
|
||||
hasDrift: true,
|
||||
proofBundle: mockProofBundle,
|
||||
}).pipe(delay(1000));
|
||||
}
|
||||
|
||||
getReplayStatus(replayId: string): Observable<ScoreReplayResult> {
|
||||
return of({
|
||||
replayId,
|
||||
scanId: 'scan-abc123',
|
||||
status: 'completed' as const,
|
||||
startedAt: new Date(Date.now() - 5000).toISOString(),
|
||||
completedAt: new Date().toISOString(),
|
||||
originalScore: mockScoreBreakdown,
|
||||
replayedScore: mockScoreBreakdown,
|
||||
hasDrift: false,
|
||||
}).pipe(delay(200));
|
||||
}
|
||||
|
||||
getScoreHistory(scanId: string): Observable<readonly ScoreBreakdown[]> {
|
||||
const history: ScoreBreakdown[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - i);
|
||||
history.push({
|
||||
...mockScoreBreakdown,
|
||||
totalScore: 85.5 - i * 0.5,
|
||||
computedAt: date.toISOString(),
|
||||
});
|
||||
}
|
||||
return of(history).pipe(delay(300));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HTTP Client Implementations (for production use)
|
||||
// ============================================================================
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ManifestClient implements ManifestApi {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly config = inject(AppConfigService);
|
||||
|
||||
getManifest(scanId: string): Observable<ScanManifest> {
|
||||
return this.http.get<ScanManifest>(
|
||||
`${this.config.apiBaseUrl}/scanner/scans/${scanId}/manifest`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to fetch manifest: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getMerkleTree(scanId: string): Observable<MerkleTree> {
|
||||
return this.http.get<MerkleTree>(
|
||||
`${this.config.apiBaseUrl}/scanner/scans/${scanId}/manifest/tree`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to fetch Merkle tree: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ProofBundleClient implements ProofBundleApi {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly config = inject(AppConfigService);
|
||||
|
||||
getProofBundle(scanId: string): Observable<ProofBundle> {
|
||||
return this.http.get<ProofBundle>(
|
||||
`${this.config.apiBaseUrl}/scanner/scans/${scanId}/proofs`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to fetch proof bundle: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
verifyProofBundle(bundleId: string): Observable<ProofVerificationResult> {
|
||||
return this.http.post<ProofVerificationResult>(
|
||||
`${this.config.apiBaseUrl}/scanner/proofs/${bundleId}/verify`,
|
||||
{}
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to verify proof bundle: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
downloadProofBundle(bundleId: string): Observable<Blob> {
|
||||
return this.http.get(
|
||||
`${this.config.apiBaseUrl}/scanner/proofs/${bundleId}/download`,
|
||||
{ responseType: 'blob' }
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to download proof bundle: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ScoreReplayClient implements ScoreReplayApi {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly config = inject(AppConfigService);
|
||||
|
||||
triggerReplay(request: ScoreReplayRequest): Observable<ScoreReplayResult> {
|
||||
return this.http.post<ScoreReplayResult>(
|
||||
`${this.config.apiBaseUrl}/scanner/scans/${request.scanId}/score/replay`,
|
||||
request
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to trigger score replay: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getReplayStatus(replayId: string): Observable<ScoreReplayResult> {
|
||||
return this.http.get<ScoreReplayResult>(
|
||||
`${this.config.apiBaseUrl}/scanner/replays/${replayId}`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to get replay status: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getScoreHistory(scanId: string): Observable<readonly ScoreBreakdown[]> {
|
||||
return this.http.get<readonly ScoreBreakdown[]>(
|
||||
`${this.config.apiBaseUrl}/scanner/scans/${scanId}/score/history`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to get score history: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
359
src/Web/StellaOps.Web/src/app/core/api/reachability.client.ts
Normal file
359
src/Web/StellaOps.Web/src/app/core/api/reachability.client.ts
Normal file
@@ -0,0 +1,359 @@
|
||||
/**
|
||||
* Reachability API client for Sprint 3500.0004.0002 - T6.
|
||||
* Provides services for reachability analysis and call graph visualization.
|
||||
*/
|
||||
|
||||
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
|
||||
import { inject, Injectable, InjectionToken } from '@angular/core';
|
||||
import { Observable, of, delay, throwError } from 'rxjs';
|
||||
import { catchError, map, switchMap } from 'rxjs/operators';
|
||||
import { AppConfigService } from '../config/app-config.service';
|
||||
import {
|
||||
CallGraph,
|
||||
CallGraphNode,
|
||||
CallGraphEdge,
|
||||
ReachabilityPath,
|
||||
ReachabilityExplanation,
|
||||
ReachabilityAnalysisRequest,
|
||||
ReachabilitySummary,
|
||||
ExportGraphRequest,
|
||||
ExportGraphResult,
|
||||
ConfidenceBreakdown,
|
||||
} from './reachability.models';
|
||||
|
||||
// ============================================================================
|
||||
// Injection Token
|
||||
// ============================================================================
|
||||
|
||||
export const REACHABILITY_API = new InjectionToken<ReachabilityApi>('REACHABILITY_API');
|
||||
|
||||
// ============================================================================
|
||||
// API Interface
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* API interface for reachability analysis operations.
|
||||
*/
|
||||
export interface ReachabilityApi {
|
||||
getExplanation(scanId: string, cveId: string): Observable<ReachabilityExplanation>;
|
||||
getSummary(scanId: string): Observable<ReachabilitySummary>;
|
||||
analyze(request: ReachabilityAnalysisRequest): Observable<ReachabilityExplanation>;
|
||||
getCallGraph(scanId: string): Observable<CallGraph>;
|
||||
exportGraph(request: ExportGraphRequest): Observable<ExportGraphResult>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mock Data Fixtures
|
||||
// ============================================================================
|
||||
|
||||
const mockNodes: CallGraphNode[] = [
|
||||
{
|
||||
nodeId: 'node-entry-1',
|
||||
type: 'entrypoint',
|
||||
name: 'main',
|
||||
qualifiedName: 'com.example.app.Main.main',
|
||||
filePath: 'src/main/java/com/example/app/Main.java',
|
||||
lineNumber: 15,
|
||||
package: 'com.example.app',
|
||||
isVulnerable: false,
|
||||
isEntrypoint: true,
|
||||
},
|
||||
{
|
||||
nodeId: 'node-service-1',
|
||||
type: 'class',
|
||||
name: 'UserService',
|
||||
qualifiedName: 'com.example.app.service.UserService',
|
||||
filePath: 'src/main/java/com/example/app/service/UserService.java',
|
||||
lineNumber: 22,
|
||||
package: 'com.example.app.service',
|
||||
isVulnerable: false,
|
||||
isEntrypoint: false,
|
||||
},
|
||||
{
|
||||
nodeId: 'node-method-1',
|
||||
type: 'method',
|
||||
name: 'processRequest',
|
||||
qualifiedName: 'com.example.app.service.UserService.processRequest',
|
||||
filePath: 'src/main/java/com/example/app/service/UserService.java',
|
||||
lineNumber: 45,
|
||||
package: 'com.example.app.service',
|
||||
isVulnerable: false,
|
||||
isEntrypoint: false,
|
||||
},
|
||||
{
|
||||
nodeId: 'node-lib-1',
|
||||
type: 'external',
|
||||
name: 'deserialize',
|
||||
qualifiedName: 'com.fasterxml.jackson.databind.ObjectMapper.readValue',
|
||||
package: 'com.fasterxml.jackson.databind',
|
||||
isVulnerable: true,
|
||||
isEntrypoint: false,
|
||||
metadata: { cveId: 'CVE-2020-36518' },
|
||||
},
|
||||
{
|
||||
nodeId: 'node-method-2',
|
||||
type: 'method',
|
||||
name: 'validateInput',
|
||||
qualifiedName: 'com.example.app.service.UserService.validateInput',
|
||||
filePath: 'src/main/java/com/example/app/service/UserService.java',
|
||||
lineNumber: 78,
|
||||
package: 'com.example.app.service',
|
||||
isVulnerable: false,
|
||||
isEntrypoint: false,
|
||||
},
|
||||
];
|
||||
|
||||
const mockEdges: CallGraphEdge[] = [
|
||||
{ edgeId: 'edge-1', sourceId: 'node-entry-1', targetId: 'node-service-1', callType: 'direct', confidence: 1.0 },
|
||||
{ edgeId: 'edge-2', sourceId: 'node-service-1', targetId: 'node-method-1', callType: 'direct', confidence: 1.0 },
|
||||
{ edgeId: 'edge-3', sourceId: 'node-method-1', targetId: 'node-lib-1', callType: 'direct', confidence: 0.95 },
|
||||
{ edgeId: 'edge-4', sourceId: 'node-method-1', targetId: 'node-method-2', callType: 'direct', confidence: 1.0 },
|
||||
];
|
||||
|
||||
const mockCallGraph: CallGraph = {
|
||||
graphId: 'graph-001',
|
||||
language: 'java',
|
||||
nodes: mockNodes,
|
||||
edges: mockEdges,
|
||||
nodeCount: mockNodes.length,
|
||||
edgeCount: mockEdges.length,
|
||||
createdAt: '2025-12-20T08:00:00Z',
|
||||
digest: 'sha256:graph123456789abcdef...',
|
||||
};
|
||||
|
||||
const mockConfidenceBreakdown: ConfidenceBreakdown = {
|
||||
overallScore: 0.87,
|
||||
factors: [
|
||||
{ factorName: 'Static Analysis', weight: 0.4, score: 0.95, contribution: 0.38, description: 'Call graph extracted via static analysis' },
|
||||
{ factorName: 'Path Length', weight: 0.2, score: 0.90, contribution: 0.18, description: 'Short path (3 hops) increases confidence' },
|
||||
{ factorName: 'Call Type', weight: 0.2, score: 0.85, contribution: 0.17, description: 'Direct calls only, no dynamic dispatch' },
|
||||
{ factorName: 'Code Coverage', weight: 0.2, score: 0.70, contribution: 0.14, description: 'Path partially covered by tests' },
|
||||
],
|
||||
computedAt: '2025-12-20T10:00:00Z',
|
||||
};
|
||||
|
||||
const mockPath: ReachabilityPath = {
|
||||
pathId: 'path-001',
|
||||
entrypoint: mockNodes[0],
|
||||
vulnerable: mockNodes[3],
|
||||
steps: [
|
||||
{ stepIndex: 0, node: mockNodes[0], confidence: 1.0 },
|
||||
{ stepIndex: 1, node: mockNodes[1], callType: 'direct', confidence: 1.0 },
|
||||
{ stepIndex: 2, node: mockNodes[2], callType: 'direct', confidence: 0.95 },
|
||||
{ stepIndex: 3, node: mockNodes[3], callType: 'direct', confidence: 0.87 },
|
||||
],
|
||||
pathLength: 3,
|
||||
overallConfidence: 0.87,
|
||||
isShortestPath: true,
|
||||
};
|
||||
|
||||
const mockExplanation: ReachabilityExplanation = {
|
||||
explanationId: 'explain-001',
|
||||
cveId: 'CVE-2020-36518',
|
||||
vulnerablePackage: 'com.fasterxml.jackson.databind:jackson-databind:2.9.8',
|
||||
vulnerableFunction: 'ObjectMapper.readValue',
|
||||
verdict: 'reachable',
|
||||
confidence: mockConfidenceBreakdown,
|
||||
paths: [mockPath],
|
||||
callGraph: mockCallGraph,
|
||||
entrypointsAnalyzed: 5,
|
||||
shortestPathLength: 3,
|
||||
analysisTime: '2.3s',
|
||||
createdAt: '2025-12-20T10:00:00Z',
|
||||
};
|
||||
|
||||
const mockSummary: ReachabilitySummary = {
|
||||
scanId: 'scan-abc123',
|
||||
totalCves: 25,
|
||||
reachableCves: 8,
|
||||
unreachableCves: 12,
|
||||
uncertainCves: 3,
|
||||
noDataCves: 2,
|
||||
analysisCompletedAt: '2025-12-20T10:00:00Z',
|
||||
callGraphAvailable: true,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Mock Service Implementation
|
||||
// ============================================================================
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MockReachabilityApi implements ReachabilityApi {
|
||||
getExplanation(scanId: string, cveId: string): Observable<ReachabilityExplanation> {
|
||||
return of({
|
||||
...mockExplanation,
|
||||
cveId,
|
||||
}).pipe(delay(400));
|
||||
}
|
||||
|
||||
getSummary(scanId: string): Observable<ReachabilitySummary> {
|
||||
return of({
|
||||
...mockSummary,
|
||||
scanId,
|
||||
}).pipe(delay(200));
|
||||
}
|
||||
|
||||
analyze(request: ReachabilityAnalysisRequest): Observable<ReachabilityExplanation> {
|
||||
const explanation: ReachabilityExplanation = {
|
||||
...mockExplanation,
|
||||
cveId: request.cveId,
|
||||
callGraph: request.includeCallGraph ? mockCallGraph : undefined,
|
||||
paths: mockExplanation.paths.slice(0, request.maxPaths ?? 10),
|
||||
};
|
||||
return of(explanation).pipe(delay(800));
|
||||
}
|
||||
|
||||
getCallGraph(scanId: string): Observable<CallGraph> {
|
||||
return of(mockCallGraph).pipe(delay(300));
|
||||
}
|
||||
|
||||
exportGraph(request: ExportGraphRequest): Observable<ExportGraphResult> {
|
||||
if (request.format === 'json') {
|
||||
return of({
|
||||
format: 'json',
|
||||
data: JSON.stringify(mockCallGraph, null, 2),
|
||||
filename: `call-graph-${request.explanationId}.json`,
|
||||
}).pipe(delay(200));
|
||||
}
|
||||
|
||||
if (request.format === 'dot') {
|
||||
const dotContent = `digraph CallGraph {
|
||||
rankdir=LR;
|
||||
node [shape=box];
|
||||
|
||||
${mockNodes.map(n => `"${n.nodeId}" [label="${n.name}"${n.isVulnerable ? ' color=red' : ''}${n.isEntrypoint ? ' color=green' : ''}];`).join('\n ')}
|
||||
|
||||
${mockEdges.map(e => `"${e.sourceId}" -> "${e.targetId}";`).join('\n ')}
|
||||
}`;
|
||||
return of({
|
||||
format: 'dot',
|
||||
data: dotContent,
|
||||
filename: `call-graph-${request.explanationId}.dot`,
|
||||
}).pipe(delay(200));
|
||||
}
|
||||
|
||||
// For PNG/SVG, return a placeholder data URL
|
||||
const svgContent = `<svg xmlns="http://www.w3.org/2000/svg" width="${request.width ?? 800}" height="${request.height ?? 600}">
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<text x="50%" y="50%" text-anchor="middle" fill="#666">Call Graph Visualization</text>
|
||||
</svg>`;
|
||||
const dataUrl = `data:image/svg+xml;base64,${btoa(svgContent)}`;
|
||||
|
||||
return of({
|
||||
format: request.format,
|
||||
dataUrl,
|
||||
filename: `call-graph-${request.explanationId}.${request.format}`,
|
||||
}).pipe(delay(400));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HTTP Client Implementation (for production use)
|
||||
// ============================================================================
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ReachabilityClient implements ReachabilityApi {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly config = inject(AppConfigService);
|
||||
|
||||
getExplanation(scanId: string, cveId: string): Observable<ReachabilityExplanation> {
|
||||
return this.http.get<ReachabilityExplanation>(
|
||||
`${this.config.apiBaseUrl}/scanner/scans/${scanId}/reachability/${encodeURIComponent(cveId)}`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to get reachability explanation: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getSummary(scanId: string): Observable<ReachabilitySummary> {
|
||||
return this.http.get<ReachabilitySummary>(
|
||||
`${this.config.apiBaseUrl}/scanner/scans/${scanId}/reachability/summary`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to get reachability summary: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
analyze(request: ReachabilityAnalysisRequest): Observable<ReachabilityExplanation> {
|
||||
return this.http.post<ReachabilityExplanation>(
|
||||
`${this.config.apiBaseUrl}/scanner/scans/${request.scanId}/reachability/analyze`,
|
||||
request
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to analyze reachability: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getCallGraph(scanId: string): Observable<CallGraph> {
|
||||
return this.http.get<CallGraph>(
|
||||
`${this.config.apiBaseUrl}/scanner/scans/${scanId}/callgraph`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to get call graph: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
exportGraph(request: ExportGraphRequest): Observable<ExportGraphResult> {
|
||||
let params = new HttpParams()
|
||||
.set('format', request.format);
|
||||
|
||||
if (request.highlightPath) {
|
||||
params = params.set('highlightPath', request.highlightPath);
|
||||
}
|
||||
if (request.width) {
|
||||
params = params.set('width', request.width.toString());
|
||||
}
|
||||
if (request.height) {
|
||||
params = params.set('height', request.height.toString());
|
||||
}
|
||||
|
||||
if (request.format === 'png' || request.format === 'svg') {
|
||||
return this.http.get(
|
||||
`${this.config.apiBaseUrl}/reachability/${request.explanationId}/export`,
|
||||
{ params, responseType: 'blob' }
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to export graph: ${error.message}`))
|
||||
),
|
||||
// Convert blob to data URL
|
||||
switchMap(blob => this.blobToDataUrl(blob, request))
|
||||
);
|
||||
}
|
||||
|
||||
return this.http.get<{ data: string }>(
|
||||
`${this.config.apiBaseUrl}/reachability/${request.explanationId}/export`,
|
||||
{ params }
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to export graph: ${error.message}`))
|
||||
),
|
||||
map(response => ({
|
||||
format: request.format,
|
||||
data: response.data,
|
||||
filename: `call-graph-${request.explanationId}.${request.format}`,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
private blobToDataUrl(blob: Blob, request: ExportGraphRequest): Observable<ExportGraphResult> {
|
||||
return new Observable(observer => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
observer.next({
|
||||
format: request.format,
|
||||
dataUrl: reader.result as string,
|
||||
filename: `call-graph-${request.explanationId}.${request.format}`,
|
||||
});
|
||||
observer.complete();
|
||||
};
|
||||
reader.onerror = () => {
|
||||
observer.error(new Error('Failed to read export blob'));
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
}
|
||||
322
src/Web/StellaOps.Web/src/app/core/api/unknowns.client.ts
Normal file
322
src/Web/StellaOps.Web/src/app/core/api/unknowns.client.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* Unknowns Registry API client for Sprint 3500.0004.0002 - T6.
|
||||
* Provides services for managing unknown packages in the registry.
|
||||
*/
|
||||
|
||||
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
|
||||
import { inject, Injectable, InjectionToken } from '@angular/core';
|
||||
import { Observable, of, delay, throwError } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { AppConfigService } from '../config/app-config.service';
|
||||
import {
|
||||
UnknownEntry,
|
||||
UnknownsFilter,
|
||||
UnknownsListResponse,
|
||||
UnknownsSummary,
|
||||
EscalateUnknownRequest,
|
||||
ResolveUnknownRequest,
|
||||
BulkUnknownsRequest,
|
||||
BulkUnknownsResult,
|
||||
UnknownBand,
|
||||
} from './unknowns.models';
|
||||
|
||||
// ============================================================================
|
||||
// Injection Token
|
||||
// ============================================================================
|
||||
|
||||
export const UNKNOWNS_API = new InjectionToken<UnknownsApi>('UNKNOWNS_API');
|
||||
|
||||
// ============================================================================
|
||||
// API Interface
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* API interface for unknowns registry operations.
|
||||
*/
|
||||
export interface UnknownsApi {
|
||||
list(filter?: UnknownsFilter): Observable<UnknownsListResponse>;
|
||||
get(unknownId: string): Observable<UnknownEntry>;
|
||||
getSummary(): Observable<UnknownsSummary>;
|
||||
escalate(request: EscalateUnknownRequest): Observable<UnknownEntry>;
|
||||
resolve(request: ResolveUnknownRequest): Observable<UnknownEntry>;
|
||||
bulkAction(request: BulkUnknownsRequest): Observable<BulkUnknownsResult>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mock Data Fixtures
|
||||
// ============================================================================
|
||||
|
||||
const mockUnknowns: UnknownEntry[] = [
|
||||
{
|
||||
unknownId: 'unk-001',
|
||||
package: { name: 'lodash', version: '4.17.15', ecosystem: 'npm', purl: 'pkg:npm/lodash@4.17.15' },
|
||||
band: 'HOT',
|
||||
status: 'pending',
|
||||
rank: 1,
|
||||
occurrenceCount: 47,
|
||||
firstSeenAt: '2025-12-15T08:00:00Z',
|
||||
lastSeenAt: '2025-12-20T09:30:00Z',
|
||||
ageInDays: 5,
|
||||
relatedCves: ['CVE-2020-8203', 'CVE-2021-23337'],
|
||||
recentOccurrences: [
|
||||
{ scanId: 'scan-001', imageDigest: 'sha256:abc123...', imageName: 'myapp:latest', detectedAt: '2025-12-20T09:30:00Z' },
|
||||
{ scanId: 'scan-002', imageDigest: 'sha256:def456...', imageName: 'api-service:v1.2', detectedAt: '2025-12-20T08:15:00Z' },
|
||||
],
|
||||
},
|
||||
{
|
||||
unknownId: 'unk-002',
|
||||
package: { name: 'requests', version: '2.25.0', ecosystem: 'pypi', purl: 'pkg:pypi/requests@2.25.0' },
|
||||
band: 'HOT',
|
||||
status: 'escalated',
|
||||
rank: 2,
|
||||
occurrenceCount: 32,
|
||||
firstSeenAt: '2025-12-10T14:00:00Z',
|
||||
lastSeenAt: '2025-12-20T07:45:00Z',
|
||||
ageInDays: 10,
|
||||
assignee: 'security-team',
|
||||
notes: 'Investigating potential CVE mapping',
|
||||
recentOccurrences: [
|
||||
{ scanId: 'scan-003', imageDigest: 'sha256:ghi789...', imageName: 'ml-worker:latest', detectedAt: '2025-12-20T07:45:00Z' },
|
||||
],
|
||||
},
|
||||
{
|
||||
unknownId: 'unk-003',
|
||||
package: { name: 'spring-core', version: '5.3.8', ecosystem: 'maven', purl: 'pkg:maven/org.springframework/spring-core@5.3.8' },
|
||||
band: 'WARM',
|
||||
status: 'pending',
|
||||
rank: 1,
|
||||
occurrenceCount: 15,
|
||||
firstSeenAt: '2025-12-01T10:00:00Z',
|
||||
lastSeenAt: '2025-12-19T16:20:00Z',
|
||||
ageInDays: 19,
|
||||
recentOccurrences: [
|
||||
{ scanId: 'scan-004', imageDigest: 'sha256:jkl012...', imageName: 'backend:v2.0', detectedAt: '2025-12-19T16:20:00Z' },
|
||||
],
|
||||
},
|
||||
{
|
||||
unknownId: 'unk-004',
|
||||
package: { name: 'Newtonsoft.Json', version: '12.0.3', ecosystem: 'nuget', purl: 'pkg:nuget/Newtonsoft.Json@12.0.3' },
|
||||
band: 'WARM',
|
||||
status: 'pending',
|
||||
rank: 2,
|
||||
occurrenceCount: 8,
|
||||
firstSeenAt: '2025-11-25T09:00:00Z',
|
||||
lastSeenAt: '2025-12-18T11:30:00Z',
|
||||
ageInDays: 25,
|
||||
recentOccurrences: [],
|
||||
},
|
||||
{
|
||||
unknownId: 'unk-005',
|
||||
package: { name: 'deprecated-pkg', version: '1.0.0', ecosystem: 'npm' },
|
||||
band: 'COLD',
|
||||
status: 'pending',
|
||||
rank: 1,
|
||||
occurrenceCount: 2,
|
||||
firstSeenAt: '2025-10-01T08:00:00Z',
|
||||
lastSeenAt: '2025-11-15T14:00:00Z',
|
||||
ageInDays: 80,
|
||||
recentOccurrences: [],
|
||||
},
|
||||
];
|
||||
|
||||
const mockSummary: UnknownsSummary = {
|
||||
hotCount: 2,
|
||||
warmCount: 2,
|
||||
coldCount: 1,
|
||||
totalCount: 5,
|
||||
pendingCount: 4,
|
||||
escalatedCount: 1,
|
||||
resolvedToday: 3,
|
||||
oldestUnresolvedDays: 80,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Mock Service Implementation
|
||||
// ============================================================================
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MockUnknownsApi implements UnknownsApi {
|
||||
list(filter?: UnknownsFilter): Observable<UnknownsListResponse> {
|
||||
let items = [...mockUnknowns];
|
||||
|
||||
// Apply filters
|
||||
if (filter?.band) {
|
||||
items = items.filter(u => u.band === filter.band);
|
||||
}
|
||||
if (filter?.status) {
|
||||
items = items.filter(u => u.status === filter.status);
|
||||
}
|
||||
if (filter?.ecosystem) {
|
||||
items = items.filter(u => u.package.ecosystem === filter.ecosystem);
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
if (filter?.sortBy) {
|
||||
items.sort((a, b) => {
|
||||
let comparison = 0;
|
||||
switch (filter.sortBy) {
|
||||
case 'rank':
|
||||
comparison = a.rank - b.rank;
|
||||
break;
|
||||
case 'age':
|
||||
comparison = a.ageInDays - b.ageInDays;
|
||||
break;
|
||||
case 'occurrenceCount':
|
||||
comparison = a.occurrenceCount - b.occurrenceCount;
|
||||
break;
|
||||
case 'lastSeen':
|
||||
comparison = new Date(a.lastSeenAt).getTime() - new Date(b.lastSeenAt).getTime();
|
||||
break;
|
||||
}
|
||||
return filter.sortOrder === 'desc' ? -comparison : comparison;
|
||||
});
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
const offset = filter?.offset ?? 0;
|
||||
const limit = filter?.limit ?? 50;
|
||||
const paginatedItems = items.slice(offset, offset + limit);
|
||||
|
||||
return of({
|
||||
items: paginatedItems,
|
||||
total: items.length,
|
||||
limit,
|
||||
offset,
|
||||
hasMore: offset + limit < items.length,
|
||||
}).pipe(delay(200));
|
||||
}
|
||||
|
||||
get(unknownId: string): Observable<UnknownEntry> {
|
||||
const entry = mockUnknowns.find(u => u.unknownId === unknownId);
|
||||
if (!entry) {
|
||||
return throwError(() => new Error(`Unknown not found: ${unknownId}`));
|
||||
}
|
||||
return of(entry).pipe(delay(100));
|
||||
}
|
||||
|
||||
getSummary(): Observable<UnknownsSummary> {
|
||||
return of(mockSummary).pipe(delay(150));
|
||||
}
|
||||
|
||||
escalate(request: EscalateUnknownRequest): Observable<UnknownEntry> {
|
||||
const entry = mockUnknowns.find(u => u.unknownId === request.unknownId);
|
||||
if (!entry) {
|
||||
return throwError(() => new Error(`Unknown not found: ${request.unknownId}`));
|
||||
}
|
||||
return of({
|
||||
...entry,
|
||||
status: 'escalated' as const,
|
||||
assignee: request.assignTo,
|
||||
notes: request.reason,
|
||||
}).pipe(delay(300));
|
||||
}
|
||||
|
||||
resolve(request: ResolveUnknownRequest): Observable<UnknownEntry> {
|
||||
const entry = mockUnknowns.find(u => u.unknownId === request.unknownId);
|
||||
if (!entry) {
|
||||
return throwError(() => new Error(`Unknown not found: ${request.unknownId}`));
|
||||
}
|
||||
return of({
|
||||
...entry,
|
||||
status: 'resolved' as const,
|
||||
notes: request.notes,
|
||||
relatedCves: request.mappedCve ? [request.mappedCve, ...(entry.relatedCves ?? [])] : entry.relatedCves,
|
||||
}).pipe(delay(300));
|
||||
}
|
||||
|
||||
bulkAction(request: BulkUnknownsRequest): Observable<BulkUnknownsResult> {
|
||||
return of({
|
||||
successCount: request.unknownIds.length,
|
||||
failureCount: 0,
|
||||
}).pipe(delay(500));
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HTTP Client Implementation (for production use)
|
||||
// ============================================================================
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class UnknownsClient implements UnknownsApi {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly config = inject(AppConfigService);
|
||||
|
||||
list(filter?: UnknownsFilter): Observable<UnknownsListResponse> {
|
||||
let params = new HttpParams();
|
||||
|
||||
if (filter) {
|
||||
if (filter.band) params = params.set('band', filter.band);
|
||||
if (filter.status) params = params.set('status', filter.status);
|
||||
if (filter.ecosystem) params = params.set('ecosystem', filter.ecosystem);
|
||||
if (filter.scanId) params = params.set('scanId', filter.scanId);
|
||||
if (filter.imageDigest) params = params.set('imageDigest', filter.imageDigest);
|
||||
if (filter.assignee) params = params.set('assignee', filter.assignee);
|
||||
if (filter.limit) params = params.set('limit', filter.limit.toString());
|
||||
if (filter.offset) params = params.set('offset', filter.offset.toString());
|
||||
if (filter.sortBy) params = params.set('sortBy', filter.sortBy);
|
||||
if (filter.sortOrder) params = params.set('sortOrder', filter.sortOrder);
|
||||
}
|
||||
|
||||
return this.http.get<UnknownsListResponse>(
|
||||
`${this.config.apiBaseUrl}/policy/unknowns`,
|
||||
{ params }
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to list unknowns: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get(unknownId: string): Observable<UnknownEntry> {
|
||||
return this.http.get<UnknownEntry>(
|
||||
`${this.config.apiBaseUrl}/policy/unknowns/${unknownId}`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to get unknown: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getSummary(): Observable<UnknownsSummary> {
|
||||
return this.http.get<UnknownsSummary>(
|
||||
`${this.config.apiBaseUrl}/policy/unknowns/summary`
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to get unknowns summary: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
escalate(request: EscalateUnknownRequest): Observable<UnknownEntry> {
|
||||
return this.http.post<UnknownEntry>(
|
||||
`${this.config.apiBaseUrl}/policy/unknowns/${request.unknownId}/escalate`,
|
||||
request
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to escalate unknown: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
resolve(request: ResolveUnknownRequest): Observable<UnknownEntry> {
|
||||
return this.http.post<UnknownEntry>(
|
||||
`${this.config.apiBaseUrl}/policy/unknowns/${request.unknownId}/resolve`,
|
||||
request
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to resolve unknown: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
bulkAction(request: BulkUnknownsRequest): Observable<BulkUnknownsResult> {
|
||||
return this.http.post<BulkUnknownsResult>(
|
||||
`${this.config.apiBaseUrl}/policy/unknowns/bulk`,
|
||||
request
|
||||
).pipe(
|
||||
catchError((error: HttpErrorResponse) =>
|
||||
throwError(() => new Error(`Failed to perform bulk action: ${error.message}`))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user