themed the bulk of advisories
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
# CVSS and Competitive Analysis Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 29-Nov-2025 - CVSS v4.0 Momentum in Vulnerability Management
|
||||
- 30-Nov-2025 - Comparative Evidence Patterns for Stella Ops
|
||||
- 03-Dec-2025 - Next‑Gen Scanner Differentiators and Evidence Moat
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. CVSS V4.0 INTEGRATION
|
||||
|
||||
### 1.1 Requirements
|
||||
|
||||
- Vendors (NVD, GitHub, Microsoft, Snyk) shipping CVSS v4 signals
|
||||
- Awareness needed for receipt schemas, reporting, UI alignment
|
||||
|
||||
### 1.2 Determinism & Offline
|
||||
|
||||
- Keep CVSS vector parsing deterministic
|
||||
- Pin scoring library versions in receipts
|
||||
- Avoid live API dependency
|
||||
- Rely on mirrored NVD feeds or frozen samples
|
||||
|
||||
### 1.3 Schema Mapping
|
||||
|
||||
- Map impacts to receipt schemas
|
||||
- Identify UI/reporting deltas for transparency
|
||||
- Note in sprint Decisions & Risks for CVSS receipts
|
||||
|
||||
## 2. SCANNER DISCREPANCIES ANALYSIS
|
||||
|
||||
### 2.1 Trivy vs Grype Comparative Study (927 images)
|
||||
|
||||
**Findings**:
|
||||
- Tools disagreed on total vulnerability counts and specific CVE IDs
|
||||
- Grype: ~603,259 vulns; Trivy: ~473,661 vulns
|
||||
- Exact match in only 9.2% of cases (80 out of 865 vulnerable images)
|
||||
- Even with same counts, specific vulnerability IDs differed
|
||||
|
||||
**Root Causes**:
|
||||
- Divergent vulnerability databases
|
||||
- Differing matching logic
|
||||
- Incomplete visibility
|
||||
|
||||
### 2.2 VEX Tools Consistency Study (2025)
|
||||
|
||||
**Tools Tested**:
|
||||
- Trivy
|
||||
- Grype
|
||||
- OWASP DepScan
|
||||
- Docker Scout
|
||||
- Snyk CLI
|
||||
- OSV-Scanner
|
||||
- Vexy
|
||||
|
||||
**Results**:
|
||||
- Low consistency/similarity across container scanners
|
||||
- DepScan: 18,680 vulns; Vexy: 191 vulns (2 orders of magnitude difference)
|
||||
- Pairwise Jaccard indices very low (near 0)
|
||||
- 4 most consistent tools shared only ~18% common vulnerabilities
|
||||
|
||||
### 2.3 Implications for StellaOps
|
||||
|
||||
**Moats Needed**:
|
||||
- Golden-fixture benchmarks (container images with known, audited vulnerabilities)
|
||||
- Deterministic, replayable scans
|
||||
- Cryptographic integrity
|
||||
- VEX/SBOM proofs
|
||||
|
||||
**Metrics**:
|
||||
- **Closure rate**: Time from flagged to confirmed exploitable
|
||||
- **Proof coverage**: % of dependencies with valid SBOM/VEX proofs
|
||||
- **Differential-closure**: Impact of database updates or policy changes on prior scan results
|
||||
|
||||
## 3. RUNTIME REACHABILITY APPROACHES
|
||||
|
||||
### 3.1 Runtime-Aware Vulnerability Prioritization
|
||||
|
||||
**Approach**:
|
||||
- Monitor container workloads at runtime to determine which vulnerable components are actually used
|
||||
- Use eBPF-based monitors, dynamic tracers, or built-in profiling
|
||||
- Construct runtime call graph or dependency graph
|
||||
- Map vulnerabilities to code entities (functions/modules)
|
||||
- If execution trace covers entity, vulnerability is "reachable"
|
||||
|
||||
**Findings**: ~85% of critical vulns in containers are in inactive code (Sysdig)
|
||||
|
||||
### 3.2 Reachability Analysis Techniques
|
||||
|
||||
**Static**:
|
||||
- Call-graph analysis (Snyk reachability, CodeQL)
|
||||
- All possible paths
|
||||
|
||||
**Dynamic**:
|
||||
- Runtime observation (loaded modules, invoked functions)
|
||||
- Actual runtime paths
|
||||
|
||||
**Granularity Levels**:
|
||||
- Function-level (precise, limited languages: Java, .NET)
|
||||
- Package/module-level (broader, coarse)
|
||||
|
||||
**Hybrid Approach**: Combine static (all possible paths) + dynamic (actual runtime paths)
|
||||
|
||||
## 4. CONTAINER PROVENANCE & SUPPLY CHAIN
|
||||
|
||||
### 4.1 In-Toto/DSSE Framework (NDSS 2024)
|
||||
|
||||
**Purpose**:
|
||||
- Track chain of custody in software builds
|
||||
- Signed metadata (attestations) for each step
|
||||
- DSSE: Dead Simple Signing Envelope for standardized signing
|
||||
|
||||
### 4.2 Scudo System
|
||||
|
||||
**Features**:
|
||||
- Combines in-toto with Uptane
|
||||
- Verifies build process and final image
|
||||
- Full verification on client inefficient; verify upstream and trust summary
|
||||
- Client checks final signature + hash only
|
||||
|
||||
### 4.3 Supply Chain Verification
|
||||
|
||||
**Signers**:
|
||||
- Developer key signs code commit
|
||||
- CI key signs build attestation
|
||||
- Scanner key signs vulnerability attestation
|
||||
- Release key signs container image
|
||||
|
||||
**Verification Optimization**: Repository verifies in-toto attestations; client verifies final metadata only
|
||||
|
||||
## 5. VENDOR EVIDENCE PATTERNS
|
||||
|
||||
### 5.1 Snyk
|
||||
|
||||
**Evidence Handling**:
|
||||
- Runtime insights integration (Nov 2025)
|
||||
- Evolution from static-scan noise to prioritized workflow
|
||||
- Deployment context awareness
|
||||
|
||||
**VEX Support**:
|
||||
- CycloneDX VEX format
|
||||
- Reachability-aware suppression
|
||||
|
||||
### 5.2 GitHub Advanced Security
|
||||
|
||||
**Features**:
|
||||
- CodeQL for static analysis
|
||||
- Dependency graph
|
||||
- Dependabot alerts
|
||||
- Security advisories
|
||||
|
||||
**Evidence**:
|
||||
- SARIF output
|
||||
- SBOM generation (SPDX)
|
||||
|
||||
### 5.3 Aqua Security
|
||||
|
||||
**Approach**:
|
||||
- Runtime protection
|
||||
- Image scanning
|
||||
- Kubernetes security
|
||||
|
||||
**Evidence**:
|
||||
- Dynamic runtime traces
|
||||
- Network policy violations
|
||||
|
||||
### 5.4 Anchore/Grype
|
||||
|
||||
**Features**:
|
||||
- Open-source scanner
|
||||
- Policy-based compliance
|
||||
- SBOM generation
|
||||
|
||||
**Evidence**:
|
||||
- CycloneDX/SPDX SBOM
|
||||
- Vulnerability reports (JSON)
|
||||
|
||||
### 5.5 Prisma Cloud
|
||||
|
||||
**Features**:
|
||||
- Cloud-native security
|
||||
- Runtime defense
|
||||
- Compliance monitoring
|
||||
|
||||
**Evidence**:
|
||||
- Multi-cloud attestations
|
||||
- Compliance reports
|
||||
|
||||
## 6. STELLAOPS DIFFERENTIATORS
|
||||
|
||||
### 6.1 Reachability-with-Evidence
|
||||
|
||||
**Why it Matters**:
|
||||
- Snyk Container integrating runtime insights as "signal" (Nov 2025)
|
||||
- Evolution from static-scan noise to prioritized, actionable workflow
|
||||
- Deployment context: what's running, what's reachable, what's exploitable
|
||||
|
||||
**Implication**: Container security triage relies on runtime/context signals
|
||||
|
||||
### 6.2 Proof-First Architecture
|
||||
|
||||
**Advantages**:
|
||||
- Every claim backed by DSSE-signed attestations
|
||||
- Cryptographic integrity
|
||||
- Audit trail
|
||||
- Offline verification
|
||||
|
||||
### 6.3 Deterministic Scanning
|
||||
|
||||
**Advantages**:
|
||||
- Reproducible results
|
||||
- Bit-identical outputs given same inputs
|
||||
- Replay manifests
|
||||
- Golden fixture benchmarks
|
||||
|
||||
### 6.4 VEX-First Decisioning
|
||||
|
||||
**Advantages**:
|
||||
- Exploitability modeled in OpenVEX
|
||||
- Lattice logic for stable outcomes
|
||||
- Evidence-linked justifications
|
||||
|
||||
### 6.5 Offline/Air-Gap First
|
||||
|
||||
**Advantages**:
|
||||
- No hidden network dependencies
|
||||
- Bundled feeds, keys, Rekor snapshots
|
||||
- Verifiable without internet access
|
||||
|
||||
## 7. COMPETITIVE POSITIONING
|
||||
|
||||
### 7.1 Market Segments
|
||||
|
||||
| Vendor | Strength | Weakness vs StellaOps |
|
||||
|--------|----------|----------------------|
|
||||
| Snyk | Developer experience | Less deterministic, SaaS-only |
|
||||
| Aqua | Runtime protection | Less reachability precision |
|
||||
| Anchore | Open-source, SBOM | Less proof infrastructure |
|
||||
| Prisma Cloud | Cloud-native breadth | Less offline/air-gap support |
|
||||
| GitHub | Integration with dev workflow | Less cryptographic proof chain |
|
||||
|
||||
### 7.2 StellaOps Unique Value
|
||||
|
||||
1. **Deterministic + Provable**: Bit-identical scans with cryptographic proofs
|
||||
2. **Reachability + Runtime**: Hybrid static/dynamic analysis
|
||||
3. **Offline/Sovereign**: Air-gap operation with regional crypto (FIPS/GOST/eIDAS/SM)
|
||||
4. **VEX-First**: Evidence-backed decisioning, not just alerting
|
||||
5. **AGPL-3.0**: Self-hostable, no vendor lock-in
|
||||
|
||||
## 8. MOAT METRICS
|
||||
|
||||
### 8.1 Proof Coverage
|
||||
|
||||
```
|
||||
proof_coverage = findings_with_valid_receipts / total_findings
|
||||
Target: ≥95%
|
||||
```
|
||||
|
||||
### 8.2 Closure Rate
|
||||
|
||||
```
|
||||
closure_rate = time_from_flagged_to_confirmed_exploitable
|
||||
Target: P95 < 24 hours
|
||||
```
|
||||
|
||||
### 8.3 Differential-Closure Impact
|
||||
|
||||
```
|
||||
differential_impact = findings_changed_after_db_update / total_findings
|
||||
Target: <5% (non-code changes)
|
||||
```
|
||||
|
||||
### 8.4 False Positive Reduction
|
||||
|
||||
```
|
||||
fp_reduction = (baseline_fp_rate - stella_fp_rate) / baseline_fp_rate
|
||||
Target: ≥50% vs baseline scanner
|
||||
```
|
||||
|
||||
### 8.5 Reachability Accuracy
|
||||
|
||||
```
|
||||
reachability_accuracy = correct_r0_r1_r2_r3_classifications / total_classifications
|
||||
Target: ≥90%
|
||||
```
|
||||
|
||||
## 9. COMPETITIVE INTELLIGENCE TRACKING
|
||||
|
||||
### 9.1 Feature Parity Matrix
|
||||
|
||||
| Feature | Snyk | Aqua | Anchore | Prisma | StellaOps |
|
||||
|---------|------|------|---------|--------|-----------|
|
||||
| SBOM Generation | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| VEX Support | ✓ | ✗ | Partial | ✗ | ✓ |
|
||||
| Reachability Analysis | ✓ | ✗ | ✗ | ✗ | ✓ |
|
||||
| Runtime Evidence | ✓ | ✓ | ✗ | ✓ | ✓ |
|
||||
| Cryptographic Proofs | ✗ | ✗ | ✗ | ✗ | ✓ |
|
||||
| Deterministic Scans | ✗ | ✗ | ✗ | ✗ | ✓ |
|
||||
| Offline/Air-Gap | ✗ | Partial | ✗ | ✗ | ✓ |
|
||||
| Regional Crypto | ✗ | ✗ | ✗ | ✗ | ✓ |
|
||||
|
||||
### 9.2 Monitoring Strategy
|
||||
|
||||
- Track vendor release notes
|
||||
- Monitor GitHub repos for feature announcements
|
||||
- Participate in security conferences
|
||||
- Engage with customer feedback
|
||||
- Update competitive matrix quarterly
|
||||
|
||||
## 10. MESSAGING FRAMEWORK
|
||||
|
||||
### 10.1 Core Message
|
||||
|
||||
"StellaOps provides deterministic, proof-backed vulnerability management with reachability analysis for offline/air-gapped environments."
|
||||
|
||||
### 10.2 Key Differentiators (Elevator Pitch)
|
||||
|
||||
1. **Deterministic**: Same inputs → same outputs, every time
|
||||
2. **Provable**: Cryptographic proof chains for every decision
|
||||
3. **Reachable**: Static + runtime analysis, not just presence
|
||||
4. **Sovereign**: Offline operation, regional crypto compliance
|
||||
5. **Open**: AGPL-3.0, self-hostable, no lock-in
|
||||
|
||||
### 10.3 Target Personas
|
||||
|
||||
- **Security Engineers**: Need proof-backed decisions for audits
|
||||
- **DevOps Teams**: Need deterministic scans in CI/CD
|
||||
- **Compliance Officers**: Need offline/air-gap for regulated environments
|
||||
- **Platform Engineers**: Need self-hostable, sovereign solution
|
||||
|
||||
## 11. BENCHMARKING PROTOCOL
|
||||
|
||||
### 11.1 Comparative Test Suite
|
||||
|
||||
**Images**:
|
||||
- 50 representative production images
|
||||
- Known vulnerabilities labeled
|
||||
- Reachability ground truth established
|
||||
|
||||
**Metrics**:
|
||||
- Precision (1 - FP rate)
|
||||
- Recall (TP / (TP + FN))
|
||||
- F1 score
|
||||
- Scan time (P50, P95)
|
||||
- Determinism (identical outputs over 10 runs)
|
||||
|
||||
### 11.2 Test Execution
|
||||
|
||||
```bash
|
||||
# Run StellaOps scan
|
||||
stellaops scan --image test-image:v1 --output stella-results.json
|
||||
|
||||
# Run competitor scans
|
||||
trivy image --format json test-image:v1 > trivy-results.json
|
||||
grype test-image:v1 -o json > grype-results.json
|
||||
snyk container test test-image:v1 --json > snyk-results.json
|
||||
|
||||
# Compare results
|
||||
stellaops benchmark compare \
|
||||
--ground-truth ground-truth.json \
|
||||
--stella stella-results.json \
|
||||
--trivy trivy-results.json \
|
||||
--grype grype-results.json \
|
||||
--snyk snyk-results.json
|
||||
```
|
||||
|
||||
### 11.3 Results Publication
|
||||
|
||||
- Publish benchmarks quarterly
|
||||
- Open-source test images and ground truth
|
||||
- Invite community contributions
|
||||
- Document methodology transparently
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,640 @@
|
||||
# Determinism and Reproducibility Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 07-Dec-2025 - Designing Deterministic Vulnerability Scores
|
||||
- 12-Dec-2025 - Designing a Deterministic Vulnerability Scoring Matrix
|
||||
- 12-Dec-2025 - Replay Fidelity as a Proof Metric
|
||||
- 01-Dec-2025 - Benchmarks for a Testable Security Moat
|
||||
- 02-Dec-2025 - Benchmarking a Testable Security Moat
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. SCORE FORMULA (BASIS POINTS)
|
||||
|
||||
**Total Score:**
|
||||
```
|
||||
riskScore = (wB*B + wR*R + wE*E + wP*P) / 10000
|
||||
```
|
||||
|
||||
**Default Weights (basis points, sum = 10000):**
|
||||
- `wB=1000` (10%) - Base Severity
|
||||
- `wR=4500` (45%) - Reachability
|
||||
- `wE=3000` (30%) - Evidence
|
||||
- `wP=1500` (15%) - Provenance
|
||||
|
||||
## 2. SUBSCORE DEFINITIONS (0-100 integers)
|
||||
|
||||
### 2.1 BaseSeverity (B)
|
||||
|
||||
```
|
||||
B = round(CVSS * 10) // CVSS 0.0-10.0 → 0-100
|
||||
```
|
||||
|
||||
### 2.2 Reachability (R)
|
||||
|
||||
Hop Buckets:
|
||||
```
|
||||
0-2 hops: 100
|
||||
3 hops: 85
|
||||
4 hops: 70
|
||||
5 hops: 55
|
||||
6 hops: 45
|
||||
7 hops: 35
|
||||
8+ hops: 20
|
||||
unreachable: 0
|
||||
```
|
||||
|
||||
Gate Multipliers (in basis points):
|
||||
```
|
||||
behind feature flag: ×7000
|
||||
auth required: ×8000
|
||||
admin only: ×8500
|
||||
non-default config: ×7500
|
||||
```
|
||||
|
||||
Final R:
|
||||
```
|
||||
R = bucketScore * gateMultiplier / 10000
|
||||
```
|
||||
|
||||
### 2.3 Evidence (E)
|
||||
|
||||
Points:
|
||||
```
|
||||
runtime trace: +60
|
||||
DAST/integration test: +30
|
||||
SAST precise sink: +20
|
||||
SCA presence only: +10
|
||||
```
|
||||
|
||||
Freshness Multiplier (basis points):
|
||||
```
|
||||
≤ 7 days: ×10000
|
||||
≤ 30 days: ×9000
|
||||
≤ 90 days: ×7500
|
||||
≤ 180 days: ×6000
|
||||
≤ 365 days: ×4000
|
||||
> 365 days: ×2000
|
||||
```
|
||||
|
||||
Final E:
|
||||
```
|
||||
E = min(100, sum(points)) * freshness / 10000
|
||||
```
|
||||
|
||||
### 2.4 Provenance (P)
|
||||
|
||||
```
|
||||
unsigned/unknown: 0
|
||||
signed image: 30
|
||||
signed + SBOM hash-linked: 60
|
||||
signed + SBOM + DSSE attestations: 80
|
||||
above + reproducible build match: 100
|
||||
```
|
||||
|
||||
## 3. SCORE POLICY YAML SCHEMA
|
||||
|
||||
```yaml
|
||||
policyVersion: score.v1
|
||||
weightsBps:
|
||||
baseSeverity: 1000
|
||||
reachability: 4500
|
||||
evidence: 3000
|
||||
provenance: 1500
|
||||
|
||||
reachability:
|
||||
hopBuckets:
|
||||
- { maxHops: 2, score: 100 }
|
||||
- { maxHops: 3, score: 85 }
|
||||
- { maxHops: 4, score: 70 }
|
||||
- { maxHops: 5, score: 55 }
|
||||
- { maxHops: 6, score: 45 }
|
||||
- { maxHops: 7, score: 35 }
|
||||
- { maxHops: 9999, score: 20 }
|
||||
unreachableScore: 0
|
||||
gateMultipliersBps:
|
||||
featureFlag: 7000
|
||||
authRequired: 8000
|
||||
adminOnly: 8500
|
||||
nonDefaultConfig: 7500
|
||||
|
||||
evidence:
|
||||
points:
|
||||
runtime: 60
|
||||
dast: 30
|
||||
sast: 20
|
||||
sca: 10
|
||||
freshnessBuckets:
|
||||
- { maxAgeDays: 7, multiplierBps: 10000 }
|
||||
- { maxAgeDays: 30, multiplierBps: 9000 }
|
||||
- { maxAgeDays: 90, multiplierBps: 7500 }
|
||||
- { maxAgeDays: 180, multiplierBps: 6000 }
|
||||
- { maxAgeDays: 365, multiplierBps: 4000 }
|
||||
- { maxAgeDays: 99999, multiplierBps: 2000 }
|
||||
|
||||
provenance:
|
||||
levels:
|
||||
unsigned: 0
|
||||
signed: 30
|
||||
signedWithSbom: 60
|
||||
signedWithSbomAndAttestations: 80
|
||||
reproducible: 100
|
||||
|
||||
overrides:
|
||||
- name: knownExploitedAndReachable
|
||||
when:
|
||||
flags:
|
||||
knownExploited: true
|
||||
minReachability: 70
|
||||
setScore: 95
|
||||
|
||||
- name: unreachableAndOnlySca
|
||||
when:
|
||||
maxReachability: 0
|
||||
maxEvidence: 10
|
||||
clampMaxScore: 25
|
||||
```
|
||||
|
||||
## 4. SCORE DATA CONTRACTS
|
||||
|
||||
### 4.1 ScoreInput
|
||||
|
||||
```json
|
||||
{
|
||||
"asOf": "2025-12-14T10:20:30Z",
|
||||
"policyVersion": "score.v1",
|
||||
"reachabilityDigest": "sha256:...",
|
||||
"evidenceDigest": "sha256:...",
|
||||
"provenanceDigest": "sha256:...",
|
||||
"baseSeverityDigest": "sha256:..."
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 ScoreResult
|
||||
|
||||
```json
|
||||
{
|
||||
"scoreId": "score_...",
|
||||
"riskScore": 73,
|
||||
"subscores": {
|
||||
"baseSeverity": 75,
|
||||
"reachability": 85,
|
||||
"evidence": 60,
|
||||
"provenance": 60
|
||||
},
|
||||
"cvss": {
|
||||
"v": "3.1",
|
||||
"base": 7.5,
|
||||
"environmental": 5.3,
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/..."
|
||||
},
|
||||
"inputsRef": ["evidence_sha256:...", "env_sha256:..."],
|
||||
"policyVersion": "score.v1",
|
||||
"policyDigest": "sha256:...",
|
||||
"engineVersion": "stella-scorer@1.8.2",
|
||||
"computedAt": "2025-12-09T10:20:30Z",
|
||||
"resultDigest": "sha256:...",
|
||||
"explain": [
|
||||
{"factor": "reachability", "value": 85, "reason": "3 hops from HTTP endpoint"},
|
||||
{"factor": "evidence", "value": 60, "reason": "Runtime trace (60pts), 20 days old (×90%)"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 ReachabilityReport
|
||||
|
||||
```json
|
||||
{
|
||||
"artifactDigest": "sha256:...",
|
||||
"graphDigest": "sha256:...",
|
||||
"vulnId": "CVE-2024-1234",
|
||||
"vulnerableSymbol": "org.example.VulnClass.vulnMethod",
|
||||
"entrypoints": ["POST /api/upload"],
|
||||
"shortestPath": {
|
||||
"hops": 3,
|
||||
"nodes": [
|
||||
{"symbol": "UploadController.handleUpload", "file": "Controller.cs", "line": 42},
|
||||
{"symbol": "ProcessorService.process", "file": "Service.cs", "line": 18},
|
||||
{"symbol": "org.example.VulnClass.vulnMethod", "file": null, "line": null}
|
||||
]
|
||||
},
|
||||
"gates": [
|
||||
{"type": "authRequired", "detail": "Requires JWT token"},
|
||||
{"type": "featureFlag", "detail": "FEATURE_UPLOAD_V2=true"}
|
||||
],
|
||||
"computedAt": "2025-12-14T10:15:30Z",
|
||||
"toolVersion": "reachability-analyzer@2.1.0"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 EvidenceBundle
|
||||
|
||||
```json
|
||||
{
|
||||
"evidenceId": "sha256:...",
|
||||
"artifactDigest": "sha256:...",
|
||||
"vulnId": "CVE-2024-1234",
|
||||
"type": "RUNTIME",
|
||||
"tool": "runtime-tracer@1.0.0",
|
||||
"timestamp": "2025-12-10T14:30:00Z",
|
||||
"confidence": 95,
|
||||
"subject": "org.example.VulnClass.vulnMethod",
|
||||
"payloadDigest": "sha256:..."
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 ProvenanceReport
|
||||
|
||||
```json
|
||||
{
|
||||
"artifactDigest": "sha256:...",
|
||||
"signatureChecks": [
|
||||
{"signer": "CI-KEY-1", "algorithm": "ECDSA-P256", "result": "VALID"}
|
||||
],
|
||||
"sbomDigest": "sha256:...",
|
||||
"sbomType": "cyclonedx-1.6",
|
||||
"attestations": ["sha256:...", "sha256:..."],
|
||||
"transparencyLogRefs": ["rekor://..."],
|
||||
"reproducibleMatch": true,
|
||||
"computedAt": "2025-12-14T10:15:30Z",
|
||||
"toolVersion": "provenance-verifier@1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. DETERMINISM CONSTRAINTS
|
||||
|
||||
### 5.1 Fixed-Point Math
|
||||
|
||||
- Use integer basis points (100% = 10,000 bps)
|
||||
- No floating point in scoring math
|
||||
- Round only at final display
|
||||
|
||||
### 5.2 Canonical Serialization
|
||||
|
||||
- RFC-style canonical JSON (JCS)
|
||||
- Sort keys and arrays deterministically
|
||||
- Stable ordering for explanation lists by `(factorId, contributingObjectDigest)`
|
||||
|
||||
### 5.3 Time Handling
|
||||
|
||||
- No implicit time
|
||||
- `asOf` is explicit input
|
||||
- Freshness = `asOf - evidence.timestamp`
|
||||
- Use monotonic time internally
|
||||
|
||||
## 6. FIDELITY METRICS
|
||||
|
||||
### 6.1 Bitwise Fidelity (BF)
|
||||
|
||||
```
|
||||
BF = identical_outputs / total_replays
|
||||
Target: ≥ 0.98
|
||||
```
|
||||
|
||||
### 6.2 Semantic Fidelity (SF)
|
||||
|
||||
- Normalized object comparison (same packages, versions, CVEs, severities, verdicts)
|
||||
- Allows formatting differences
|
||||
|
||||
### 6.3 Policy Fidelity (PF)
|
||||
|
||||
- Final policy decision (pass/fail + reason codes) matches
|
||||
|
||||
## 7. SCAN MANIFEST SCHEMA
|
||||
|
||||
```json
|
||||
{
|
||||
"manifest_version": "1.0",
|
||||
"scan_id": "scan_123",
|
||||
"created_at": "2025-12-12T10:15:30Z",
|
||||
|
||||
"input": {
|
||||
"type": "oci_image",
|
||||
"image_ref": "registry/app@sha256:...",
|
||||
"layers": ["sha256:...", "sha256:..."],
|
||||
"source_provenance": {"repo_sha": "abc123", "build_id": "ci-999"}
|
||||
},
|
||||
|
||||
"scanner": {
|
||||
"engine": "stella",
|
||||
"scanner_image_digest": "sha256:...",
|
||||
"scanner_version": "2025.12.0",
|
||||
"config_digest": "sha256:...",
|
||||
"flags": ["--deep", "--vex"]
|
||||
},
|
||||
|
||||
"feeds": {
|
||||
"vuln_feed_bundle_digest": "sha256:...",
|
||||
"license_db_digest": "sha256:..."
|
||||
},
|
||||
|
||||
"policy": {
|
||||
"policy_bundle_digest": "sha256:...",
|
||||
"policy_set": "prod-default"
|
||||
},
|
||||
|
||||
"environment": {
|
||||
"arch": "amd64",
|
||||
"os": "linux",
|
||||
"tz": "UTC",
|
||||
"locale": "C",
|
||||
"network": "disabled",
|
||||
"clock_mode": "frozen",
|
||||
"clock_value": "2025-12-12T10:15:30Z"
|
||||
},
|
||||
|
||||
"normalization": {
|
||||
"canonicalizer_version": "1.2.0",
|
||||
"sbom_schema": "cyclonedx-1.6",
|
||||
"vex_schema": "cyclonedx-vex-1.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. MISMATCH CLASSIFICATION TAXONOMY
|
||||
|
||||
```
|
||||
- Feed drift
|
||||
- Policy drift
|
||||
- Runtime drift
|
||||
- Scanner drift
|
||||
- Nondeterminism (ordering, concurrency, RNG, time-based logic)
|
||||
- External IO
|
||||
```
|
||||
|
||||
## 9. POSTGRESQL SCHEMA
|
||||
|
||||
```sql
|
||||
CREATE TABLE scan_manifest (
|
||||
manifest_id UUID PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
artifact_digest TEXT NOT NULL,
|
||||
feeds_merkle_root TEXT NOT NULL,
|
||||
engine_build_hash TEXT NOT NULL,
|
||||
policy_lattice_hash TEXT NOT NULL,
|
||||
|
||||
ruleset_hash TEXT NOT NULL,
|
||||
config_flags JSONB NOT NULL,
|
||||
|
||||
environment_fingerprint JSONB NOT NULL,
|
||||
|
||||
raw_manifest JSONB NOT NULL,
|
||||
raw_manifest_sha256 TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE scan_execution (
|
||||
execution_id UUID PRIMARY KEY,
|
||||
manifest_id UUID NOT NULL REFERENCES scan_manifest(manifest_id) ON DELETE CASCADE,
|
||||
|
||||
started_at TIMESTAMPTZ NOT NULL,
|
||||
finished_at TIMESTAMPTZ NOT NULL,
|
||||
|
||||
t_ingest_ms INT NOT NULL,
|
||||
t_analyze_ms INT NOT NULL,
|
||||
t_reachability_ms INT NOT NULL,
|
||||
t_vex_ms INT NOT NULL,
|
||||
t_sign_ms INT NOT NULL,
|
||||
t_publish_ms INT NOT NULL,
|
||||
|
||||
proof_bundle_sha256 TEXT NOT NULL,
|
||||
findings_sha256 TEXT NOT NULL,
|
||||
vex_bundle_sha256 TEXT NOT NULL,
|
||||
|
||||
replay_mode BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE TABLE classification_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
artifact_digest TEXT NOT NULL,
|
||||
manifest_id UUID NOT NULL REFERENCES scan_manifest(manifest_id) ON DELETE CASCADE,
|
||||
execution_id UUID NOT NULL REFERENCES scan_execution(execution_id) ON DELETE CASCADE,
|
||||
|
||||
previous_status TEXT NOT NULL,
|
||||
new_status TEXT NOT NULL,
|
||||
cause TEXT NOT NULL,
|
||||
|
||||
changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE VIEW scan_tte AS
|
||||
SELECT
|
||||
execution_id,
|
||||
manifest_id,
|
||||
(finished_at - started_at) AS tte_interval
|
||||
FROM scan_execution;
|
||||
|
||||
CREATE MATERIALIZED VIEW fn_drift_stats AS
|
||||
SELECT
|
||||
date_trunc('day', changed_at) AS day_bucket,
|
||||
COUNT(*) FILTER (WHERE new_status = 'affected') AS affected_count,
|
||||
COUNT(*) AS total_reclassified,
|
||||
ROUND(
|
||||
(COUNT(*) FILTER (WHERE new_status = 'affected')::numeric /
|
||||
NULLIF(COUNT(*), 0)) * 100, 4
|
||||
) AS drift_percent
|
||||
FROM classification_history
|
||||
GROUP BY 1;
|
||||
```
|
||||
|
||||
## 10. C# CANONICAL DATA STRUCTURES
|
||||
|
||||
```csharp
|
||||
public sealed record CanonicalScanManifest
|
||||
{
|
||||
public required string ArtifactDigest { get; init; }
|
||||
public required string FeedsMerkleRoot { get; init; }
|
||||
public required string EngineBuildHash { get; init; }
|
||||
public required string PolicyLatticeHash { get; init; }
|
||||
public required string RulesetHash { get; init; }
|
||||
|
||||
public required IReadOnlyDictionary<string, string> ConfigFlags { get; init; }
|
||||
public required EnvironmentFingerprint Environment { get; init; }
|
||||
}
|
||||
|
||||
public sealed record EnvironmentFingerprint
|
||||
{
|
||||
public required string CpuModel { get; init; }
|
||||
public required string RuntimeVersion { get; init; }
|
||||
public required string Os { get; init; }
|
||||
public required IReadOnlyDictionary<string, string> Extra { get; init; }
|
||||
}
|
||||
|
||||
public sealed record ScanExecutionMetrics
|
||||
{
|
||||
public required int IngestMs { get; init; }
|
||||
public required int AnalyzeMs { get; init; }
|
||||
public required int ReachabilityMs { get; init; }
|
||||
public required int VexMs { get; init; }
|
||||
public required int SignMs { get; init; }
|
||||
public required int PublishMs { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
## 11. CANONICALIZATION IMPLEMENTATION
|
||||
|
||||
```csharp
|
||||
internal static class CanonicalJson
|
||||
{
|
||||
private static readonly JsonSerializerOptions Options = new()
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public static string Serialize(object obj)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
|
||||
{
|
||||
Indented = false,
|
||||
SkipValidation = false
|
||||
}))
|
||||
{
|
||||
JsonSerializer.Serialize(writer, obj, obj.GetType(), Options);
|
||||
}
|
||||
|
||||
var bytes = stream.ToArray();
|
||||
var canonical = JsonCanonicalizer.Canonicalize(bytes);
|
||||
|
||||
return canonical;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 12. REPLAY RUNNER
|
||||
|
||||
```csharp
|
||||
public static class ReplayRunner
|
||||
{
|
||||
public static ReplayResult Replay(Guid manifestId, IScannerEngine engine)
|
||||
{
|
||||
var manifest = ManifestRepository.Load(manifestId);
|
||||
var canonical = CanonicalJson.Serialize(manifest.RawObject);
|
||||
var canonicalHash = Sha256(canonical);
|
||||
|
||||
if (canonicalHash != manifest.RawManifestSHA256)
|
||||
throw new InvalidOperationException("Manifest integrity violation.");
|
||||
|
||||
using var feeds = FeedSnapshotResolver.Open(manifest.FeedsMerkleRoot);
|
||||
|
||||
var exec = engine.Scan(new ScanRequest
|
||||
{
|
||||
ArtifactDigest = manifest.ArtifactDigest,
|
||||
Feeds = feeds,
|
||||
LatticeHash = manifest.PolicyLatticeHash,
|
||||
EngineBuildHash = manifest.EngineBuildHash,
|
||||
CanonicalManifest = canonical
|
||||
});
|
||||
|
||||
return new ReplayResult(
|
||||
exec.FindingsHash == manifest.FindingsSHA256,
|
||||
exec.VexBundleHash == manifest.VexBundleSHA256,
|
||||
exec.ProofBundleHash == manifest.ProofBundleSHA256,
|
||||
exec
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 13. BENCHMARK METRICS
|
||||
|
||||
### 13.1 Time-to-Evidence (TTE)
|
||||
|
||||
**Definition:**
|
||||
```
|
||||
TTE = t(proof_ready) – t(artifact_ingested)
|
||||
```
|
||||
|
||||
**Targets:**
|
||||
- P50 < 2m for typical containers (≤ 500 MB)
|
||||
- P95 < 5m including cold-start/offline-bundle mode
|
||||
|
||||
**Stage Breakdown:**
|
||||
- t_ingest_ms
|
||||
- t_analyze_ms
|
||||
- t_reachability_ms
|
||||
- t_vex_ms
|
||||
- t_sign_ms
|
||||
- t_publish_ms
|
||||
|
||||
### 13.2 False-Negative Drift Rate (FN-DRIFT)
|
||||
|
||||
**Definition (rolling 30d window):**
|
||||
```
|
||||
FN-Drift = (# artifacts re-classified from {unaffected/unknown} → affected) / (total artifacts re-evaluated)
|
||||
```
|
||||
|
||||
**Stratification:**
|
||||
- feed delta
|
||||
- rule delta
|
||||
- lattice/policy delta
|
||||
- reachability delta
|
||||
|
||||
**Targets:**
|
||||
- Engine-caused FN-Drift ≈ 0
|
||||
- Feed-caused FN-Drift: faster is better
|
||||
|
||||
### 13.3 Deterministic Reproducibility
|
||||
|
||||
**Proof Object:**
|
||||
```json
|
||||
{
|
||||
"artifact_digest": "sha256:...",
|
||||
"scan_manifest_hash": "sha256:...",
|
||||
"feeds_merkle_root": "sha256:...",
|
||||
"engine_build_hash": "sha256:...",
|
||||
"policy_lattice_hash": "sha256:...",
|
||||
"findings_sha256": "sha256:...",
|
||||
"vex_bundle_sha256": "sha256:...",
|
||||
"proof_bundle_sha256": "sha256:..."
|
||||
}
|
||||
```
|
||||
|
||||
**Metric:**
|
||||
```
|
||||
Repro rate = identical_outputs / total_replays
|
||||
Target: 100%
|
||||
```
|
||||
|
||||
### 13.4 Detection Metrics
|
||||
|
||||
```
|
||||
true_positive_count (TP)
|
||||
false_positive_count (FP)
|
||||
false_negative_count (FN)
|
||||
|
||||
precision = TP / (TP + FP)
|
||||
recall = TP / (TP + FN)
|
||||
|
||||
fp_reduction = (baseline_fp_rate - stella_fp_rate) / baseline_fp_rate
|
||||
```
|
||||
|
||||
### 13.5 Proof Coverage
|
||||
|
||||
```
|
||||
proof_coverage_all = findings_with_valid_receipts / total_findings
|
||||
|
||||
proof_coverage_vex = vex_items_with_valid_receipts / total_vex_items
|
||||
|
||||
proof_coverage_reachable = reachable_findings_with_proofs / total_reachable_findings
|
||||
```
|
||||
|
||||
## 14. SLO THRESHOLDS
|
||||
|
||||
**Fidelity:**
|
||||
- BF ≥ 0.98 (general)
|
||||
- BF ≥ 0.95 (regulated projects)
|
||||
- PF ≈ 1.0 (unless policy changed intentionally)
|
||||
|
||||
**Alerts:**
|
||||
- BF drops ≥2% week-over-week → warn
|
||||
- BF < 0.90 overall → page/block release
|
||||
- Regulated BF < 0.95 → page/block release
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,399 @@
|
||||
# Developer Onboarding Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 01-Dec-2025 - Common Developers guides
|
||||
- 29-Nov-2025 - StellaOps – Mid-Level .NET Onboarding (Quick Start)
|
||||
- 30-Nov-2025 - Implementor Guidelines for Stella Ops
|
||||
- 30-Nov-2025 - Standup Sprint Kickstarters
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. CORE ENGINEERING PRINCIPLES
|
||||
|
||||
- **SOLID First**: Interface and dependency inversion required
|
||||
- **100-line File Rule**: Files >100 lines must be split/refactored
|
||||
- **Contracts vs Runtime**: Public DTOs/interfaces in `*.Contracts` projects
|
||||
- **Single Composition Root**: DI wiring in `StellaOps.Web/Program.cs` and plugin `IoCConfigurator`
|
||||
- **No Service Locator**: Constructor injection only
|
||||
- **Fail-fast Startup**: Validate configuration before web host starts
|
||||
- **Hot-load Compatibility**: Avoid static singletons that survive plugin unload
|
||||
|
||||
## 2. REPOSITORY LAYOUT RULES
|
||||
|
||||
- No "Module" folders or nested solution hierarchies
|
||||
- Tests mirror `src/` structure 1:1
|
||||
- No test code in production projects
|
||||
- Feature folder layout: `Scan/ScanService.cs`, `Scan/ScanController.cs`
|
||||
|
||||
## 3. NAMING & STYLE CONVENTIONS
|
||||
|
||||
### 3.1 Namespaces & Files
|
||||
|
||||
- **Namespaces**: File-scoped, `StellaOps.*`
|
||||
- **Classes/records**: PascalCase
|
||||
- **Interfaces**: `I` prefix (`IScannerRunner`)
|
||||
- **Private fields**: `camelCase` (no leading `_`)
|
||||
- **Constants**: `SCREAMING_SNAKE_CASE`
|
||||
- **Async methods**: End with `Async`
|
||||
|
||||
### 3.2 Usings
|
||||
|
||||
- Outside namespace
|
||||
- Sorted
|
||||
- No wildcards
|
||||
|
||||
## 4. C# FEATURE USAGE
|
||||
|
||||
- Nullable reference types enabled
|
||||
- Use `record` for immutable DTOs
|
||||
- Prefer pattern matching over long `switch` cascades
|
||||
- `Span`/`Memory` only when measured as necessary
|
||||
- Use `await foreach` instead of manual iterator loops
|
||||
|
||||
## 5. DI POLICY
|
||||
|
||||
### 5.1 Composition Root
|
||||
|
||||
- **One composition root** per process
|
||||
- Plugins contribute via `[ServiceBinding]` or `IoCConfigurator : IDependencyInjectionRoutine`
|
||||
- Default lifetime: **scoped**
|
||||
- Singletons only for stateless, thread-safe helpers
|
||||
- Never use service locator or manually build nested service providers
|
||||
|
||||
### 5.2 Service Binding Attributes
|
||||
|
||||
```csharp
|
||||
[ServiceBinding(typeof(IMyContract), ServiceLifetime.Scoped)]
|
||||
public class MyService : IMyContract
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Advanced DI Configuration
|
||||
|
||||
```csharp
|
||||
public class MyPluginIoCConfigurator : IDependencyInjectionRoutine
|
||||
{
|
||||
public void Configure(IServiceCollection services, IConfiguration config)
|
||||
{
|
||||
services.AddScoped<IMyContract, MyService>();
|
||||
services.Configure<MyOptions>(config.GetSection("MyPlugin"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. ASYNC & THREADING
|
||||
|
||||
- All I/O is async; avoid `.Result` / `.Wait()`
|
||||
- Library code uses `ConfigureAwait(false)`
|
||||
- Control concurrency with channels or `Parallel.ForEachAsync`
|
||||
|
||||
## 7. TEST LAYERS
|
||||
|
||||
- **Unit**: xUnit
|
||||
- **Property-based**: FsCheck
|
||||
- **Integration**: API with Testcontainers, DB/merge with Mongo + Redis
|
||||
- **Contracts**: gRPC breakage checks with Buf
|
||||
- **Frontend**: Jest (unit), Playwright (e2e), Lighthouse (performance/a11y)
|
||||
- **Non-functional**: k6 (load), Docker (chaos), dependency/license scanning, SBOM reproducibility
|
||||
|
||||
## 8. QUALITY GATES
|
||||
|
||||
- API unit test coverage ≥ ~85%
|
||||
- API P95 latency ≤ ~120ms
|
||||
- Δ-SBOM warm scan P95 ≤ ~5s
|
||||
- Lighthouse perf ≥ ~90, a11y ≥ ~95
|
||||
|
||||
## 9. PLUGIN SYSTEM
|
||||
|
||||
### 9.1 Plugin Templates
|
||||
|
||||
```bash
|
||||
dotnet new stellaops-plugin-schedule -n MyPlugin.Schedule
|
||||
```
|
||||
|
||||
### 9.2 Plugin Publishing
|
||||
|
||||
- Publish signed artifacts to `src/backend/Stella.Ops.Plugin.Binaries/<MyPlugin>/`
|
||||
- Backend verifies Cosign signature
|
||||
- Enforces `[StellaPluginVersion]` compatibility
|
||||
- Loads plugins in isolated `AssemblyLoadContext`s
|
||||
|
||||
### 9.3 Plugin Signing
|
||||
|
||||
```bash
|
||||
dotnet publish -c Release -p:PublishSingleFile=true -o out
|
||||
cosign sign --key $COSIGN_KEY out/MyPlugin.Schedule.dll
|
||||
```
|
||||
|
||||
## 10. POLICY DSL (stella-dsl@1)
|
||||
|
||||
### 10.1 Goals
|
||||
|
||||
- Deterministic
|
||||
- Declarative
|
||||
- Explainable
|
||||
- Offline-friendly
|
||||
- Reachability-aware
|
||||
|
||||
### 10.2 Structure
|
||||
|
||||
- One `policy` block per `.stella` file
|
||||
- Contains: `metadata`, `profile` blocks, `rule` blocks, optional `settings`
|
||||
|
||||
### 10.3 Context Namespaces
|
||||
|
||||
- `sbom`
|
||||
- `advisory`
|
||||
- `vex`
|
||||
- `env`
|
||||
- `telemetry`
|
||||
- `secret`
|
||||
- `profile.*`
|
||||
|
||||
### 10.4 Helpers
|
||||
|
||||
- `normalize_cvss`
|
||||
- `risk_score`
|
||||
- `vex.any`
|
||||
- `vex.latest`
|
||||
- `sbom.any_component`
|
||||
- `exists`
|
||||
- `coalesce`
|
||||
|
||||
### 10.5 Rules
|
||||
|
||||
- Always include clear `because` when changing `status` or `severity`
|
||||
- Avoid catch-all suppressions (`when true` + `status := "suppressed"`)
|
||||
- Use `stella policy lint/compile/simulate` in CI
|
||||
- Test in sealed (offline) mode
|
||||
|
||||
## 11. PR CHECKLIST
|
||||
|
||||
1. Use **Conventional Commit** prefixes (`feat:`, `fix:`, `docs:`)
|
||||
2. Run `dotnet format` and `dotnet test` (both must be green)
|
||||
3. Keep files within 100-line guideline
|
||||
4. Update XML-doc comments for new public API
|
||||
5. Update docs and JSON schema for contract changes
|
||||
6. Ensure analyzers and CI jobs pass
|
||||
|
||||
## 12. ONBOARDING DETERMINISM REQUIREMENTS
|
||||
|
||||
- Use fixed seeds and pinned toolchain versions
|
||||
- Avoid live network calls; prefer cached feeds/mirrors
|
||||
- Note mirror paths in examples
|
||||
|
||||
## 13. SPRINT READINESS CHECKLIST
|
||||
|
||||
- Scanner regressions verification
|
||||
- Postgres slice validation
|
||||
- DSSE/Rekor sweep complete
|
||||
- Pin tool versions in scripts
|
||||
|
||||
## 14. MODULE-SPECIFIC GUIDANCE
|
||||
|
||||
### 14.1 Scanner Module
|
||||
|
||||
- Reachability algorithms only in Scanner.WebService
|
||||
- Cache lazy and keyed by deterministic inputs
|
||||
- Output includes explicit evidence pointers
|
||||
- UI endpoints expose reachability state in structured form
|
||||
|
||||
### 14.2 Authority Module
|
||||
|
||||
- Trust roots: pinned via out-of-band distribution
|
||||
- Key rotation: maintain version history in trust store
|
||||
- Revocation: maintain revoked_keys list in trust anchors
|
||||
|
||||
### 14.3 Excititor (VEX) Module
|
||||
|
||||
- VEX schema includes pointers to all upstream artifacts
|
||||
- No duplication of SBOM/scan content inside VEX
|
||||
- DSSE used as standard envelope type
|
||||
|
||||
### 14.4 Policy Module
|
||||
|
||||
- Facts and policies serialized separately
|
||||
- Lattice code in allowed services only
|
||||
- Merge strategies named and versioned
|
||||
- Artifacts record which lattice algorithm used
|
||||
|
||||
## 15. COMMON PITFALLS & SOLUTIONS
|
||||
|
||||
### 15.1 Avoid
|
||||
|
||||
- ❌ Service Locator pattern
|
||||
- ❌ Static mutable state
|
||||
- ❌ Async void (except event handlers)
|
||||
- ❌ Blocking on async code (.Result, .Wait())
|
||||
- ❌ Non-deterministic ordering
|
||||
- ❌ Hard-coded timestamps
|
||||
- ❌ Environment variables in core algorithms
|
||||
|
||||
### 15.2 Prefer
|
||||
|
||||
- ✅ Constructor injection
|
||||
- ✅ Immutable data structures
|
||||
- ✅ async Task
|
||||
- ✅ await or Task.Run for CPU-bound work
|
||||
- ✅ Stable sorting with explicit comparers
|
||||
- ✅ Explicit `asOf` parameters
|
||||
- ✅ Configuration objects passed as parameters
|
||||
|
||||
## 16. DEBUGGING WORKFLOW
|
||||
|
||||
### 16.1 Local Development
|
||||
|
||||
```bash
|
||||
# Run all services
|
||||
docker-compose up -d
|
||||
|
||||
# Run specific service
|
||||
dotnet run --project src/Scanner/StellaOps.Scanner.WebService
|
||||
|
||||
# Attach debugger
|
||||
# Use VS Code launch.json or Visual Studio F5
|
||||
```
|
||||
|
||||
### 16.2 Log Correlation
|
||||
|
||||
```csharp
|
||||
using var activity = Activity.Current;
|
||||
activity?.SetTag("scan.id", scanId);
|
||||
_logger.LogInformation("Processing scan {ScanId}", scanId);
|
||||
```
|
||||
|
||||
### 16.3 OpenTelemetry
|
||||
|
||||
```csharp
|
||||
services.AddOpenTelemetry()
|
||||
.WithTracing(builder => builder
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddNpgsql()
|
||||
.AddOtlpExporter());
|
||||
```
|
||||
|
||||
## 17. PERFORMANCE OPTIMIZATION
|
||||
|
||||
### 17.1 Database
|
||||
|
||||
- Use indexes for hot queries
|
||||
- Batch inserts/updates
|
||||
- Use `COPY` for bulk data
|
||||
- Avoid N+1 queries
|
||||
|
||||
### 17.2 Memory
|
||||
|
||||
- Use `Span<T>` for hot paths
|
||||
- Pool large objects
|
||||
- Dispose `IDisposable` promptly
|
||||
- Profile with dotMemory
|
||||
|
||||
### 17.3 Caching
|
||||
|
||||
- Cache deterministically (keyed by input hashes)
|
||||
- Use distributed cache (Valkey/Redis) for shared state
|
||||
- TTL appropriate to data volatility
|
||||
|
||||
## 18. SECURITY GUIDELINES
|
||||
|
||||
### 18.1 Input Validation
|
||||
|
||||
- Validate all user inputs
|
||||
- Use allowlists, not denylists
|
||||
- Sanitize for SQL, XSS, path traversal
|
||||
|
||||
### 18.2 Authentication & Authorization
|
||||
|
||||
- Never roll your own crypto
|
||||
- Use standard protocols (OAuth2, OIDC)
|
||||
- Implement principle of least privilege
|
||||
|
||||
### 18.3 Secrets Management
|
||||
|
||||
- Never commit secrets
|
||||
- Use environment variables or KMS
|
||||
- Rotate credentials regularly
|
||||
|
||||
## 19. DOCUMENTATION STANDARDS
|
||||
|
||||
### 19.1 XML Documentation
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Scans the specified artifact for vulnerabilities.
|
||||
/// </summary>
|
||||
/// <param name="artifactId">The artifact identifier.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Scan results with reachability analysis.</returns>
|
||||
/// <exception cref="ArgumentNullException">If artifactId is null.</exception>
|
||||
public Task<ScanResult> ScanAsync(string artifactId, CancellationToken ct);
|
||||
```
|
||||
|
||||
### 19.2 Architecture Decision Records (ADRs)
|
||||
|
||||
```markdown
|
||||
# ADR-XXX: Title
|
||||
|
||||
## Status
|
||||
Proposed | Accepted | Deprecated | Superseded
|
||||
|
||||
## Context
|
||||
What is the issue?
|
||||
|
||||
## Decision
|
||||
What did we decide?
|
||||
|
||||
## Consequences
|
||||
What are the implications?
|
||||
```
|
||||
|
||||
## 20. CI/CD INTEGRATION
|
||||
|
||||
### 20.1 Build Pipeline
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- restore
|
||||
- build
|
||||
- test
|
||||
- analyze
|
||||
- package
|
||||
- deploy
|
||||
```
|
||||
|
||||
### 20.2 Required Checks
|
||||
|
||||
- Unit tests pass
|
||||
- Integration tests pass
|
||||
- Code coverage ≥85%
|
||||
- No high/critical vulnerabilities
|
||||
- SBOM generated
|
||||
- Determinism tests pass
|
||||
|
||||
## 21. MIGRATION GUIDE
|
||||
|
||||
### 21.1 From .NET 8 to .NET 10
|
||||
|
||||
- Update `<TargetFramework>net10.0</TargetFramework>`
|
||||
- Review breaking changes
|
||||
- Update NuGet packages
|
||||
- Test thoroughly
|
||||
|
||||
### 21.2 Database Migrations
|
||||
|
||||
```bash
|
||||
# Create migration
|
||||
dotnet ef migrations add MigrationName -p src/Module -s src/WebService
|
||||
|
||||
# Apply migration
|
||||
dotnet ef database update -p src/Module -s src/WebService
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,379 @@
|
||||
# Offline and Air-Gap Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 01-Dec-2025 - DSSE‑Signed Offline Scanner Updates
|
||||
- 07-Dec-2025 - Reliable Air‑Gap Verification Workflows
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. OFFLINE UPDATE BUNDLE STRUCTURE
|
||||
|
||||
### 1.1 Directory Layout
|
||||
|
||||
```
|
||||
/bundle-2025-12-14/
|
||||
manifest.json # version, created_at, entries[], sha256s
|
||||
payload.tar.zst # actual DB/indices/feeds
|
||||
payload.tar.zst.sha256
|
||||
statement.dsse.json # DSSE-wrapped statement over payload hash
|
||||
rekor-receipt.json # Rekor v2 inclusion/verification material
|
||||
```
|
||||
|
||||
### 1.2 Manifest Schema
|
||||
|
||||
**manifest.json**:
|
||||
```json
|
||||
{
|
||||
"version": "string",
|
||||
"created_at": "UTC ISO-8601",
|
||||
"entries": [{"name": "string", "sha256": "string", "size": int}],
|
||||
"payload_sha256": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 DSSE Predicate Schema
|
||||
|
||||
**statement.dsse.json payload**:
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": {
|
||||
"subject": {
|
||||
"name": "stella-ops-offline-kit-<DATE>.tgz",
|
||||
"digest": {"sha256": "string"}
|
||||
},
|
||||
"predicateType": "https://stella-ops.org/attestations/offline-update/1",
|
||||
"predicate": {
|
||||
"offline_manifest_sha256": "string",
|
||||
"feeds": [{"name": "string", "snapshot_date": "UTC ISO-8601", "archive_digest": "string"}],
|
||||
"builder": "string",
|
||||
"created_at": "UTC ISO-8601",
|
||||
"oukit_channel": "edge|stable|fips-profile"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4 Rekor Receipt Schema
|
||||
|
||||
**rekor-receipt.json**:
|
||||
```json
|
||||
{
|
||||
"uuid": "string",
|
||||
"logIndex": int,
|
||||
"rootHash": "string",
|
||||
"hashes": ["string"],
|
||||
"checkpoint": "string"
|
||||
}
|
||||
```
|
||||
|
||||
## 2. VERIFICATION SEQUENCE
|
||||
|
||||
### 2.1 Offline Kit Import Steps
|
||||
|
||||
```
|
||||
1. Validate Cosign signature of tarball
|
||||
2. Validate offline-manifest.json with JWS signature
|
||||
3. Verify file digests for all entries (including /attestations/*)
|
||||
4. Verify DSSE:
|
||||
- Call StellaOps.Attestor.Verify with:
|
||||
- offline-update.dsse.json
|
||||
- offline-update.rekor.json
|
||||
- local Rekor log snapshot/segment
|
||||
- Ensure payload digest matches kit tarball + manifest digests
|
||||
5. Only after all checks pass:
|
||||
- Swap Scanner's feed pointer to new snapshot
|
||||
- Emit audit event (kit filename, tarball digest, DSSE digest, Rekor UUID + log index)
|
||||
```
|
||||
|
||||
### 2.2 Activation Acceptance Rules
|
||||
|
||||
- Trust root: pinned publisher public keys (out-of-band rotation)
|
||||
- Monotonicity: only activate if `manifest.version > current.version`
|
||||
- Atomic switch: unpack → validate → symlink flip (`db/staging/` → `db/active/`)
|
||||
- Quarantine on failure: move to `updates/quarantine/` with reason code
|
||||
|
||||
## 3. OFFLINE DIRECTORY LAYOUT
|
||||
|
||||
```
|
||||
/evidence/
|
||||
keys/
|
||||
roots/ # root/intermediate certs, PGP pubkeys
|
||||
identities/ # per-vendor public keys
|
||||
tlog-root/ # hashed/pinned tlog root(s)
|
||||
policy/
|
||||
verify-policy.yaml
|
||||
lattice-rules.yaml
|
||||
sboms/ # *.cdx.json, *.spdx.json
|
||||
attestations/ # *.intoto.jsonl.dsig (DSSE)
|
||||
tlog/
|
||||
checkpoint.sig # signed tree head
|
||||
entries/ # *.jsonl (Merkle leaves) + proofs
|
||||
tools/
|
||||
cosign-<ver> (sha256)
|
||||
oras-<ver> (sha256)
|
||||
jq-<ver> (sha256)
|
||||
scanner-<ver> (sha256)
|
||||
```
|
||||
|
||||
## 4. OFFLINE VERIFICATION POLICY SCHEMA
|
||||
|
||||
```yaml
|
||||
keys:
|
||||
- ./evidence/keys/identities/vendor_A.pub
|
||||
- ./evidence/keys/identities/your_authority.pub
|
||||
tlog:
|
||||
mode: "offline"
|
||||
checkpoint: "./evidence/tlog/checkpoint.sig"
|
||||
entry_pack: "./evidence/tlog/entries"
|
||||
attestations:
|
||||
required:
|
||||
- type: slsa-provenance
|
||||
- type: cyclonedx-sbom
|
||||
optional:
|
||||
- type: vex
|
||||
constraints:
|
||||
subjects:
|
||||
alg: "sha256"
|
||||
certs:
|
||||
allowed_issuers:
|
||||
- "https://fulcio.offline"
|
||||
allow_expired_if_timepinned: true
|
||||
```
|
||||
|
||||
## 5. DETERMINISTIC EVIDENCE RECONCILIATION ALGORITHM
|
||||
|
||||
```
|
||||
1. Index artifacts by immutable digest
|
||||
2. For each artifact digest:
|
||||
- Collect SBOM nodes from canonical SBOM files
|
||||
- Collect attestations (provenance, VEX, SLSA, signatures)
|
||||
- Validate each attestation (sig + tlog inclusion proof)
|
||||
3. Normalize all docs (stable sort, strip non-essential timestamps, lowercase URIs)
|
||||
4. Apply lattice rules (precedence: vendor > maintainer > 3rd-party)
|
||||
5. Emit `evidence-graph.json` (stable node/edge order) + `evidence-graph.sha256` + DSSE signature
|
||||
```
|
||||
|
||||
## 6. OFFLINE FLOW OPERATIONAL STEPS
|
||||
|
||||
```
|
||||
1. Import bundle (mount WORM media read-only)
|
||||
2. Verify tools (hash + signature) before execution
|
||||
3. Verify tlog checkpoint
|
||||
4. Verify each inclusion proof
|
||||
5. Verify attestations (keyring + policy)
|
||||
6. Ingest SBOMs (canonicalize + hash)
|
||||
7. Reconcile (apply lattice rules → evidence graph)
|
||||
8. Record run: write `run.manifest` with input/policy/tool/output hashes; DSSE-sign with Authority key
|
||||
```
|
||||
|
||||
## 7. SCANNER CONFIG SURFACE
|
||||
|
||||
### 7.1 Offline Kit Configuration
|
||||
|
||||
```yaml
|
||||
scanner:
|
||||
offlineKit:
|
||||
requireDsse: true # fail import if DSSE/Rekor verification fails
|
||||
rekorOfflineMode: true # use local snapshots only
|
||||
attestationVerifier: https://attestor.internal
|
||||
trustAnchors:
|
||||
- anchorId: "UUID"
|
||||
purlPattern: "pkg:npm/*"
|
||||
allowedKeyids: ["key1", "key2"]
|
||||
```
|
||||
|
||||
### 7.2 DSSE/Rekor Failure Handling
|
||||
|
||||
**DSSE/Rekor fail, Cosign + manifest OK**:
|
||||
- Keep old feeds active
|
||||
- Mark import as failed; surface ProblemDetails error via API/UI
|
||||
- Log structured fields: `rekorUuid`, `attestationDigest`, `offlineKitHash`, `failureReason`
|
||||
|
||||
**Config flag to soften during rollout**:
|
||||
- When `requireDsse=false`: treat DSSE/Rekor failure as warning; allow import with alerts
|
||||
|
||||
## 8. SBOM INGESTION DETERMINISTIC FLOW
|
||||
|
||||
```bash
|
||||
# 1. Normalize SBOMs to canonical form
|
||||
jq -S . sboms/app.cdx.json > sboms/_canon/app.cdx.json
|
||||
|
||||
# 2. Validate schemas (vendored validators)
|
||||
|
||||
# 3. Hash-pin canonical files and record in manifest.lock
|
||||
sha256sum sboms/_canon/*.json > manifest.lock
|
||||
|
||||
# 4. Import to DB with idempotent keys: (artifactDigest, sbomHash)
|
||||
```
|
||||
|
||||
## 9. OFFLINE REKOR MIRROR VERIFICATION
|
||||
|
||||
### 9.1 File-Ledger Pattern
|
||||
|
||||
- Keep `tlog/checkpoint.sig` (signed tree head) + `tlog/entries/*.jsonl` (leaves + proofs)
|
||||
|
||||
### 9.2 Verification Steps
|
||||
|
||||
```
|
||||
1. Recompute Merkle root from entries
|
||||
2. Check matches `checkpoint.sig` (after verifying signature with tlog root key)
|
||||
3. For each attestation:
|
||||
- Verify UUID/digest appears in entry pack
|
||||
- Verify inclusion proof resolves
|
||||
```
|
||||
|
||||
## 10. METRICS & OBSERVABILITY
|
||||
|
||||
### 10.1 Offline Kit Metrics (Prometheus)
|
||||
|
||||
```
|
||||
offlinekit_import_total{status="success|failed_dsse|failed_rekor|failed_cosign"}
|
||||
offlinekit_attestation_verify_latency_seconds (histogram)
|
||||
attestor_rekor_success_total
|
||||
attestor_rekor_retry_total
|
||||
rekor_inclusion_latency
|
||||
```
|
||||
|
||||
### 10.2 Structured Logging Fields
|
||||
|
||||
```
|
||||
rekorUuid
|
||||
attestationDigest
|
||||
offlineKitHash
|
||||
failureReason
|
||||
kitFilename
|
||||
tarballDigest
|
||||
dsseStatementDigest
|
||||
rekorLogIndex
|
||||
```
|
||||
|
||||
## 11. ERROR HANDLING
|
||||
|
||||
### 11.1 Import Failure Modes
|
||||
|
||||
| Failure Type | Action | Audit Event |
|
||||
|--------------|--------|-------------|
|
||||
| Cosign signature invalid | Reject, quarantine | `IMPORT_FAILED_COSIGN` |
|
||||
| Manifest signature invalid | Reject, quarantine | `IMPORT_FAILED_MANIFEST` |
|
||||
| DSSE verification failed | Reject (if requireDsse=true) | `IMPORT_FAILED_DSSE` |
|
||||
| Rekor inclusion failed | Reject (if requireDsse=true) | `IMPORT_FAILED_REKOR` |
|
||||
| Digest mismatch | Reject, quarantine | `IMPORT_FAILED_DIGEST` |
|
||||
| Version not monotonic | Reject | `IMPORT_FAILED_VERSION` |
|
||||
|
||||
### 11.2 Quarantine Structure
|
||||
|
||||
```
|
||||
/updates/quarantine/<timestamp>-<reason>/
|
||||
bundle.tar.zst
|
||||
manifest.json
|
||||
verification.log
|
||||
failure-reason.txt
|
||||
```
|
||||
|
||||
## 12. CLI COMMANDS
|
||||
|
||||
### 12.1 Offline Kit Import
|
||||
|
||||
```bash
|
||||
stellaops offline import \
|
||||
--bundle ./bundle-2025-12-14.tar.zst \
|
||||
--verify-dsse \
|
||||
--verify-rekor \
|
||||
--trust-root /evidence/keys/roots/stella-root.pub
|
||||
```
|
||||
|
||||
### 12.2 Offline Kit Status
|
||||
|
||||
```bash
|
||||
stellaops offline status
|
||||
# Output:
|
||||
# Active kit: bundle-2025-12-14
|
||||
# Kit digest: sha256:abc123...
|
||||
# Activated at: 2025-12-14T10:00:00Z
|
||||
# DSSE verified: true
|
||||
# Rekor verified: true
|
||||
```
|
||||
|
||||
### 12.3 Offline Verification
|
||||
|
||||
```bash
|
||||
stellaops verify offline \
|
||||
--evidence-dir /evidence \
|
||||
--artifact sha256:def456... \
|
||||
--policy verify-policy.yaml
|
||||
```
|
||||
|
||||
## 13. AUDIT TRAIL
|
||||
|
||||
### 13.1 Audit Event Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"eventId": "uuid",
|
||||
"eventType": "OFFLINE_KIT_IMPORTED",
|
||||
"timestamp": "2025-12-14T10:00:00Z",
|
||||
"actor": "system",
|
||||
"details": {
|
||||
"kitFilename": "bundle-2025-12-14.tar.zst",
|
||||
"tarballDigest": "sha256:...",
|
||||
"dsseStatementDigest": "sha256:...",
|
||||
"rekorUuid": "...",
|
||||
"rekorLogIndex": 12345,
|
||||
"previousKitVersion": "bundle-2025-12-07",
|
||||
"newKitVersion": "bundle-2025-12-14"
|
||||
},
|
||||
"result": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 13.2 Audit Log Storage
|
||||
|
||||
```sql
|
||||
CREATE TABLE offline_kit_audit (
|
||||
event_id UUID PRIMARY KEY,
|
||||
event_type TEXT NOT NULL,
|
||||
timestamp TIMESTAMPTZ NOT NULL,
|
||||
actor TEXT NOT NULL,
|
||||
details JSONB NOT NULL,
|
||||
result TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_offline_kit_audit_ts ON offline_kit_audit(timestamp DESC);
|
||||
CREATE INDEX idx_offline_kit_audit_type ON offline_kit_audit(event_type);
|
||||
```
|
||||
|
||||
## 14. SECURITY CONSIDERATIONS
|
||||
|
||||
### 14.1 Key Management
|
||||
|
||||
- Trust roots: pinned via out-of-band distribution
|
||||
- Key rotation: maintain version history in trust store
|
||||
- Revocation: maintain revoked_keys list in trust anchors
|
||||
|
||||
### 14.2 Integrity Guarantees
|
||||
|
||||
- All bundles content-addressed
|
||||
- Manifest integrity via signature
|
||||
- DSSE envelope integrity via signature
|
||||
- Rekor inclusion proof integrity via Merkle tree
|
||||
|
||||
### 14.3 Air-Gap Boundaries
|
||||
|
||||
**Allowed**:
|
||||
- Local file system reads (read-only mount)
|
||||
- Local tool execution (verified binaries)
|
||||
- Local database writes (staged)
|
||||
|
||||
**Forbidden**:
|
||||
- Network egress
|
||||
- DNS lookups
|
||||
- NTP synchronization (use frozen clock)
|
||||
- External API calls
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,503 @@
|
||||
# PostgreSQL Patterns Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 01-Dec-2025 - PostgreSQL Patterns for Each StellaOps Module
|
||||
- 14-Dec-2025 - Evaluate PostgreSQL vs MongoDB for StellaOps
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. MODULE-SCHEMA MAPPING
|
||||
|
||||
| Module | Schema | Primary Tables |
|
||||
|--------|--------|----------------|
|
||||
| Authority | `authority` | `user`, `role`, `grant`, `oauth_client`, `oauth_token`, `audit_log` |
|
||||
| Routing | `routing` | `feature_flag`, `instance`, `rate_limit_config` |
|
||||
| VEX | `vex` | `vuln_fact`, `package`, `vex_decision`, `mv_triage_queue` |
|
||||
| Unknowns | `unknowns` | `unknown` (bitemporal) |
|
||||
| Artifact | `artifact` | `artifact`, `signature`, `tag` |
|
||||
| Core | `core` | `outbox` |
|
||||
|
||||
## 2. CORE POSTGRESQL CONVENTIONS
|
||||
|
||||
### 2.1 Required Columns (All Tables)
|
||||
|
||||
```sql
|
||||
id uuid primary key default gen_random_uuid()
|
||||
tenant_id uuid not null
|
||||
created_at timestamptz not null default now()
|
||||
updated_at timestamptz not null default now()
|
||||
```
|
||||
|
||||
### 2.2 Multi-Tenancy RLS Pattern
|
||||
|
||||
```sql
|
||||
alter table <table> enable row level security;
|
||||
|
||||
create policy p_<table>_tenant on <table>
|
||||
for all using (tenant_id = current_setting('app.tenant_id')::uuid);
|
||||
```
|
||||
|
||||
### 2.3 Session Configuration (Set Per Request)
|
||||
|
||||
```sql
|
||||
select set_config('app.user_id', '<uuid>', false);
|
||||
select set_config('app.tenant_id', '<uuid>', false);
|
||||
select set_config('app.roles', 'role1,role2', false);
|
||||
```
|
||||
|
||||
## 3. TABLE TAXONOMY AND PATTERNS
|
||||
|
||||
### 3.1 Source-of-Truth (SOR) Tables
|
||||
|
||||
```sql
|
||||
create table <module>.<entity> (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null,
|
||||
external_id uuid,
|
||||
content_hash bytea not null,
|
||||
doc jsonb not null,
|
||||
schema_version int not null,
|
||||
created_at timestamptz not null default now(),
|
||||
supersedes_id bigint null
|
||||
);
|
||||
|
||||
create unique index on <entity>(tenant_id, content_hash);
|
||||
```
|
||||
|
||||
### 3.2 JSONB Facts + Relational Decisions
|
||||
|
||||
**Facts (Immutable)**:
|
||||
```sql
|
||||
create table vex.vuln_fact (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null,
|
||||
source text not null,
|
||||
external_id text,
|
||||
payload jsonb not null,
|
||||
schema_version int not null,
|
||||
received_at timestamptz not null default now()
|
||||
);
|
||||
```
|
||||
|
||||
**Decisions (Relational)**:
|
||||
```sql
|
||||
create table vex.vex_decision (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null,
|
||||
package_id uuid not null,
|
||||
vuln_id text not null,
|
||||
status text check (status in ('not_affected','affected','fixed','under_investigation')),
|
||||
rationale text,
|
||||
proof_ref text,
|
||||
decided_by uuid,
|
||||
decided_at timestamptz not null default now(),
|
||||
unique (tenant_id, package_id, vuln_id)
|
||||
);
|
||||
```
|
||||
|
||||
### 3.3 Queue Pattern (SKIP LOCKED)
|
||||
|
||||
```sql
|
||||
create table job_queue (
|
||||
id bigserial primary key,
|
||||
tenant_id uuid,
|
||||
kind text not null,
|
||||
payload jsonb not null,
|
||||
run_after timestamptz default now(),
|
||||
attempts int default 0,
|
||||
locked_at timestamptz,
|
||||
locked_by text
|
||||
);
|
||||
|
||||
create index ix_job_ready
|
||||
on job_queue(kind, run_after, id)
|
||||
where locked_at is null;
|
||||
|
||||
-- Claim job
|
||||
with cte as (
|
||||
select id from job_queue
|
||||
where kind = $1
|
||||
and run_after <= now()
|
||||
and locked_at is null
|
||||
order by id
|
||||
for update skip locked
|
||||
limit 1
|
||||
)
|
||||
update job_queue j
|
||||
set locked_at = now(), locked_by = $2
|
||||
from cte
|
||||
where j.id = cte.id
|
||||
returning j.*;
|
||||
```
|
||||
|
||||
### 3.4 Temporal Pattern (Unknowns)
|
||||
|
||||
```sql
|
||||
create table unknowns.unknown (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null,
|
||||
subject_hash text not null,
|
||||
kind text not null,
|
||||
context jsonb not null,
|
||||
valid_from timestamptz not null default now(),
|
||||
valid_to timestamptz,
|
||||
sys_from timestamptz not null default now(),
|
||||
sys_to timestamptz,
|
||||
created_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
create unique index unknown_one_open_per_subject
|
||||
on unknowns.unknown (tenant_id, subject_hash, kind)
|
||||
where valid_to is null;
|
||||
|
||||
create view unknowns.current as
|
||||
select * from unknowns.unknown
|
||||
where valid_to is null;
|
||||
```
|
||||
|
||||
### 3.5 Audit Log Pattern
|
||||
|
||||
```sql
|
||||
create table authority.audit_log (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null,
|
||||
actor_id uuid,
|
||||
action text not null,
|
||||
entity_type text not null,
|
||||
entity_id uuid,
|
||||
at timestamptz not null default now(),
|
||||
diff jsonb not null
|
||||
);
|
||||
```
|
||||
|
||||
### 3.6 Outbox Pattern (Exactly-Once Side Effects)
|
||||
|
||||
```sql
|
||||
create table core.outbox (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid,
|
||||
aggregate_type text not null,
|
||||
aggregate_id uuid,
|
||||
topic text not null,
|
||||
payload jsonb not null,
|
||||
created_at timestamptz not null default now(),
|
||||
dispatched_at timestamptz,
|
||||
dispatch_attempts int not null default 0,
|
||||
error text
|
||||
);
|
||||
```
|
||||
|
||||
## 4. JSONB WITH GENERATED COLUMNS
|
||||
|
||||
```sql
|
||||
create table sbom_document (
|
||||
id bigserial primary key,
|
||||
tenant_id uuid not null,
|
||||
artifact_purl text not null,
|
||||
content_hash bytea not null,
|
||||
doc jsonb not null,
|
||||
created_at timestamptz not null default now(),
|
||||
|
||||
-- hot keys as generated columns
|
||||
bom_format text generated always as ((doc->>'bomFormat')) stored,
|
||||
spec_version text generated always as ((doc->>'specVersion')) stored
|
||||
);
|
||||
|
||||
create unique index ux_sbom_doc_hash on sbom_document(tenant_id, content_hash);
|
||||
create index ix_sbom_doc_tenant_artifact on sbom_document(tenant_id, artifact_purl, created_at desc);
|
||||
create index ix_sbom_doc_json_gin on sbom_document using gin (doc jsonb_path_ops);
|
||||
create index ix_sbom_doc_bomformat on sbom_document(tenant_id, bom_format);
|
||||
```
|
||||
|
||||
## 5. MATERIALIZED VIEWS FOR HOT READS
|
||||
|
||||
```sql
|
||||
create materialized view mv_artifact_risk as
|
||||
select tenant_id, artifact_purl, max(score) as risk_score
|
||||
from open_findings
|
||||
group by tenant_id, artifact_purl;
|
||||
|
||||
create unique index ux_mv_artifact_risk
|
||||
on mv_artifact_risk(tenant_id, artifact_purl);
|
||||
|
||||
-- Refresh
|
||||
refresh materialized view concurrently mv_artifact_risk;
|
||||
```
|
||||
|
||||
## 6. PARTITIONING (TIME-BASED EVENTS)
|
||||
|
||||
```sql
|
||||
create table scan_run_event (
|
||||
tenant_id uuid not null,
|
||||
scan_run_id bigint not null,
|
||||
occurred_at timestamptz not null,
|
||||
event_type text not null,
|
||||
payload jsonb not null
|
||||
) partition by range (occurred_at);
|
||||
|
||||
create index brin_scan_events_time
|
||||
on scan_run_event using brin (occurred_at);
|
||||
```
|
||||
|
||||
## 7. INDEX PATTERNS
|
||||
|
||||
| Use Case | Index Pattern |
|
||||
|----------|---------------|
|
||||
| Tenant-scoped queries | `INDEX(tenant_id, ...)` |
|
||||
| Latest version lookup | `INDEX(tenant_id, artifact_purl, created_at DESC)` |
|
||||
| Queue readiness | `INDEX(kind, run_after, id) WHERE locked_at IS NULL` |
|
||||
| JSONB containment | `INDEX USING GIN (doc jsonb_path_ops)` |
|
||||
| JSONB key lookup | `INDEX((doc->>'key'))` |
|
||||
| Time-series scan | `INDEX USING BRIN (occurred_at)` |
|
||||
|
||||
## 8. PERFORMANCE REQUIREMENTS
|
||||
|
||||
### 8.1 Query Performance Standards
|
||||
|
||||
**Required per PR**:
|
||||
- Provide SQL query + intended parameters
|
||||
- Provide `EXPLAIN (ANALYZE, BUFFERS)` from staging-sized dataset
|
||||
- Identify serving index(es)
|
||||
- Confirm row estimates not wildly wrong
|
||||
- Confirm tenant-scoped and uses tenant-leading index
|
||||
|
||||
### 8.2 Index Performance Standards
|
||||
|
||||
| Pattern | Requirement |
|
||||
|---------|-------------|
|
||||
| Tenant queries | `INDEX(tenant_id, ...)` leading column |
|
||||
| Sort ordering | Index must end with `ORDER BY` column + direction |
|
||||
| Queue claims | Partial index `WHERE locked_at IS NULL` |
|
||||
| Time-series | BRIN index on timestamp columns for partitioned tables |
|
||||
| JSONB containment | GIN `jsonb_path_ops` for `@>` queries |
|
||||
|
||||
### 8.3 General Performance Rules
|
||||
|
||||
- Every hot query must have an index story
|
||||
- Write path stays simple: prefer append-only versioning
|
||||
- Multi-tenant explicit: all core tables include `tenant_id`
|
||||
- Derived data modeled as projection tables or materialized views
|
||||
- Idempotency enforced in DB: unique keys for imports/jobs/results
|
||||
|
||||
## 9. FEATURE FLAG SCHEMA
|
||||
|
||||
```sql
|
||||
create table routing.feature_flag (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null,
|
||||
key text not null,
|
||||
rules jsonb not null,
|
||||
version int not null default 1,
|
||||
is_enabled boolean not null default true,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (tenant_id, key)
|
||||
);
|
||||
|
||||
create table routing.feature_flag_history (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
feature_flag_id uuid not null,
|
||||
tenant_id uuid not null,
|
||||
key text not null,
|
||||
rules jsonb not null,
|
||||
version int not null,
|
||||
changed_at timestamptz not null default now(),
|
||||
changed_by uuid
|
||||
);
|
||||
```
|
||||
|
||||
**Redis Cache Pattern**:
|
||||
```
|
||||
SETEX flag:{key}:{version} <ttl> <json>
|
||||
```
|
||||
|
||||
## 10. RATE LIMIT CONFIGURATION
|
||||
|
||||
```sql
|
||||
create table routing.rate_limit_config (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null,
|
||||
key text not null,
|
||||
limit_per_interval int not null,
|
||||
interval_seconds int not null,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (tenant_id, key)
|
||||
);
|
||||
```
|
||||
|
||||
**Redis Counter Pattern**:
|
||||
```
|
||||
INCR rl:{bucket}:{window}
|
||||
```
|
||||
|
||||
## 11. INSTANCE REGISTRY
|
||||
|
||||
```sql
|
||||
create table routing.instance (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
tenant_id uuid not null,
|
||||
instance_key text not null,
|
||||
domain text not null,
|
||||
last_heartbeat timestamptz not null default now(),
|
||||
status text not null check (status in ('active','draining','offline')),
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now(),
|
||||
unique (tenant_id, instance_key),
|
||||
unique (tenant_id, domain)
|
||||
);
|
||||
```
|
||||
|
||||
## 12. MIGRATION PATTERNS
|
||||
|
||||
### 12.1 Schema Versioning
|
||||
|
||||
```sql
|
||||
create table core.schema_version (
|
||||
module text primary key,
|
||||
version int not null,
|
||||
applied_at timestamptz not null default now(),
|
||||
migration_hash text not null
|
||||
);
|
||||
```
|
||||
|
||||
### 12.2 Migration Script Template
|
||||
|
||||
```sql
|
||||
-- Migration: <module>_v<version>_<description>
|
||||
-- Dependencies: <module>_v<previous_version>
|
||||
|
||||
begin;
|
||||
|
||||
-- Schema changes
|
||||
create table if not exists <module>.<table> (...);
|
||||
|
||||
-- Data migrations (if needed)
|
||||
|
||||
-- Update version
|
||||
insert into core.schema_version (module, version, migration_hash)
|
||||
values ('<module>', <version>, '<hash>')
|
||||
on conflict (module) do update
|
||||
set version = excluded.version,
|
||||
applied_at = now(),
|
||||
migration_hash = excluded.migration_hash;
|
||||
|
||||
commit;
|
||||
```
|
||||
|
||||
## 13. CONNECTION POOLING
|
||||
|
||||
### 13.1 Recommended Settings
|
||||
|
||||
```yaml
|
||||
database:
|
||||
host: postgres.local
|
||||
port: 5432
|
||||
database: stellaops
|
||||
username: stellaops_app
|
||||
password: <from secrets>
|
||||
pool:
|
||||
min_size: 5
|
||||
max_size: 20
|
||||
connection_timeout: 5000 # ms
|
||||
idle_timeout: 600000 # ms (10 min)
|
||||
max_lifetime: 1800000 # ms (30 min)
|
||||
```
|
||||
|
||||
### 13.2 .NET Configuration
|
||||
|
||||
```csharp
|
||||
services.AddNpgsqlDataSource(connectionString, builder =>
|
||||
{
|
||||
builder.MaxConnections = 20;
|
||||
builder.MinConnections = 5;
|
||||
builder.ConnectionIdleLifetime = TimeSpan.FromMinutes(10);
|
||||
builder.ConnectionLifetime = TimeSpan.FromMinutes(30);
|
||||
});
|
||||
```
|
||||
|
||||
## 14. MONITORING & OBSERVABILITY
|
||||
|
||||
### 14.1 Essential Metrics
|
||||
|
||||
```
|
||||
postgres_connections_active
|
||||
postgres_connections_idle
|
||||
postgres_transaction_duration_seconds
|
||||
postgres_query_duration_seconds
|
||||
postgres_cache_hit_ratio
|
||||
postgres_table_size_bytes
|
||||
postgres_index_size_bytes
|
||||
postgres_slow_queries_total
|
||||
```
|
||||
|
||||
### 14.2 Query Performance Monitoring
|
||||
|
||||
```sql
|
||||
-- Enable pg_stat_statements
|
||||
create extension if not exists pg_stat_statements;
|
||||
|
||||
-- Top 10 slowest queries
|
||||
select
|
||||
substring(query, 1, 100) as query_snippet,
|
||||
calls,
|
||||
total_exec_time / 1000 as total_time_sec,
|
||||
mean_exec_time as mean_time_ms,
|
||||
max_exec_time as max_time_ms
|
||||
from pg_stat_statements
|
||||
order by total_exec_time desc
|
||||
limit 10;
|
||||
```
|
||||
|
||||
## 15. BACKUP & RECOVERY
|
||||
|
||||
### 15.1 Backup Strategy
|
||||
|
||||
- **Point-in-time recovery (PITR)**: Enabled via WAL archiving
|
||||
- **Daily full backups**: Automated via `pg_basebackup`
|
||||
- **Retention**: 30 days for compliance
|
||||
- **Testing**: Monthly restore drills
|
||||
|
||||
### 15.2 Backup Commands
|
||||
|
||||
```bash
|
||||
# Full backup
|
||||
pg_basebackup -h postgres.local -D /backup/$(date +%Y%m%d) -Ft -z -P
|
||||
|
||||
# WAL archiving (postgresql.conf)
|
||||
# archive_mode = on
|
||||
# archive_command = 'cp %p /archive/%f'
|
||||
```
|
||||
|
||||
## 16. SECURITY BEST PRACTICES
|
||||
|
||||
### 16.1 Access Control
|
||||
|
||||
- Use RLS for multi-tenancy isolation
|
||||
- Grant minimal privileges per role
|
||||
- Separate read-only and read-write users
|
||||
- Use connection pooler with separate credentials
|
||||
|
||||
### 16.2 Encryption
|
||||
|
||||
- TLS for connections: `sslmode=require`
|
||||
- Transparent data encryption (TDE) for data at rest
|
||||
- Encrypted backups
|
||||
|
||||
### 16.3 Audit Logging
|
||||
|
||||
```sql
|
||||
-- Enable audit logging
|
||||
create extension if not exists pgaudit;
|
||||
|
||||
-- Configure audit (postgresql.conf)
|
||||
-- pgaudit.log = 'write, ddl'
|
||||
-- pgaudit.log_catalog = off
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,971 @@
|
||||
# Proof and Evidence Chain Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 29-Nov-2025 - SBOM to VEX Proof Pipeline Blueprint
|
||||
- 01-Dec-2025 - Turning SBOM Data Into Verifiable Proofs
|
||||
- 01-Dec-2025 - Proof-Linked VEX User Interface
|
||||
- 02-Dec-2025 - Converting SBOM Data into Proof Chains
|
||||
- 06-Dec-2025 - How to Build a Verifiable SBOM→VEX Chain
|
||||
- 08-Dec-2025 - Defining Stella Ops' Proof‑Linked Advantage
|
||||
- 08-Dec-2025 - Designing UX for Signed Evidence Trails
|
||||
- 03-Dec-2025 - Comparing Proof‑Linked VEX UX Across Tools
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. CORE IDENTIFIERS & DATA MODEL
|
||||
|
||||
### 1.1 Canonical IDs
|
||||
|
||||
```
|
||||
ArtifactID = sha256:<digest>
|
||||
SBOMEntryID = <sbomDigest>:<purl>[@<version>]
|
||||
EvidenceID = hash(canonical_evidence_json)
|
||||
ReasoningID = hash(canonical_reasoning_json)
|
||||
VEXVerdictID = hash(canonical_vex_json)
|
||||
ProofBundleID = merkle_root(SBOMEntryID, EvidenceID[], ReasoningID, VEXVerdictID)
|
||||
TrustAnchorID = per-dependency anchor (public key + policy)
|
||||
```
|
||||
|
||||
### 1.2 Component Identifiers (bom-ref)
|
||||
|
||||
```
|
||||
Format: pkg:<ecosystem>/<name>@<version>?sha256=<digest>
|
||||
|
||||
Examples:
|
||||
pkg:maven/org.apache.commons/commons-lang3@3.14.0?sha256=<digest>
|
||||
pkg:apk/alpine/openssl@3.2.1-r0?sha256=2c0f...54e
|
||||
pkg:oci/<repo>@sha256:<manifestDigest>
|
||||
pkg:npm/lodash@4.17.21
|
||||
|
||||
Rules:
|
||||
- Must be stable across regenerations for identical content
|
||||
- Independent of local paths, build numbers
|
||||
- Derived from canonical bytes
|
||||
- Used as CycloneDX bom-ref
|
||||
```
|
||||
|
||||
### 1.3 Subject Schema
|
||||
|
||||
```csharp
|
||||
public sealed record ProofSubject(
|
||||
string Name, // PURL or canonical URI
|
||||
IReadOnlyDictionary<string,string> Digest // {"sha256": "...", "sha512": "..."}
|
||||
);
|
||||
```
|
||||
|
||||
### 1.4 SBOM Identity
|
||||
|
||||
```
|
||||
sbomId = sha256(canonical_sbom_bytes)
|
||||
```
|
||||
|
||||
## 2. DSSE ENVELOPE STRUCTURES
|
||||
|
||||
### 2.1 Evidence Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": {
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
|
||||
"predicateType": "evidence.stella/v1",
|
||||
"predicate": {
|
||||
"source": "scanner/feed name",
|
||||
"sourceVersion": "tool version",
|
||||
"collectionTime": "2025-12-14T00:00:00Z",
|
||||
"sbomEntryId": "<SBOMEntryID>",
|
||||
"vulnerabilityId": "CVE-XXXX-YYYY",
|
||||
"rawFinding": "<pointer or data>",
|
||||
"evidenceId": "<EvidenceID>"
|
||||
}
|
||||
},
|
||||
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
|
||||
}
|
||||
```
|
||||
|
||||
Signer: Scanner/Ingestor key
|
||||
|
||||
### 2.2 Reasoning Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": {
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
|
||||
"predicateType": "reasoning.stella/v1",
|
||||
"predicate": {
|
||||
"sbomEntryId": "<SBOMEntryID>",
|
||||
"evidenceIds": ["<EvidenceID>", ...],
|
||||
"policyVersion": "v2.3.1",
|
||||
"inputs": {
|
||||
"currentEvaluationTime": "2025-12-14T00:00:00Z",
|
||||
"severityThresholds": {...},
|
||||
"latticeRules": {...}
|
||||
},
|
||||
"intermediateFindings": {},
|
||||
"reasoningId": "<ReasoningID>"
|
||||
}
|
||||
},
|
||||
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
|
||||
}
|
||||
```
|
||||
|
||||
Signer: Policy/Authority key
|
||||
|
||||
### 2.3 VEX Verdict Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": {
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
|
||||
"predicateType": "cdx-vex.stella/v1",
|
||||
"predicate": {
|
||||
"sbomEntryId": "<SBOMEntryID>",
|
||||
"vulnerabilityId": "CVE-XXXX-YYYY",
|
||||
"status": "not_affected|affected|fixed|under_investigation",
|
||||
"justification": "vulnerable_code_not_in_execute_path",
|
||||
"policyVersion": "v2.3.1",
|
||||
"reasoningId": "<ReasoningID>",
|
||||
"vexVerdictId": "<VEXVerdictID>"
|
||||
}
|
||||
},
|
||||
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
|
||||
}
|
||||
```
|
||||
|
||||
Signer: VEXer key or vendor key
|
||||
|
||||
### 2.4 Proof Spine Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"payloadType": "application/vnd.in-toto+json",
|
||||
"payload": {
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [{"name": "<SBOMEntryID>", "digest": {"sha256": "..."}}],
|
||||
"predicateType": "proofspine.stella/v1",
|
||||
"predicate": {
|
||||
"sbomEntryId": "<SBOMEntryID>",
|
||||
"evidenceIds": ["<ID1>", "<ID2>"],
|
||||
"reasoningId": "<ID>",
|
||||
"vexVerdictId": "<ID>",
|
||||
"policyVersion": "v2.3.1",
|
||||
"proofBundleId": "<ProofBundleID>"
|
||||
}
|
||||
},
|
||||
"signatures": [{"keyid": "<KID>", "sig": "BASE64(SIG)"}]
|
||||
}
|
||||
```
|
||||
|
||||
Signer: Authority key
|
||||
|
||||
### 2.5 SBOM Linkage Statement
|
||||
|
||||
```json
|
||||
{
|
||||
"_type": "https://in-toto.io/Statement/v1",
|
||||
"subject": [
|
||||
{"name": "pkg:npm/lodash@4.17.21", "digest": {"sha256": "...", "sha512": "..."}}
|
||||
],
|
||||
"predicateType": "https://stella-ops.org/predicates/sbom-linkage/v1",
|
||||
"predicate": {
|
||||
"sbom": {
|
||||
"id": "<sbomId>",
|
||||
"format": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"mediaType": "application/vnd.cyclonedx+json",
|
||||
"sha256": "<sha256>",
|
||||
"location": "oci://... or file://..."
|
||||
},
|
||||
"generator": {
|
||||
"name": "StellaOps.Sbomer",
|
||||
"version": "x.y.z"
|
||||
},
|
||||
"generatedAt": "2025-12-14T00:00:00Z",
|
||||
"incompleteSubjects": [],
|
||||
"tags": {
|
||||
"tenantId": "...",
|
||||
"projectId": "...",
|
||||
"pipelineRunId": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. CYCLONEDX VEX STRUCTURE
|
||||
|
||||
```json
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.6",
|
||||
"version": 1,
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"id": "CVE-2024-12345",
|
||||
"source": {"name": "NVD"},
|
||||
"analysis": {
|
||||
"state": "not_affected",
|
||||
"justification": "vulnerable_code_not_present",
|
||||
"response": ["will_not_fix"],
|
||||
"detail": "Linked OpenSSL feature set excludes the vulnerable cipher."
|
||||
},
|
||||
"affects": [
|
||||
{"ref": "pkg:apk/alpine/openssl@3.2.1-r0?sha256=2c0f...54e"}
|
||||
],
|
||||
"properties": [
|
||||
{"name": "evidence.sbomDigest", "value": "sha256:91f2...9a"},
|
||||
{"name": "evidence.rekorLogID", "value": "425c1d1e..."},
|
||||
{"name": "reachability.report", "value": "sha256:reacha..."},
|
||||
{"name": "policy.decision", "value": "TrustGate#R-17.2"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### VEX Status Values
|
||||
|
||||
```
|
||||
not_affected
|
||||
affected
|
||||
fixed
|
||||
under_investigation
|
||||
```
|
||||
|
||||
### VEX Justification Values
|
||||
|
||||
```
|
||||
vulnerable_code_not_present
|
||||
vulnerable_code_not_in_execute_path
|
||||
vulnerable_code_not_configured
|
||||
vulnerable_code_cannot_be_controlled_by_adversary
|
||||
component_not_present
|
||||
inline_mitigations_exist
|
||||
```
|
||||
|
||||
## 4. STORAGE SCHEMA
|
||||
|
||||
### 4.1 PostgreSQL Tables
|
||||
|
||||
```sql
|
||||
CREATE TABLE sbom_entries (
|
||||
entry_id UUID PRIMARY KEY,
|
||||
bom_digest VARCHAR(64) NOT NULL,
|
||||
purl TEXT NOT NULL,
|
||||
version TEXT,
|
||||
artifact_digest VARCHAR(64),
|
||||
trust_anchor_id UUID,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE dsse_envelopes (
|
||||
env_id UUID PRIMARY KEY,
|
||||
entry_id UUID REFERENCES sbom_entries(entry_id),
|
||||
predicate_type TEXT NOT NULL,
|
||||
signer_keyid TEXT NOT NULL,
|
||||
body_hash VARCHAR(64) NOT NULL,
|
||||
envelope_blob_ref TEXT NOT NULL,
|
||||
signed_at TIMESTAMPTZ NOT NULL,
|
||||
INDEX idx_entry_predicate (entry_id, predicate_type)
|
||||
);
|
||||
|
||||
CREATE TABLE spines (
|
||||
entry_id UUID PRIMARY KEY REFERENCES sbom_entries(entry_id),
|
||||
bundle_id VARCHAR(64) NOT NULL,
|
||||
evidence_ids TEXT[] NOT NULL,
|
||||
reasoning_id VARCHAR(64) NOT NULL,
|
||||
vex_id VARCHAR(64) NOT NULL,
|
||||
anchor_id UUID,
|
||||
policy_version TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE trust_anchors (
|
||||
anchor_id UUID PRIMARY KEY,
|
||||
purl_pattern TEXT NOT NULL,
|
||||
allowed_keyids TEXT[] NOT NULL,
|
||||
policy_ref TEXT,
|
||||
revoked_keys TEXT[],
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE rekor_entries (
|
||||
dsse_sha256 VARCHAR(64) PRIMARY KEY,
|
||||
log_index BIGINT NOT NULL,
|
||||
log_id TEXT NOT NULL,
|
||||
integrated_time BIGINT NOT NULL,
|
||||
inclusion_proof JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 4.2 Proof Graph Nodes
|
||||
|
||||
```
|
||||
Node Types:
|
||||
- Artifact (container image, binary, Helm chart)
|
||||
- SbomDocument (by sbomId)
|
||||
- InTotoStatement (by statement hash)
|
||||
- DsseEnvelope (by envelope hash)
|
||||
- RekorEntry (by log index/UUID)
|
||||
- VexStatement (by vex hash)
|
||||
- Subject (component from SBOM)
|
||||
|
||||
Edge Types:
|
||||
- DESCRIBED_BY: Artifact → SbomDocument
|
||||
- ATTESTED_BY: SbomDocument → InTotoStatement
|
||||
- WRAPPED_BY: InTotoStatement → DsseEnvelope
|
||||
- LOGGED_IN: DsseEnvelope → RekorEntry
|
||||
- HAS_VEX: Artifact/Subject → VexStatement
|
||||
- CONTAINS_SUBJECT: InTotoStatement → Subject
|
||||
- PRODUCES: Build → SBOM
|
||||
- AFFECTS: VEX → Component
|
||||
- SIGNED_BY: Envelope → Key
|
||||
- RECORDED_AT: Envelope → Rekor
|
||||
```
|
||||
|
||||
## 5. API CONTRACTS
|
||||
|
||||
### 5.1 Proof Spine API
|
||||
|
||||
```
|
||||
POST /proofs/:entry/spine
|
||||
Body: {
|
||||
"evidenceIds": ["<ID1>", ...],
|
||||
"reasoningId": "<ID>",
|
||||
"vexVerdictId": "<ID>",
|
||||
"policyVersion": "v2.3.1"
|
||||
}
|
||||
Response: 201 Created, {"proofBundleId": "..."}
|
||||
|
||||
GET /proofs/:entry/receipt
|
||||
Response: {
|
||||
"proofBundleId": "...",
|
||||
"verifiedAt": "2025-12-14T00:00:00Z",
|
||||
"verifierVersion": "1.0.0",
|
||||
"anchorId": "...",
|
||||
"result": "pass|fail",
|
||||
"details": {...}
|
||||
}
|
||||
|
||||
GET /proofs/:entry/vex
|
||||
Response: <VEX JSON body>
|
||||
|
||||
GET /anchors/:anchor
|
||||
Response: {
|
||||
"anchorId": "...",
|
||||
"purlPattern": "pkg:npm/*",
|
||||
"allowedKeyids": ["key1", "key2"],
|
||||
"policyRef": "...",
|
||||
"revokedKeys": []
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Verification API
|
||||
|
||||
```
|
||||
POST /verify
|
||||
Body: {
|
||||
"artifactDigest": "sha256:...",
|
||||
"sbom": <SBOM JSON or reference>,
|
||||
"vex": <VEX JSON or reference>,
|
||||
"signatures": [...],
|
||||
"logs": [...]
|
||||
}
|
||||
Response: {
|
||||
"artifact": "pkg:oci/...",
|
||||
"sbomVerified": true,
|
||||
"vexVerified": true,
|
||||
"components": [
|
||||
{
|
||||
"bomRef": "pkg:...",
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"id": "CVE-...",
|
||||
"state": "not_affected",
|
||||
"justification": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 6. CANONICALIZATION RULES
|
||||
|
||||
### 6.1 JSON Canonicalization
|
||||
|
||||
```
|
||||
1. UTF-8 encoding
|
||||
2. Sorted keys (lexicographic)
|
||||
3. No insignificant whitespace
|
||||
4. No volatile fields beyond semantic need
|
||||
5. Version schema: evidence.stella/v1, reasoning.stella/v1
|
||||
6. Deterministic array ordering where semantically unordered
|
||||
```
|
||||
|
||||
### 6.2 SBOM Canonicalization
|
||||
|
||||
```csharp
|
||||
public interface ISbomCanonicalizer
|
||||
{
|
||||
byte[] Canonicalize(ReadOnlySpan<byte> rawSbom, string mediaType);
|
||||
}
|
||||
|
||||
public interface IBlobHasher
|
||||
{
|
||||
string ComputeSha256Hex(ReadOnlySpan<byte> data);
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Remove insignificant whitespace
|
||||
- Sort object keys lexicographically
|
||||
- Sort arrays deterministically (by bom-ref or purl)
|
||||
- Strip volatile fields: generation timestamps, tool build IDs, non-deterministic UUIDs
|
||||
- Convert to internal JSON, then canonicalize
|
||||
|
||||
### 6.3 Subject Extraction
|
||||
|
||||
```csharp
|
||||
IEnumerable<Subject> ToSubjects(CycloneDxSbom sbom)
|
||||
{
|
||||
foreach (var c in sbom.Metadata.Components)
|
||||
{
|
||||
if (c.Hashes == null || c.Hashes.Count == 0) continue;
|
||||
var name = $"pkg:{c.Type}/{c.Name}@{c.Version}";
|
||||
var dig = c.Hashes
|
||||
.OrderBy(h => h.Algorithm)
|
||||
.ToDictionary(
|
||||
h => h.Algorithm.ToLowerInvariant(),
|
||||
h => h.Value.ToLowerInvariant()
|
||||
);
|
||||
yield return new Subject(name, dig);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Digest requirements:
|
||||
- Must have at least sha256 or sha512
|
||||
- Normalize algorithm keys to lowercase
|
||||
- Sort subjects by: 1) Name ascending, 2) Algorithm:value pairs
|
||||
|
||||
## 7. REKOR INTEGRATION
|
||||
|
||||
### 7.1 Rekor Entry Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dsseSha256": "sha256:...",
|
||||
"rekor": {
|
||||
"uuid": "...",
|
||||
"logIndex": 12345,
|
||||
"logId": "...",
|
||||
"integratedTime": 1733736000,
|
||||
"inclusionProof": {
|
||||
"rootHash": "...",
|
||||
"hashes": ["...", "..."],
|
||||
"checkpoint": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Offline Update Bundle Structure
|
||||
|
||||
```
|
||||
/bundle-2025-12-14/
|
||||
manifest.json # version, created_at, entries[], sha256s
|
||||
payload.tar.zst # actual DB/indices/feeds
|
||||
payload.tar.zst.sha256
|
||||
statement.dsse.json # DSSE-wrapped statement over payload hash
|
||||
rekor-receipt.json # Rekor v2 inclusion/verification material
|
||||
```
|
||||
|
||||
### 7.3 Offline Update DSSE Predicate
|
||||
|
||||
```json
|
||||
{
|
||||
"predicateType": "https://stella-ops.org/attestations/offline-update/1",
|
||||
"predicate": {
|
||||
"offline_manifest_sha256": "sha256:...",
|
||||
"feeds": [
|
||||
{
|
||||
"name": "nvd",
|
||||
"snapshot_date": "2025-12-14",
|
||||
"archive_digest": "sha256:..."
|
||||
}
|
||||
],
|
||||
"builder": "ci-workflow-id / git-commit / job-id",
|
||||
"created_at": "2025-12-14T00:00:00Z",
|
||||
"oukit_channel": "stable|edge|fips-profile"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.4 Verification Sequence (Offline Kit)
|
||||
|
||||
```
|
||||
1. Validate Cosign signature of tarball
|
||||
2. Validate offline-manifest.json with JWS signature
|
||||
3. Verify file digests for all entries (including /attestations/*)
|
||||
4. Verify DSSE:
|
||||
- Call StellaOps.Attestor.Verify with:
|
||||
- offline-update.dsse.json
|
||||
- offline-update.rekor.json
|
||||
- local Rekor log snapshot/segment
|
||||
- Ensure payload digest matches kit tarball + manifest digests
|
||||
5. Only after all checks pass:
|
||||
- Swap Scanner's feed pointer to new snapshot
|
||||
- Emit audit event (kit filename, tarball digest, DSSE digest, Rekor UUID + log index)
|
||||
```
|
||||
|
||||
## 8. CRYPTOGRAPHIC SPECIFICATIONS
|
||||
|
||||
### 8.1 Signing Keys & Profiles
|
||||
|
||||
```
|
||||
Default Profile:
|
||||
Hash: SHA-256
|
||||
Signature: Ed25519 or ECDSA P-256
|
||||
|
||||
Future Profiles:
|
||||
GOST R 34.10-2012
|
||||
eIDAS-compliant algorithms
|
||||
FIPS 140-2/140-3
|
||||
SM2/SM3 (Chinese standards)
|
||||
PQC: Dilithium/Falcon for long-term archives
|
||||
|
||||
Key Storage:
|
||||
KMS/HSM (production)
|
||||
PKCS#11 for air-gap
|
||||
Per-environment keysets: dev, staging, prod
|
||||
Per-role keysets: Authority, VEXer, Evidence Ingestor
|
||||
```
|
||||
|
||||
### 8.2 Key Rotation
|
||||
|
||||
```
|
||||
Rotation Process:
|
||||
1. Add new allowed_keyids to TrustAnchor
|
||||
2. Never mutate old DSSE envelopes
|
||||
3. Publish key material via attestation feed or Rekor-mirror
|
||||
4. Record all changes in audit log
|
||||
5. Maintain key version history
|
||||
```
|
||||
|
||||
### 8.3 Trust Anchor Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"trustAnchorId": "UUID",
|
||||
"purlPattern": "pkg:npm/*",
|
||||
"allowedKeyids": ["keyid1", "keyid2"],
|
||||
"allowedPredicateTypes": [
|
||||
"evidence.stella/v1",
|
||||
"reasoning.stella/v1",
|
||||
"cdx-vex.stella/v1"
|
||||
],
|
||||
"policyVersion": "v2.3.1",
|
||||
"revokedKeys": []
|
||||
}
|
||||
```
|
||||
|
||||
## 9. VERIFICATION PIPELINE
|
||||
|
||||
### 9.1 Verification Algorithm
|
||||
|
||||
```
|
||||
Input: SBOMEntryID or ProofBundleID
|
||||
|
||||
Steps:
|
||||
1. Resolve SBOMEntryID → TrustAnchorID
|
||||
2. Fetch spine and trust anchor
|
||||
3. Verify spine DSSE signature against TrustAnchor.allowedKeyids
|
||||
4. Verify VEX DSSE signature
|
||||
5. Verify reasoning DSSE signature
|
||||
6. Verify evidence DSSE signatures
|
||||
7. Recompute EvidenceIDs from stored canonical evidence
|
||||
8. Recompute ReasoningID from reasoning
|
||||
9. Recompute VEXVerdictID from VEX body
|
||||
10. Recompute ProofBundleID (merkle root) from above
|
||||
11. Compare all computed IDs to stored IDs
|
||||
12. If using Rekor:
|
||||
- Verify log inclusion proof
|
||||
- Verify payload hashes match local files
|
||||
13. Emit Receipt
|
||||
|
||||
Output: Receipt {
|
||||
proofBundleId,
|
||||
verifiedAt,
|
||||
verifierVersion,
|
||||
anchorId,
|
||||
result: "pass|fail",
|
||||
details: [...]
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Receipt Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"proofBundleId": "sha256:...",
|
||||
"verifiedAt": "2025-12-14T00:00:00Z",
|
||||
"verifierVersion": "1.0.0",
|
||||
"anchorId": "UUID",
|
||||
"result": "pass",
|
||||
"checks": [
|
||||
{
|
||||
"check": "spine_signature",
|
||||
"status": "pass",
|
||||
"keyid": "..."
|
||||
},
|
||||
{
|
||||
"check": "evidence_id_recompute",
|
||||
"status": "pass",
|
||||
"expected": "sha256:...",
|
||||
"actual": "sha256:..."
|
||||
},
|
||||
{
|
||||
"check": "rekor_inclusion",
|
||||
"status": "pass",
|
||||
"logIndex": 12345
|
||||
}
|
||||
],
|
||||
"toolDigests": {
|
||||
"verifier": "sha256:...",
|
||||
"canonicalizer": "sha256:..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 10. DETERMINISM CONSTRAINTS
|
||||
|
||||
### 10.1 Non-Negotiable Invariants
|
||||
|
||||
```
|
||||
1. Immutability of Signed Facts
|
||||
- DSSE envelopes are append-only
|
||||
- Never edit or delete content inside signed envelope
|
||||
- Corrections via superseding (new statement pointing to old)
|
||||
|
||||
2. Determinism
|
||||
- Same {SBOMEntryID, Evidence set, policyVersion} ⇒ same {ReasoningID, VEXVerdictID, ProofBundleID}
|
||||
- No non-deterministic inputs in ID computation
|
||||
- No current time, random IDs in verdict logic
|
||||
|
||||
3. Traceability
|
||||
- Every VEX verdict → SBOM entry, evidence blobs, policy snapshot, trust anchor
|
||||
|
||||
4. Least Trust/Least Privilege
|
||||
- Trust always explicit via TrustAnchors + signature verification
|
||||
- Never "because it's in our DB"
|
||||
|
||||
5. Backwards Compatibility
|
||||
- New code verifies old proofs
|
||||
- New policies generate new spines, old spines intact
|
||||
```
|
||||
|
||||
### 10.2 Temporal Handling
|
||||
|
||||
```
|
||||
UTC ISO-8601 only
|
||||
No local time
|
||||
Timestamps only when semantically required
|
||||
Derivation from content preferred over wall-clock time
|
||||
Record evaluation time as explicit input if policy needs it
|
||||
```
|
||||
|
||||
### 10.3 Ordering Requirements
|
||||
|
||||
```
|
||||
Subjects: sorted by Name ascending, then digest keys
|
||||
Evidence IDs: sorted lexicographically
|
||||
Keys in JSON: sorted lexicographically
|
||||
Array elements: stable sort by semantic key (bom-ref, purl)
|
||||
```
|
||||
|
||||
## 11. IMPLEMENTATION INTERFACES
|
||||
|
||||
### 11.1 .NET 10 Core Interfaces
|
||||
|
||||
```csharp
|
||||
// DSSE Signing
|
||||
public interface IDsseSigner
|
||||
{
|
||||
Task<DsseEnvelope> SignAsync(
|
||||
ReadOnlyMemory<byte> payload,
|
||||
string payloadType,
|
||||
string keyProfile,
|
||||
CancellationToken ct = default
|
||||
);
|
||||
}
|
||||
|
||||
// Verification
|
||||
public record DsseEnvelope(
|
||||
string PayloadType,
|
||||
byte[] Payload,
|
||||
Signature[] Signatures
|
||||
);
|
||||
|
||||
public record Signature(
|
||||
string Keyid,
|
||||
string Sig,
|
||||
string? Cert
|
||||
);
|
||||
|
||||
// Subject Extraction
|
||||
public sealed record ProofSubject(
|
||||
string Name,
|
||||
IReadOnlyDictionary<string,string> Digest
|
||||
);
|
||||
|
||||
// Predicate Models
|
||||
public record SbomLinkagePredicate(
|
||||
SbomDescriptor Sbom,
|
||||
GeneratorDescriptor Generator,
|
||||
DateTimeOffset GeneratedAt,
|
||||
IReadOnlyList<IncompleteSubject>? IncompleteSubjects,
|
||||
IReadOnlyDictionary<string,string>? Tags
|
||||
);
|
||||
|
||||
public record EvidencePredicate(
|
||||
string Source,
|
||||
string SourceVersion,
|
||||
DateTimeOffset CollectionTime,
|
||||
string SbomEntryId,
|
||||
string? VulnerabilityId,
|
||||
object RawFinding,
|
||||
string EvidenceId
|
||||
);
|
||||
|
||||
public record ReasoningPredicate(
|
||||
string SbomEntryId,
|
||||
string[] EvidenceIds,
|
||||
string PolicyVersion,
|
||||
Dictionary<string,object> Inputs,
|
||||
Dictionary<string,object>? IntermediateFindings,
|
||||
string ReasoningId
|
||||
);
|
||||
|
||||
public record VexPredicate(
|
||||
string SbomEntryId,
|
||||
string VulnerabilityId,
|
||||
string Status,
|
||||
string Justification,
|
||||
string PolicyVersion,
|
||||
string ReasoningId,
|
||||
string VexVerdictId
|
||||
);
|
||||
|
||||
public record ProofSpinePredicate(
|
||||
string SbomEntryId,
|
||||
string[] EvidenceIds,
|
||||
string ReasoningId,
|
||||
string VexVerdictId,
|
||||
string PolicyVersion,
|
||||
string ProofBundleId
|
||||
);
|
||||
```
|
||||
|
||||
### 11.2 Rekor Client Interface
|
||||
|
||||
```csharp
|
||||
public interface IRekorClient
|
||||
{
|
||||
Task<RekorEntry> SubmitDsseAsync(
|
||||
DsseEnvelope envelope,
|
||||
CancellationToken ct = default
|
||||
);
|
||||
|
||||
Task<bool> VerifyInclusionAsync(
|
||||
RekorEntry entry,
|
||||
byte[] payloadDigest,
|
||||
byte[] rekorPublicKey,
|
||||
CancellationToken ct = default
|
||||
);
|
||||
}
|
||||
|
||||
public record RekorEntry(
|
||||
string Uuid,
|
||||
long LogIndex,
|
||||
string LogId,
|
||||
long IntegratedTime,
|
||||
InclusionProof Proof
|
||||
);
|
||||
|
||||
public record InclusionProof(
|
||||
string RootHash,
|
||||
string[] Hashes,
|
||||
string Checkpoint
|
||||
);
|
||||
```
|
||||
|
||||
## 12. CONFIGURATION SCHEMA
|
||||
|
||||
### 12.1 Scanner Offline Kit Config
|
||||
|
||||
```yaml
|
||||
scanner:
|
||||
offlineKit:
|
||||
requireDsse: true
|
||||
rekorOfflineMode: true
|
||||
attestationVerifier: https://attestor.internal
|
||||
trustAnchors:
|
||||
- anchorId: "UUID"
|
||||
purlPattern: "pkg:npm/*"
|
||||
allowedKeyids: ["key1", "key2"]
|
||||
```
|
||||
|
||||
### 12.2 Signer Config
|
||||
|
||||
```yaml
|
||||
signer:
|
||||
profiles:
|
||||
default:
|
||||
algorithm: "SHA256-ED25519"
|
||||
keyStore: "kms://..."
|
||||
fips:
|
||||
algorithm: "SHA256-ECDSA-P256"
|
||||
keyStore: "hsm://..."
|
||||
pqc:
|
||||
algorithm: "SHA256-DILITHIUM3"
|
||||
keyStore: "kms://..."
|
||||
```
|
||||
|
||||
### 12.3 Authority Config
|
||||
|
||||
```yaml
|
||||
authority:
|
||||
trustRoots:
|
||||
- id: "root-ca-1"
|
||||
publicKey: "..."
|
||||
validFrom: "2025-01-01T00:00:00Z"
|
||||
validUntil: "2030-01-01T00:00:00Z"
|
||||
keystores:
|
||||
- type: "kms"
|
||||
url: "aws-kms://..."
|
||||
region: "us-east-1"
|
||||
```
|
||||
|
||||
## 13. ERROR HANDLING
|
||||
|
||||
### 13.1 Ingestion Failures
|
||||
|
||||
```
|
||||
If SBOM invalid:
|
||||
- Reject SBOM
|
||||
- Record DSSE failure attestation:
|
||||
{
|
||||
"error": "schema_validation_failed",
|
||||
"file": "sbom.json",
|
||||
"system_version": "1.0.0"
|
||||
}
|
||||
- Maintain proof trail for "we tried and it failed"
|
||||
```
|
||||
|
||||
### 13.2 Missing Digests
|
||||
|
||||
```
|
||||
If component lacks sha256/sha512:
|
||||
- Do NOT use as primary subject in proof chain
|
||||
- Log in "incompleteSubjects" block in predicate
|
||||
- Expose in UI as "unverifiable component"
|
||||
```
|
||||
|
||||
### 13.3 Rekor Failures
|
||||
|
||||
```
|
||||
If Rekor unavailable:
|
||||
- Store DSSE envelope locally
|
||||
- Queue for retry
|
||||
- Mark proof chain as "rekorStatus: pending"
|
||||
- Internal-only until Rekor sync succeeds
|
||||
- Flag in verification results
|
||||
```
|
||||
|
||||
## 14. METRICS & OBSERVABILITY
|
||||
|
||||
### 14.1 Pipeline Metrics
|
||||
|
||||
```
|
||||
sboms_ingested_total
|
||||
sbom_ingest_errors_total{reason}
|
||||
evidence_statements_created_total
|
||||
reasoning_statements_created_total
|
||||
vex_statements_created_total
|
||||
proof_spines_created_total
|
||||
proof_verifications_total{result}
|
||||
*_duration_seconds (latency histograms per stage)
|
||||
|
||||
offlinekit_import_total{status="success|failed_dsse|failed_rekor|failed_cosign"}
|
||||
offlinekit_attestation_verify_latency_seconds
|
||||
attestor_rekor_success_total
|
||||
attestor_rekor_retry_total
|
||||
rekor_inclusion_latency
|
||||
```
|
||||
|
||||
### 14.2 Structured Logging Fields
|
||||
|
||||
```
|
||||
sbomEntryId
|
||||
proofBundleId
|
||||
anchorId
|
||||
policyVersion
|
||||
requestId / traceId
|
||||
rekorUuid
|
||||
attestationDigest
|
||||
offlineKitHash
|
||||
failureReason
|
||||
```
|
||||
|
||||
## 15. CI/CD INTEGRATION
|
||||
|
||||
### 15.1 Pipeline Hooks
|
||||
|
||||
```
|
||||
On SBOM ingest:
|
||||
- Create/refresh SBOMEntry rows
|
||||
- Attach TrustAnchor
|
||||
|
||||
On scan completion:
|
||||
- Produce Evidence Statements (DSSE) immediately
|
||||
|
||||
On policy evaluation:
|
||||
- Produce Reasoning + VEX
|
||||
- Assemble Spine
|
||||
|
||||
Release gates:
|
||||
- Require: GET /proofs/:entry/receipt == PASS
|
||||
```
|
||||
|
||||
### 15.2 CLI Exit Codes
|
||||
|
||||
```
|
||||
0 = no policy violation
|
||||
1 = policy violation
|
||||
2 = scanner/system error (distinguish from "found vulns")
|
||||
```
|
||||
|
||||
### 15.3 CLI Output Modes
|
||||
|
||||
```
|
||||
Default: Human-readable summary (3-5 lines)
|
||||
--output json: Machine-readable with:
|
||||
- Web UI run page link
|
||||
- Proof bundle ID
|
||||
- Rekor/ledger reference
|
||||
|
||||
-v / -vv: Verbose details
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,992 @@
|
||||
# Reachability Analysis Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 05-Dec-2025 - Building a Deterministic, Reachability‑First Architecture
|
||||
- 13-Dec-2025 - Designing the Call‑Stack Reachability Engine
|
||||
- 03-Dec-2025 - Reachability Benchmarks and Moat Metrics
|
||||
- 09-Dec-2025 - Caching Reachability the Smart Way
|
||||
- 04-Dec-2025 - Ranking Unknowns in Reachability Graphs
|
||||
- 02-Dec-2025 - Designing Deterministic Reachability UX
|
||||
- 05-Dec-2025 - Design Notes on Smart‑Diff and Call‑Stack Analysis
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. ARCHITECTURE PATTERNS
|
||||
|
||||
### 1.1 Core System Architecture
|
||||
|
||||
**Module Responsibilities**
|
||||
- `StellaOps.Scanner.WebService`: Authoritative for reachability pipeline and lattice computation
|
||||
- Language workers: Stateless compute producing `CallGraph.v1.json`
|
||||
- Runtime collectors: Agent/sidecar emitting evidence events only
|
||||
- Concelier/Excititor: Provide pruned sources; never compute reachability
|
||||
- Authority: Manages replay manifests, crypto profiles
|
||||
- Scheduler: Executes rescan/escalation policies
|
||||
|
||||
**Architectural Rules**
|
||||
- Scanner = origin of truth for reachability
|
||||
- Concelier/Vexer = prune-preservers only
|
||||
- Authority = replay manifest owner
|
||||
- Scheduler = executor of policies
|
||||
- Postgres = System of Record (SoR)
|
||||
- Valkey = ephemeral only (dedupe, hot cache, rate limits)
|
||||
|
||||
### 1.2 Evidence Graph Structure
|
||||
|
||||
**Node Types**
|
||||
- `Artifact`, `Component`, `Vulnerability`, `Attestation`, `Build`, `Deployment`, `RuntimeSignal`
|
||||
|
||||
**Edge Types**
|
||||
- `DESCRIBES`, `AFFECTS`, `NOT_AFFECTED_BY`, `FIXED_IN`, `DERIVED_FROM`, `DEPLOYS`, `OBSERVED_AT_RUNTIME`
|
||||
|
||||
**Edge Signing**
|
||||
- Sign edges, not just nodes (edge = claim)
|
||||
|
||||
### 1.3 Determinism Requirements
|
||||
|
||||
**Input Manifest Structure**
|
||||
```jsonc
|
||||
{
|
||||
"scannerVersion": "1.3.0",
|
||||
"rulesetId": "stella-default-2025.11",
|
||||
"feeds": {
|
||||
"nvdDigest": "sha256:...",
|
||||
"osvDigest": "sha256:..."
|
||||
},
|
||||
"sbomDigest": "sha256:...",
|
||||
"policyDigest": "sha256:..."
|
||||
}
|
||||
```
|
||||
|
||||
**Canonicalization Rules**
|
||||
- Sort arrays by stable keys
|
||||
- Normalize paths (POSIX style)
|
||||
- Line endings (LF)
|
||||
- Encodings (UTF-8)
|
||||
- No environment variables in core algorithms
|
||||
- No machine-local files
|
||||
- No system clock inside algorithms
|
||||
|
||||
## 2. DATA CONTRACTS
|
||||
|
||||
### 2.1 CallGraph.v1.json Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "stella.callgraph.v1",
|
||||
"scanKey": "uuid",
|
||||
"language": "dotnet|java|node|python|go|rust|binary",
|
||||
"artifacts": [{
|
||||
"artifactKey": "…",
|
||||
"kind": "assembly|jar|module|binary",
|
||||
"sha256": "…"
|
||||
}],
|
||||
"nodes": [{
|
||||
"nodeId": "…",
|
||||
"artifactKey": "…",
|
||||
"symbolKey": "Namespace.Type::Method(…)",
|
||||
"visibility": "public|internal|private|unknown",
|
||||
"isEntrypointCandidate": false
|
||||
}],
|
||||
"edges": [{
|
||||
"from": "nodeId",
|
||||
"to": "nodeId",
|
||||
"kind": "static|heuristic",
|
||||
"reason": "direct_call|virtual_call|reflection_string|di_binding|dynamic_import|unknown",
|
||||
"weight": 1.0
|
||||
}],
|
||||
"entrypoints": [{
|
||||
"nodeId": "…",
|
||||
"kind": "http|grpc|cli|job|event|unknown",
|
||||
"route": "/api/orders/{id}",
|
||||
"framework": "aspnetcore|minimalapi|spring|express|unknown"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 RuntimeEvidence.v1.json Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "stella.runtimeevidence.v1",
|
||||
"scanKey": "uuid",
|
||||
"collectedAt": "2025-12-14T10:00:00Z",
|
||||
"environment": {
|
||||
"os": "linux|windows",
|
||||
"k8s": {"namespace": "…", "pod": "…", "container": "…"},
|
||||
"imageDigest": "sha256:…",
|
||||
"buildId": "…"
|
||||
},
|
||||
"samples": [{
|
||||
"timestamp": "…",
|
||||
"pid": 1234,
|
||||
"threadId": 77,
|
||||
"frames": ["nodeId","nodeId","nodeId"],
|
||||
"sampleWeight": 1.0
|
||||
}],
|
||||
"loadedArtifacts": [{
|
||||
"artifactKey": "…",
|
||||
"evidence": "loaded_module|mapped_file|jar_loaded"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 ReplayManifest.json Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "stella.replaymanifest.v1",
|
||||
"scanId": "uuid",
|
||||
"inputs": {
|
||||
"sbomDigest": "sha256:…",
|
||||
"callGraphs": [{"language":"dotnet","digest":"sha256:…"}],
|
||||
"runtimeEvidence": [{"digest":"sha256:…"}],
|
||||
"concelierSnapshot": "sha256:…",
|
||||
"excititorSnapshot": "sha256:…",
|
||||
"policyDigest": "sha256:…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 ProofSpine Data Model
|
||||
|
||||
```csharp
|
||||
public sealed record ProofSpine(
|
||||
string SpineId,
|
||||
string ArtifactId,
|
||||
string VulnerabilityId,
|
||||
string PolicyProfileId,
|
||||
IReadOnlyList<ProofSegment> Segments,
|
||||
string Verdict,
|
||||
string VerdictReason,
|
||||
string RootHash,
|
||||
string ScanRunId,
|
||||
DateTimeOffset CreatedAt,
|
||||
string? SupersededBySpineId
|
||||
);
|
||||
|
||||
public sealed record ProofSegment(
|
||||
string SegmentId,
|
||||
string SegmentType,
|
||||
int Index,
|
||||
string InputHash,
|
||||
string ResultHash,
|
||||
string? PrevSegmentHash,
|
||||
DsseEnvelope Envelope,
|
||||
string ToolId,
|
||||
string ToolVersion,
|
||||
string Status
|
||||
);
|
||||
```
|
||||
|
||||
**Segment Types**
|
||||
- `SBOM_SLICE`: Component relevance
|
||||
- `MATCH`: SBOM-to-vuln mapping
|
||||
- `REACHABILITY`: Symbol reachability
|
||||
- `GUARD_ANALYSIS`: Config/feature flag gates
|
||||
- `RUNTIME_OBSERVATION`: Runtime evidence
|
||||
- `POLICY_EVAL`: Lattice decision
|
||||
|
||||
## 3. DATABASE SCHEMAS
|
||||
|
||||
### 3.1 Core Reachability Tables (Postgres)
|
||||
|
||||
```sql
|
||||
-- Scan tracking
|
||||
CREATE TABLE scan (
|
||||
scan_id uuid PRIMARY KEY,
|
||||
created_at timestamptz,
|
||||
repo_uri text,
|
||||
commit_sha text,
|
||||
sbom_digest text,
|
||||
policy_digest text,
|
||||
status text
|
||||
);
|
||||
CREATE INDEX idx_scan_cache ON scan(commit_sha, sbom_digest);
|
||||
|
||||
-- Artifacts
|
||||
CREATE TABLE artifact (
|
||||
artifact_id uuid PRIMARY KEY,
|
||||
scan_id uuid REFERENCES scan,
|
||||
artifact_key text,
|
||||
kind text,
|
||||
sha256 text,
|
||||
build_id text,
|
||||
purl text,
|
||||
UNIQUE(scan_id, artifact_key)
|
||||
);
|
||||
|
||||
-- Call graph nodes
|
||||
CREATE TABLE cg_node (
|
||||
scan_id uuid,
|
||||
node_id text,
|
||||
artifact_key text,
|
||||
symbol_key text,
|
||||
visibility text,
|
||||
flags int,
|
||||
PRIMARY KEY(scan_id, node_id)
|
||||
);
|
||||
|
||||
-- Call graph edges
|
||||
CREATE TABLE cg_edge (
|
||||
scan_id uuid,
|
||||
from_node_id text,
|
||||
to_node_id text,
|
||||
kind smallint,
|
||||
reason smallint,
|
||||
weight real,
|
||||
PRIMARY KEY(scan_id, from_node_id, to_node_id, kind, reason)
|
||||
);
|
||||
CREATE INDEX idx_cg_edge_from ON cg_edge(scan_id, from_node_id);
|
||||
CREATE INDEX idx_cg_edge_to ON cg_edge(scan_id, to_node_id);
|
||||
|
||||
-- Entrypoints
|
||||
CREATE TABLE entrypoint (
|
||||
scan_id uuid,
|
||||
node_id text,
|
||||
kind text,
|
||||
framework text,
|
||||
route text,
|
||||
PRIMARY KEY(scan_id, node_id, kind, framework, route)
|
||||
);
|
||||
|
||||
-- Runtime samples
|
||||
CREATE TABLE runtime_sample (
|
||||
scan_id uuid,
|
||||
collected_at timestamptz,
|
||||
env_hash text,
|
||||
sample_id bigserial PRIMARY KEY,
|
||||
timestamp timestamptz,
|
||||
pid int,
|
||||
thread_id int,
|
||||
frames text[],
|
||||
weight real
|
||||
);
|
||||
|
||||
-- Symbol-to-component mapping
|
||||
CREATE TABLE symbol_component_map (
|
||||
scan_id uuid,
|
||||
node_id text,
|
||||
purl text,
|
||||
mapping_kind text,
|
||||
confidence real,
|
||||
PRIMARY KEY(scan_id, node_id, purl)
|
||||
);
|
||||
|
||||
-- Reachability results
|
||||
CREATE TABLE reachability_component (
|
||||
scan_id uuid,
|
||||
purl text,
|
||||
status smallint,
|
||||
confidence real,
|
||||
why jsonb,
|
||||
evidence jsonb,
|
||||
PRIMARY KEY(scan_id, purl)
|
||||
);
|
||||
|
||||
CREATE TABLE reachability_finding (
|
||||
scan_id uuid,
|
||||
cve_id text,
|
||||
purl text,
|
||||
status smallint,
|
||||
confidence real,
|
||||
why jsonb,
|
||||
evidence jsonb,
|
||||
PRIMARY KEY(scan_id, cve_id, purl)
|
||||
);
|
||||
```
|
||||
|
||||
### 3.2 Unknowns Ranking Tables
|
||||
|
||||
```sql
|
||||
CREATE TABLE unknowns (
|
||||
unknown_id uuid PRIMARY KEY,
|
||||
pkg_id text,
|
||||
pkg_version text,
|
||||
digest_anchor bytea,
|
||||
unknown_flags jsonb,
|
||||
popularity_p float,
|
||||
potential_e float,
|
||||
uncertainty_u float,
|
||||
centrality_c float,
|
||||
staleness_s float,
|
||||
score float,
|
||||
band text CHECK(band IN ('HOT','WARM','COLD')),
|
||||
graph_slice_hash bytea,
|
||||
evidence_set_hash bytea,
|
||||
normalization_trace jsonb,
|
||||
callgraph_attempt_hash bytea,
|
||||
created_at timestamptz,
|
||||
updated_at timestamptz
|
||||
);
|
||||
|
||||
CREATE TABLE deploy_refs (
|
||||
pkg_id text,
|
||||
image_id text,
|
||||
env text,
|
||||
first_seen timestamptz,
|
||||
last_seen timestamptz
|
||||
);
|
||||
|
||||
CREATE TABLE graph_metrics (
|
||||
pkg_id text PRIMARY KEY,
|
||||
degree_c float,
|
||||
betweenness_c float,
|
||||
last_calc_at timestamptz
|
||||
);
|
||||
```
|
||||
|
||||
### 3.3 Proof Spine Tables
|
||||
|
||||
```sql
|
||||
CREATE TABLE proof_spines (
|
||||
spine_id uuid PRIMARY KEY,
|
||||
artifact_id text,
|
||||
vuln_id text,
|
||||
policy_profile_id text,
|
||||
verdict text,
|
||||
verdict_reason text,
|
||||
root_hash text,
|
||||
scan_run_id uuid,
|
||||
created_at timestamptz,
|
||||
superseded_by_spine_id uuid,
|
||||
segment_count int
|
||||
);
|
||||
CREATE INDEX idx_spine_lookup ON proof_spines(artifact_id, vuln_id, policy_profile_id);
|
||||
|
||||
CREATE TABLE proof_segments (
|
||||
segment_id uuid PRIMARY KEY,
|
||||
spine_id uuid REFERENCES proof_spines,
|
||||
idx int,
|
||||
segment_type text,
|
||||
input_hash text,
|
||||
result_hash text,
|
||||
prev_segment_hash text,
|
||||
envelope bytea,
|
||||
tool_id text,
|
||||
tool_version text,
|
||||
status text,
|
||||
created_at timestamptz
|
||||
);
|
||||
```
|
||||
|
||||
## 4. ALGORITHMS
|
||||
|
||||
### 4.1 Reachability Status Classification
|
||||
|
||||
**Status Values**
|
||||
- `UNREACHABLE`: No path from entrypoints
|
||||
- `POSSIBLY_REACHABLE`: Graph incomplete/dynamic behavior
|
||||
- `REACHABLE_STATIC`: Static path exists
|
||||
- `REACHABLE_PROVEN`: Runtime evidence confirms
|
||||
|
||||
**Confidence Scoring (Deterministic)**
|
||||
|
||||
Base scores:
|
||||
```
|
||||
UNREACHABLE → 0.05
|
||||
POSSIBLY_REACHABLE → 0.35
|
||||
REACHABLE_STATIC → 0.70
|
||||
REACHABLE_PROVEN → 0.95
|
||||
```
|
||||
|
||||
Modifiers:
|
||||
```
|
||||
+0.10 if path uses only static edges
|
||||
-0.15 if path includes reflection_string|dynamic_import
|
||||
+0.10 if runtime evidence hits affected component
|
||||
-0.10 if NO_ENTRYPOINTS_DISCOVERED
|
||||
Clamp to [0, 1]
|
||||
```
|
||||
|
||||
### 4.2 Reachability Computation Algorithm
|
||||
|
||||
```
|
||||
Inputs:
|
||||
- Call graph nodes/edges + entrypoints
|
||||
- Runtime evidence (optional)
|
||||
- SBOM with purls
|
||||
- Vulnerability facts (CVE ↔ purl/version)
|
||||
- VEX statements
|
||||
|
||||
Steps:
|
||||
1. Build adjacency list for cg_edge.kind in (static, heuristic)
|
||||
2. Optional: Compress SCCs (Tarjan/Kosaraju)
|
||||
3. Seed from entrypoints; if empty → mark POSSIBLY_REACHABLE with NO_ENTRYPOINTS_DISCOVERED
|
||||
4. Traverse reachable nodes; track:
|
||||
- firstSeenFromEntrypoint[node]
|
||||
- pathWitness[node]
|
||||
5. Map reachable nodes to purls via symbol_component_map:
|
||||
Priority order:
|
||||
a. Exact binary symbol → package metadata
|
||||
b. Assembly/jar/module to SBOM component (hash/purl)
|
||||
c. Heuristics: namespace prefixes, import paths, jar manifest
|
||||
6. Runtime evidence upgrade:
|
||||
- Mark frame nodes as "executed"
|
||||
- Mint runtime edges: consecutive frames → runtime_minted
|
||||
- Upgrade to REACHABLE_PROVEN if executed node maps to affected purl
|
||||
7. Compute confidence using deterministic formula
|
||||
```
|
||||
|
||||
### 4.3 Unknowns Ranking Algorithm
|
||||
|
||||
**Score Formula**
|
||||
```
|
||||
Score = clamp01(
|
||||
wP·P + # Popularity impact
|
||||
wE·E + # Exploit consequence potential
|
||||
wU·U + # Uncertainty density
|
||||
wC·C + # Graph centrality
|
||||
wS·S # Evidence staleness
|
||||
)
|
||||
```
|
||||
|
||||
**Default Weights**
|
||||
```
|
||||
wP = 0.25 (deployment impact)
|
||||
wE = 0.25 (potential consequence)
|
||||
wU = 0.25 (uncertainty density)
|
||||
wC = 0.15 (graph centrality)
|
||||
wS = 0.10 (evidence staleness)
|
||||
```
|
||||
|
||||
**Heuristics**
|
||||
```
|
||||
P = min(1, log10(1 + deployments)/log10(1 + 100))
|
||||
U = sum of flags, capped at 1.0:
|
||||
+0.30 if no provenance anchor
|
||||
+0.25 if version_range
|
||||
+0.20 if conflicting_feeds
|
||||
+0.15 if missing_vector
|
||||
+0.10 if unreachable source advisory
|
||||
S = min(1, age_days / 14)
|
||||
```
|
||||
|
||||
**Band Assignment**
|
||||
```
|
||||
Score ≥ 0.70 → HOT (immediate rescan + VEX escalation)
|
||||
0.40 ≤ Score < 0.70 → WARM (scheduled rescan 12-72h)
|
||||
Score < 0.40 → COLD (weekly batch)
|
||||
```
|
||||
|
||||
### 4.4 Node ID Computation (.NET)
|
||||
|
||||
**Primary (IL-based)**
|
||||
```
|
||||
nodeId = SHA256(MVID + ":" + metadataToken + ":" + arity + ":" + signatureShape)
|
||||
```
|
||||
|
||||
**Fallback (source-only)**
|
||||
```
|
||||
nodeId = SHA256(projectPath + ":" + file + ":" + span + ":" + symbolDisplayString)
|
||||
```
|
||||
|
||||
**External nodes**
|
||||
```
|
||||
nodeId = SHA256("ext:" + artifactKey + ":" + symbolKey)
|
||||
```
|
||||
|
||||
### 4.5 Canonical Symbol Key Format
|
||||
|
||||
```
|
||||
{Namespace}.{TypeName}[`Arity][+Nested]::{MethodName}[`Arity]({ParamType1},{ParamType2},...)
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Use `System.*` full names for BCL types
|
||||
- Use `+` for nested types (metadata style)
|
||||
- Use backtick arity for generic type/method definitions
|
||||
- Arrays: `System.String[]`
|
||||
- Byref: `System.String&`
|
||||
|
||||
### 4.6 Reachability Cache Algorithm
|
||||
|
||||
```csharp
|
||||
public readonly record struct ReachKey(
|
||||
string AlgoSig, // e.g., "RTA@sha256:…"
|
||||
string InputsHash, // SBOM slice + policy + versions
|
||||
string CallPathHash // normalized query graph
|
||||
);
|
||||
|
||||
public sealed class ReachCache {
|
||||
private readonly ConcurrentDictionary<ReachKey, Lazy<Task<ReachResult>>> _memo = new();
|
||||
|
||||
public Task<ReachResult> GetOrComputeAsync(
|
||||
ReachKey key,
|
||||
Func<Task<ReachResult>> compute,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var lazy = _memo.GetOrAdd(key, _ => new Lazy<Task<ReachResult>>(
|
||||
() => compute(), LazyThreadSafetyMode.ExecutionAndPublication));
|
||||
|
||||
return lazy.Value.ContinueWith(t => {
|
||||
if (t.IsCompletedSuccessfully) return t.Result;
|
||||
_memo.TryRemove(key, out _);
|
||||
throw t.Exception ?? new Exception("reachability failed");
|
||||
}, ct);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Operational rules**
|
||||
- Canonical everything: sort nodes/edges, normalize paths
|
||||
- Cache scope: per-scan, per-workspace, or per-feed version
|
||||
- TTL: 15-60 minutes or evict after pipeline completes
|
||||
- Max-entries cap
|
||||
- Concurrency: `Lazy<Task<>>` coalesces duplicate in-flight calls
|
||||
|
||||
### 4.7 Proof Spine Construction
|
||||
|
||||
```
|
||||
1. Sbomer produces SBOM_SLICE segment, DSSE-signs it
|
||||
2. Scanner produces MATCH segment
|
||||
3. Reachability produces REACHABILITY segment
|
||||
4. Guard Analyzer produces GUARD_ANALYSIS segment
|
||||
5. Vexer evaluates lattice, produces POLICY_EVAL segment
|
||||
6. ProofSpineBuilder:
|
||||
- Sorts segments by predetermined order
|
||||
- Chains PrevSegmentHash
|
||||
- Computes RootHash = hash(concat of segment hashes)
|
||||
- Stores ProofSpine with deterministic SpineId
|
||||
```
|
||||
|
||||
## 5. API CONTRACTS
|
||||
|
||||
### 5.1 Scanner Ingestion Endpoints
|
||||
|
||||
```
|
||||
POST /api/scans
|
||||
Returns: scanId
|
||||
|
||||
POST /api/scans/{scanId}/callgraphs
|
||||
Body: CallGraph.v1.json
|
||||
Header: Content-Digest (for idempotency)
|
||||
|
||||
POST /api/scans/{scanId}/runtimeevidence
|
||||
Body: RuntimeEvidence.v1.json
|
||||
|
||||
POST /api/scans/{scanId}/sbom
|
||||
Body: CycloneDX/SPDX
|
||||
|
||||
POST /api/scans/{scanId}/compute-reachability
|
||||
Idempotent trigger
|
||||
```
|
||||
|
||||
### 5.2 Query Endpoints
|
||||
|
||||
```
|
||||
GET /api/scans/{scanId}/reachability/components?purl=...
|
||||
GET /api/scans/{scanId}/reachability/findings?cve=...
|
||||
GET /api/scans/{scanId}/reachability/explain?cve=...&purl=...
|
||||
Returns: why[], path witness, sample refs
|
||||
```
|
||||
|
||||
### 5.3 Export Endpoints
|
||||
|
||||
```
|
||||
GET /api/scans/{scanId}/exports/sarif
|
||||
GET /api/scans/{scanId}/exports/cdxr # CycloneDX reachability extension
|
||||
GET /api/scans/{scanId}/exports/openvex
|
||||
```
|
||||
|
||||
### 5.4 Proof Spine Endpoints
|
||||
|
||||
```
|
||||
GET /graph/runtime/{podId}/lineage
|
||||
GET /graph/image/{digest}/vex
|
||||
GET /spines/{spineId}
|
||||
Returns: ProofSpine with all segments
|
||||
```
|
||||
|
||||
## 6. .NET IMPLEMENTATION PATTERNS
|
||||
|
||||
### 6.1 .NET Worker (Roslyn + IL) Required Features
|
||||
|
||||
**Call Graph Extraction**
|
||||
- Direct invocation edges: `InvocationExpressionSyntax`
|
||||
- Object creation edges: constructors
|
||||
- Delegate invocation: record heuristic edge when target unresolved
|
||||
- Virtual/interface dispatch: record `virtual_call` edge to declared method
|
||||
- Async/await: connect logical caller → awaited method
|
||||
|
||||
**Entrypoint Discovery**
|
||||
- `Program.Main` (classic)
|
||||
- ASP.NET Core:
|
||||
- Controllers: `[ApiController]`, route attributes, action methods
|
||||
- Minimal APIs: `MapGet/MapPost/MapMethods` patterns
|
||||
- gRPC: `MapGrpcService<T>()` and service methods
|
||||
- Hosted services: `IHostedService`, `BackgroundService.ExecuteAsync`
|
||||
|
||||
**Reflection and DI Heuristics**
|
||||
- `Type.GetType("…")`, `Assembly.GetType`, `GetMethod("…")`, `Invoke`
|
||||
- `services.AddTransient<IFoo,Foo>()` / `AddScoped` / `AddSingleton`
|
||||
- `Activator.CreateInstance`, `ServiceProvider.GetService`
|
||||
- Produce heuristic edges with `reason = di_binding` or `reflection_string`
|
||||
|
||||
### 6.2 NuGet Dependencies
|
||||
|
||||
```xml
|
||||
<!-- Roslyn / MSBuild -->
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Build.Locator" Version="*" />
|
||||
|
||||
<!-- IL + metadata -->
|
||||
<!-- Use System.Reflection.Metadata (BCL) -->
|
||||
<!-- Optional: Mono.Cecil for faster IL traversal -->
|
||||
|
||||
<!-- CLI + JSON -->
|
||||
<PackageReference Include="System.CommandLine" Version="*" />
|
||||
<PackageReference Include="System.Text.Json" Version="*" />
|
||||
```
|
||||
|
||||
### 6.3 Roslyn IL Opcodes for Call Detection
|
||||
|
||||
```
|
||||
Recognize as "calls":
|
||||
- call
|
||||
- callvirt
|
||||
- newobj
|
||||
- jmp
|
||||
- ldftn
|
||||
- ldvirtftn
|
||||
```
|
||||
|
||||
### 6.4 MSBuild Workspace Loading Pattern
|
||||
|
||||
```csharp
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.MSBuild;
|
||||
|
||||
var ws = MSBuildWorkspace.Create();
|
||||
var sln = await ws.OpenSolutionAsync(@"path\to.sln");
|
||||
foreach (var proj in sln.Projects)
|
||||
foreach (var doc in proj.Documents)
|
||||
{
|
||||
var model = await doc.GetSemanticModelAsync();
|
||||
var root = await doc.GetSyntaxRootAsync();
|
||||
foreach (var node in root.DescendantNodes()
|
||||
.OfType<InvocationExpressionSyntax>())
|
||||
{
|
||||
var sym = model.GetSymbolInfo(node).Symbol as IMethodSymbol;
|
||||
if (sym != null)
|
||||
{
|
||||
// record edge: caller -> sym
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. CONFIGURATION PATTERNS
|
||||
|
||||
### 7.1 Crypto Profiles
|
||||
|
||||
```csharp
|
||||
public interface ICryptoProfile
|
||||
{
|
||||
string ProfileId { get; }
|
||||
byte[] Sign(byte[] data, string keyId);
|
||||
bool Verify(byte[] data, byte[] signature, string keyId);
|
||||
}
|
||||
|
||||
// Implementations: FipsProfile, GostProfile, EidasProfile, SmProfile, PqcProfile
|
||||
```
|
||||
|
||||
**Profile Metadata**
|
||||
- Algorithm
|
||||
- Key ID
|
||||
- Profile name
|
||||
|
||||
**Key Rotation**
|
||||
- Keys have validity intervals
|
||||
- Spines keep KeyId in each DSSE signature
|
||||
- Authority maintains trust table: which keys trusted for which SegmentType and time window
|
||||
|
||||
### 7.2 Offline Bundle Format
|
||||
|
||||
**Required Contents**
|
||||
- SBOM + feeds + policy bundle + key material
|
||||
- Manifest with hashes of all contents
|
||||
- Replay manifest for deterministic rerun
|
||||
|
||||
**Format**
|
||||
- Zip/tar + manifest
|
||||
- Each entry content-addressed
|
||||
|
||||
### 7.3 Cache Configuration (appsettings)
|
||||
|
||||
```json
|
||||
{
|
||||
"Scanner": {
|
||||
"Reach": {
|
||||
"Cache": {
|
||||
"MaxEntries": 10000,
|
||||
"TtlMinutes": 60,
|
||||
"EvictOnPipelineComplete": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. METRICS AND THRESHOLDS
|
||||
|
||||
### 8.1 Performance SLOs (v1)
|
||||
|
||||
```
|
||||
Medium service (100k LOC .NET) static graph: < 2 minutes
|
||||
Reachability compute: < 30 seconds
|
||||
Query GET finding: < 200ms p95
|
||||
```
|
||||
|
||||
### 8.2 Quality Metrics
|
||||
|
||||
**Proof verification**
|
||||
- % verified proofs
|
||||
- Proof verification failures
|
||||
- Proof age since last verification
|
||||
- % entries with valid inclusion proof (Rekor)
|
||||
|
||||
**Unknowns triage**
|
||||
- Hot/Warm/Cold distribution
|
||||
- Rescan success rate after N attempts
|
||||
- Evidence staleness distribution
|
||||
|
||||
**Drift detection**
|
||||
- Runtime edges not in static graph (above threshold → emit COVERAGE_DRIFT warning)
|
||||
|
||||
### 8.3 Benchmark Metrics (Moat)
|
||||
|
||||
| Metric | Target |
|
||||
|--------|--------|
|
||||
| Time-to-evidence: SBOM → signed call-graph | < 5 minutes for 100k LOC service |
|
||||
| SBOM-diff false positive rate under dependency churn | < 5% change in reachability status for non-code changes |
|
||||
| Deterministic priority scoring under air-gap replay | Bit-identical results given same inputs |
|
||||
| Proof verification time | < 200ms p95 |
|
||||
|
||||
## 9. TESTING PATTERNS
|
||||
|
||||
### 9.1 Golden Corpus Requirements
|
||||
|
||||
**Mandatory Test Cases**
|
||||
1. Minimal ASP.NET controller with reachable endpoint → vulnerable lib call
|
||||
2. Vulnerable lib present but never called → unreachable
|
||||
3. Reflection-based activation → "possible" unless runtime proves
|
||||
4. BackgroundService job case
|
||||
5. Version range ambiguity
|
||||
6. Mismatched epoch/backport
|
||||
7. Missing CVSS vector
|
||||
8. Conflicting severity vendor/NVD
|
||||
9. Unanchored filesystem library
|
||||
|
||||
**Assertions per Test**
|
||||
- Reachability status
|
||||
- At least one `why[]` reason
|
||||
- Deterministic confidence within ±0.01
|
||||
- Segments with expected status (verified/partial/invalid)
|
||||
|
||||
### 9.2 Replay Manifest Tests
|
||||
|
||||
Given manifest containing:
|
||||
- feed hashes
|
||||
- rules version
|
||||
- normalization logic
|
||||
- lattice rules
|
||||
|
||||
Assert: ranking/reachability recomputes identically (byte-for-byte)
|
||||
|
||||
### 9.3 Signature Tampering Tests
|
||||
|
||||
- Flip byte in DSSE payload → UI must show `invalid`
|
||||
- Mark key untrusted → segments show `untrusted-key`
|
||||
- Entire spine marked as compromised
|
||||
|
||||
## 10. SPECIFICATION COMPLIANCE
|
||||
|
||||
### 10.1 SBOM Standards
|
||||
|
||||
**CycloneDX**
|
||||
- Version: 1.6 (ECMA-424) and 1.7 (current)
|
||||
- Media type: include version parameter
|
||||
- Ingest: validate against ECMA-424/1.7 schemas
|
||||
|
||||
**SPDX**
|
||||
- Version: 3.0.1
|
||||
- Validate against canonical spec
|
||||
|
||||
**Ingestion Rules**
|
||||
- Accept only `*.cdx.json` and `*.spdx.json`
|
||||
- Hard fail others
|
||||
- Store: raw bytes + parsed form + normalized canonical form
|
||||
|
||||
### 10.2 VEX Standards
|
||||
|
||||
**OpenVEX**
|
||||
- Minimal, SBOM-agnostic
|
||||
- Predicate type: `https://openvex.dev/ns/v0.2.0`
|
||||
|
||||
**CSAF VEX**
|
||||
- Alternative format for interoperability
|
||||
|
||||
**Required VEX Fields**
|
||||
- status: `not_affected|affected|fixed|under_investigation`
|
||||
- justification
|
||||
- timestamp
|
||||
- author
|
||||
- link to supporting evidence
|
||||
|
||||
### 10.3 Attestation Standards
|
||||
|
||||
**DSSE (Dead Simple Signing Envelope)**
|
||||
- Use for all signed artifacts
|
||||
- Payload: canonical JSON
|
||||
- Envelope: signature + key metadata + profile
|
||||
|
||||
**in-toto**
|
||||
- Statement structure
|
||||
- Predicate types:
|
||||
- SBOM: `https://spdx.dev/Document`
|
||||
- VEX: OpenVEX predicate URI
|
||||
- Custom: reachability predicates
|
||||
|
||||
**Cosign Integration**
|
||||
```bash
|
||||
cosign attest --yes --type https://spdx.dev/Document \
|
||||
--predicate sbom.spdx.json \
|
||||
--key cosign.key \
|
||||
"${IMAGE_DIGEST}"
|
||||
```
|
||||
|
||||
### 10.4 Rekor Integration
|
||||
|
||||
**CLI Verification**
|
||||
```bash
|
||||
rekor-cli verify --rekor_server https://rekor.sigstore.dev \
|
||||
--signature artifact.sig \
|
||||
--public-key cosign.pub \
|
||||
--artifact artifact.bin
|
||||
```
|
||||
|
||||
**Persistence per Entry**
|
||||
- Rekor UUID
|
||||
- Log index
|
||||
- Integrated time
|
||||
- Inclusion proof data
|
||||
|
||||
## 11. CLI COMMANDS
|
||||
|
||||
### 11.1 Worker CLI
|
||||
|
||||
```bash
|
||||
# Artifacts-first scan
|
||||
stella-worker-dotnet scan \
|
||||
--scanKey 00000000-0000-0000-0000-000000000000 \
|
||||
--assemblies ./artifacts/bin/Release \
|
||||
--out ./callgraph.json
|
||||
|
||||
# Build-and-scan
|
||||
stella-worker-dotnet scan \
|
||||
--scanKey ... \
|
||||
--sln ./src/MySolution.sln \
|
||||
--configuration Release \
|
||||
--tfm net10.0 \
|
||||
--buildMode build \
|
||||
--out ./callgraph.json
|
||||
|
||||
# Upload to scanner.webservice
|
||||
stella-worker-dotnet scan \
|
||||
--scanKey ... \
|
||||
--assemblies ./artifacts/bin/Release \
|
||||
--upload https://scanner/api/scans/{scanId}/callgraphs \
|
||||
--apiKey $STELLA_API_KEY
|
||||
```
|
||||
|
||||
### 11.2 Replay CLI
|
||||
|
||||
```bash
|
||||
stellaops scan \
|
||||
--replay-manifest <id-or-file> \
|
||||
--artifact <image-digest> \
|
||||
--vuln <cve> \
|
||||
--explain
|
||||
```
|
||||
|
||||
### 11.3 Reachability Query CLI
|
||||
|
||||
```bash
|
||||
stella scan graph --lang dotnet --sln path.sln --out graph.scc.json
|
||||
stella scan runtime --target pod/myservice --duration 30s --out stacks.json
|
||||
stella reachability join \
|
||||
--graph graph.scc.json \
|
||||
--runtime stacks.json \
|
||||
--sbom bom.cdx.json \
|
||||
--out reach.cdxr.json
|
||||
```
|
||||
|
||||
## 12. DEVELOPER CHECKLIST
|
||||
|
||||
### 12.1 Per-Feature Definition of Done
|
||||
|
||||
For any feature touching scans, VEX, or evidence:
|
||||
|
||||
- [ ] Deterministic: input manifest defined, canonicalization applied, golden fixture(s) added
|
||||
- [ ] Evidence: outputs DSSE-wrapped and linked
|
||||
- [ ] Reachability/Lattice: runs only in allowed services, records algorithm IDs
|
||||
- [ ] Crypto: calls through profile abstraction, tests for ≥2 profiles
|
||||
- [ ] Graph: lineage edges added, node/edge IDs stable and queryable
|
||||
- [ ] UX/API: API to retrieve structured evidence
|
||||
- [ ] Tests: unit + golden + integration test with full SBOM → scan → VEX chain
|
||||
|
||||
### 12.2 Determinism Checklist
|
||||
|
||||
- [ ] Input manifest type defined and versioned
|
||||
- [ ] Canonicalization applied before hashing/signing
|
||||
- [ ] Output stored with `inputsDigest` and `algoDigest`
|
||||
- [ ] At least one golden fixture proves determinism
|
||||
- [ ] No environment variables in core algorithm
|
||||
- [ ] No machine-local files
|
||||
- [ ] No system clock inside algorithms
|
||||
|
||||
### 12.3 Reachability Implementation Checklist
|
||||
|
||||
- [ ] Reachability algorithms only in Scanner.WebService
|
||||
- [ ] Cache lazy and keyed by deterministic inputs
|
||||
- [ ] Output includes explicit evidence pointers
|
||||
- [ ] UI endpoints expose reachability state in structured form
|
||||
- [ ] All modifiers recorded in `why[]`
|
||||
|
||||
### 12.4 Crypto-Sovereign Checklist
|
||||
|
||||
- [ ] No direct crypto calls in feature code; only via profile layer
|
||||
- [ ] All attestations carry algorithm + key id + profile
|
||||
- [ ] Offline bundle type exists for this workflow
|
||||
- [ ] Tests for at least 2 different crypto profiles
|
||||
|
||||
### 12.5 Policy/Lattice Checklist
|
||||
|
||||
- [ ] Facts and policies serialized separately
|
||||
- [ ] Lattice code in allowed services only
|
||||
- [ ] Merge strategies named and versioned
|
||||
- [ ] Artifacts record which lattice algorithm used
|
||||
|
||||
### 12.6 Proof-Linked VEX Checklist
|
||||
|
||||
- [ ] VEX schema includes pointers to all upstream artifacts (sbomDigest, scanId, reachMapDigest, policyDigest, signerKeyId)
|
||||
- [ ] No duplication of SBOM/scan content inside VEX
|
||||
- [ ] DSSE used as standard envelope type
|
||||
|
||||
### 12.7 Unknowns Triage Checklist
|
||||
|
||||
- [ ] Persist all traces for deterministic replay
|
||||
- [ ] Ranking depends only on manifest-declared parameters
|
||||
- [ ] All uncertainty factors are explicit flags
|
||||
- [ ] Scoring reproducible under identical inputs
|
||||
- [ ] Scheduler decision table deterministic and tested
|
||||
- [ ] API exposes full reasoning
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,291 @@
|
||||
# Rekor Integration Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 30-Nov-2025 - Rekor Receipt Checklist for Stella Ops
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. REQUIREMENTS
|
||||
|
||||
- Rekor receipts must be deterministic, tenant-scoped, and verifiable offline
|
||||
- For Authority/Sbomer/Vexer flows
|
||||
- Field-level ownership map for receipts and bundles
|
||||
- Offline verifier expectations
|
||||
- Mirror snapshot rules
|
||||
- DSSE/receipt schema pointers
|
||||
|
||||
## 2. DETERMINISM & OFFLINE
|
||||
|
||||
- Bundle TSA/time anchors with receipts
|
||||
- Prefer mirror snapshots
|
||||
- Avoid live log fetches in examples
|
||||
|
||||
## 3. DELIVERABLES
|
||||
|
||||
- Schema draft
|
||||
- Offline verifier stub
|
||||
- Module dossier updates
|
||||
|
||||
## 4. REKOR ENTRY STRUCTURE
|
||||
|
||||
```json
|
||||
{
|
||||
"dsseSha256": "sha256:...",
|
||||
"rekor": {
|
||||
"uuid": "...",
|
||||
"logIndex": 12345,
|
||||
"logId": "...",
|
||||
"integratedTime": 1733736000,
|
||||
"inclusionProof": {
|
||||
"rootHash": "...",
|
||||
"hashes": ["...", "..."],
|
||||
"checkpoint": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. REKOR CLIENT INTERFACE
|
||||
|
||||
```csharp
|
||||
public interface IRekorClient
|
||||
{
|
||||
Task<RekorEntry> SubmitDsseAsync(
|
||||
DsseEnvelope envelope,
|
||||
CancellationToken ct = default
|
||||
);
|
||||
|
||||
Task<bool> VerifyInclusionAsync(
|
||||
RekorEntry entry,
|
||||
byte[] payloadDigest,
|
||||
byte[] rekorPublicKey,
|
||||
CancellationToken ct = default
|
||||
);
|
||||
}
|
||||
|
||||
public record RekorEntry(
|
||||
string Uuid,
|
||||
long LogIndex,
|
||||
string LogId,
|
||||
long IntegratedTime,
|
||||
InclusionProof Proof
|
||||
);
|
||||
|
||||
public record InclusionProof(
|
||||
string RootHash,
|
||||
string[] Hashes,
|
||||
string Checkpoint
|
||||
);
|
||||
```
|
||||
|
||||
## 6. CLI VERIFICATION
|
||||
|
||||
### 6.1 Rekor CLI Commands
|
||||
|
||||
```bash
|
||||
rekor-cli verify --rekor_server https://rekor.sigstore.dev \
|
||||
--signature artifact.sig \
|
||||
--public-key cosign.pub \
|
||||
--artifact artifact.bin
|
||||
```
|
||||
|
||||
### 6.2 Persistence per Entry
|
||||
|
||||
- Rekor UUID
|
||||
- Log index
|
||||
- Integrated time
|
||||
- Inclusion proof data
|
||||
|
||||
## 7. OFFLINE REKOR MIRROR
|
||||
|
||||
### 7.1 Mirror Structure
|
||||
|
||||
```
|
||||
/evidence/tlog/
|
||||
checkpoint.sig # signed tree head
|
||||
entries/ # *.jsonl (Merkle leaves) + proofs
|
||||
```
|
||||
|
||||
### 7.2 Verification Steps
|
||||
|
||||
```
|
||||
1. Recompute Merkle root from entries
|
||||
2. Check matches `checkpoint.sig` (after verifying signature with tlog root key)
|
||||
3. For each attestation:
|
||||
- Verify UUID/digest appears in entry pack
|
||||
- Verify inclusion proof resolves
|
||||
```
|
||||
|
||||
## 8. REKOR STORAGE SCHEMA
|
||||
|
||||
```sql
|
||||
CREATE TABLE rekor_entries (
|
||||
dsse_sha256 VARCHAR(64) PRIMARY KEY,
|
||||
log_index BIGINT NOT NULL,
|
||||
log_id TEXT NOT NULL,
|
||||
integrated_time BIGINT NOT NULL,
|
||||
inclusion_proof JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_rekor_log_index ON rekor_entries(log_index);
|
||||
CREATE INDEX idx_rekor_integrated_time ON rekor_entries(integrated_time);
|
||||
```
|
||||
|
||||
## 9. REKOR FAILURE HANDLING
|
||||
|
||||
### 9.1 Rekor Unavailable
|
||||
|
||||
```
|
||||
If Rekor unavailable:
|
||||
- Store DSSE envelope locally
|
||||
- Queue for retry
|
||||
- Mark proof chain as "rekorStatus: pending"
|
||||
- Internal-only until Rekor sync succeeds
|
||||
- Flag in verification results
|
||||
```
|
||||
|
||||
### 9.2 Rekor Verification Failed
|
||||
|
||||
```
|
||||
If verification fails:
|
||||
- Log error with structured fields (rekorUuid, dsseDigest, failureReason)
|
||||
- Mark envelope as "rekor_verification_failed"
|
||||
- Do not accept as valid proof
|
||||
- Alert security team
|
||||
```
|
||||
|
||||
## 10. INTEGRATION POINTS
|
||||
|
||||
### 10.1 Authority Module
|
||||
|
||||
- Submit signed attestations to Rekor
|
||||
- Store receipts with DSSE envelopes
|
||||
- Verify inclusion proofs on retrieval
|
||||
|
||||
### 10.2 Sbomer Module
|
||||
|
||||
- Submit SBOM attestations to Rekor
|
||||
- Link Rekor UUID to SBOM entries
|
||||
|
||||
### 10.3 Vexer Module
|
||||
|
||||
- Submit VEX statements to Rekor
|
||||
- Store receipts with VEX decisions
|
||||
|
||||
## 11. METRICS & OBSERVABILITY
|
||||
|
||||
```
|
||||
rekor_submit_total{status="success|failed"}
|
||||
rekor_submit_latency_seconds
|
||||
rekor_verify_total{result="pass|fail"}
|
||||
rekor_verify_latency_seconds
|
||||
rekor_queue_depth (pending submissions)
|
||||
rekor_retry_attempts_total
|
||||
```
|
||||
|
||||
## 12. CONFIGURATION
|
||||
|
||||
```yaml
|
||||
rekor:
|
||||
server_url: https://rekor.sigstore.dev
|
||||
public_key_path: /etc/stellaops/rekor-pub.pem
|
||||
offline_mode: false
|
||||
retry:
|
||||
max_attempts: 3
|
||||
initial_delay_ms: 1000
|
||||
max_delay_ms: 10000
|
||||
timeout_seconds: 30
|
||||
```
|
||||
|
||||
## 13. OFFLINE BUNDLE INTEGRATION
|
||||
|
||||
### 13.1 Rekor Receipt in Offline Kit
|
||||
|
||||
**rekor-receipt.json**:
|
||||
```json
|
||||
{
|
||||
"uuid": "string",
|
||||
"logIndex": int,
|
||||
"rootHash": "string",
|
||||
"hashes": ["string"],
|
||||
"checkpoint": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### 13.2 Offline Verification
|
||||
|
||||
```
|
||||
1. Load Rekor public key from offline bundle
|
||||
2. Verify checkpoint signature
|
||||
3. Recompute Merkle root from inclusion proof
|
||||
4. Verify root hash matches checkpoint
|
||||
5. Verify DSSE envelope hash appears in proof
|
||||
```
|
||||
|
||||
## 14. SECURITY CONSIDERATIONS
|
||||
|
||||
### 14.1 Trust Model
|
||||
|
||||
- Rekor provides transparency, not trust
|
||||
- Trust derives from key verification
|
||||
- Inclusion proof demonstrates timestamp
|
||||
- Does not prove correctness of content
|
||||
|
||||
### 14.2 Key Pinning
|
||||
|
||||
- Pin Rekor public key via out-of-band distribution
|
||||
- Verify checkpoint signatures before trusting
|
||||
- Maintain key version history
|
||||
|
||||
### 14.3 Replay Protection
|
||||
|
||||
- Use integrated_time to detect backdated entries
|
||||
- Compare with local clock (within reasonable skew)
|
||||
- Alert on time anomalies
|
||||
|
||||
## 15. TESTING REQUIREMENTS
|
||||
|
||||
### 15.1 Integration Tests
|
||||
|
||||
- Submit DSSE to Rekor (staging)
|
||||
- Verify inclusion proof
|
||||
- Offline verification with mirror
|
||||
- Retry on failure
|
||||
- Timeout handling
|
||||
|
||||
### 15.2 Failure Scenarios
|
||||
|
||||
- Rekor unavailable
|
||||
- Network timeout
|
||||
- Invalid inclusion proof
|
||||
- Signature verification failure
|
||||
- Malformed response
|
||||
|
||||
## 16. OPERATIONAL PROCEDURES
|
||||
|
||||
### 16.1 Rekor Mirror Sync
|
||||
|
||||
```bash
|
||||
# Download latest checkpoint
|
||||
curl https://rekor.sigstore.dev/api/v1/log/checkpoint > checkpoint.sig
|
||||
|
||||
# Verify checkpoint signature
|
||||
rekor-cli verify --checkpoint checkpoint.sig --public-key rekor-pub.pem
|
||||
|
||||
# Sync entries since last update
|
||||
rekor-cli sync --since <last_log_index> --output ./entries/
|
||||
```
|
||||
|
||||
### 16.2 Monitoring
|
||||
|
||||
- Alert on Rekor submission failures >1% over 5 minutes
|
||||
- Alert on verification failures >0.1% over 5 minutes
|
||||
- Alert on queue depth >1000 for >10 minutes
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,255 @@
|
||||
# Smart-Diff Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 09-Dec-2025 - Smart‑Diff and Provenance‑Rich Binaries
|
||||
- 12-Dec-2025 - Smart‑Diff Detects Meaningful Risk Shifts
|
||||
- 13-Dec-2025 - Smart‑Diff - Defining Meaningful Risk Change
|
||||
- 05-Dec-2025 - Design Notes on Smart‑Diff and Call‑Stack Analysis
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. SMART-DIFF PREDICATE SCHEMA
|
||||
|
||||
```json
|
||||
{
|
||||
"predicateType": "stellaops.dev/predicates/smart-diff@v1",
|
||||
"predicate": {
|
||||
"baseImage": {"name":"...", "digest":"sha256:..."},
|
||||
"targetImage": {"name":"...", "digest":"sha256:..."},
|
||||
"diff": {
|
||||
"filesAdded": [...],
|
||||
"filesRemoved": [...],
|
||||
"filesChanged": [{"path":"...", "hunks":[...]}],
|
||||
"packagesChanged": [{"name":"openssl","from":"1.1.1u","to":"3.0.14"}]
|
||||
},
|
||||
"context": {
|
||||
"entrypoint":["/app/start"],
|
||||
"env":{"FEATURE_X":"true"},
|
||||
"user":{"uid":1001,"caps":["NET_BIND_SERVICE"]}
|
||||
},
|
||||
"reachabilityGate": {"reachable":true,"configActivated":true,"runningUser":false,"class":6},
|
||||
"scanner": {"name":"StellaOps.Scanner","version":"...","ruleset":"reachability-2025.12"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. REACHABILITY GATE (3-BIT SEVERITY)
|
||||
|
||||
**Data Model:**
|
||||
```csharp
|
||||
public sealed record ReachabilityGate(
|
||||
bool? Reachable, // true / false / null for unknown
|
||||
bool? ConfigActivated,
|
||||
bool? RunningUser,
|
||||
int Class, // 0..7 derived from the bits when all known
|
||||
string Rationale // short explanation, human-readable
|
||||
);
|
||||
```
|
||||
|
||||
**Class Computation:** 0-7 based on 3 binary gates (reachable, config-activated, running user)
|
||||
|
||||
**Unknown Handling:**
|
||||
- Never silently treat `null` as `false` or `true`
|
||||
- If any bit is `null`, set `Class = -1` or compute from known bits only
|
||||
|
||||
## 3. DELTA DATA STRUCTURES
|
||||
|
||||
```csharp
|
||||
// Delta.Packages
|
||||
{
|
||||
added[],
|
||||
removed[],
|
||||
changed[{name, fromVer, toVer}]
|
||||
}
|
||||
|
||||
// Delta.Layers
|
||||
{
|
||||
changed[{path, fromHash, toHash, licenseDelta}]
|
||||
}
|
||||
|
||||
// Delta.Functions
|
||||
{
|
||||
added[],
|
||||
removed[],
|
||||
changed[{symbol, file, signatureHashFrom, signatureHashTo}]
|
||||
}
|
||||
|
||||
// PatchDelta
|
||||
{
|
||||
addedSymbols[],
|
||||
removedSymbols[],
|
||||
changedSignatures[]
|
||||
}
|
||||
```
|
||||
|
||||
## 4. SMART-DIFF ALGORITHMS
|
||||
|
||||
**Core Diff Computation:**
|
||||
```pseudo
|
||||
prev = load_snapshot(t-1)
|
||||
curr = load_snapshot(t)
|
||||
|
||||
Δ.pkg = diff_packages(prev.lock, curr.lock)
|
||||
Δ.layers= diff_layers(prev.sbom, curr.sbom)
|
||||
Δ.funcs = diff_cfg(prev.cfgIndex, curr.cfgIndex)
|
||||
|
||||
scope = union(
|
||||
impact_of(Δ.pkg.changed),
|
||||
impact_of_files(Δ.layers.changed),
|
||||
reachability_of(Δ.funcs.changed)
|
||||
)
|
||||
|
||||
for f in scope.functions:
|
||||
rescore(f)
|
||||
|
||||
for v in impacted_vulns(scope):
|
||||
annotate(v, patch_delta(Δ))
|
||||
link_evidence(v, dsse_attestation(), proof_links())
|
||||
|
||||
for v in previously_flagged where vulnerable_apis_now_absent(v, curr):
|
||||
emit_vex_candidate(v, status="not_affected", rationale="API not present", evidence=proof_links())
|
||||
```
|
||||
|
||||
## 5. MATERIAL RISK CHANGE DETECTION RULES
|
||||
|
||||
**FindingKey:**
|
||||
```
|
||||
FindingKey = (component_purl, component_version, cve_id)
|
||||
```
|
||||
|
||||
**RiskState Fields:**
|
||||
- `reachable: bool | unknown`
|
||||
- `vex_status: enum` (AFFECTED | NOT_AFFECTED | FIXED | UNDER_INVESTIGATION | UNKNOWN)
|
||||
- `in_affected_range: bool | unknown`
|
||||
- `kev: bool`
|
||||
- `epss_score: float | null`
|
||||
- `policy_flags: set<string>`
|
||||
- `evidence_links: list<EvidenceLink>`
|
||||
|
||||
**Rule R1: Reachability Flip**
|
||||
- `reachable` changes: `false → true` (risk ↑) or `true → false` (risk ↓)
|
||||
|
||||
**Rule R2: VEX Status Flip**
|
||||
- Meaningful changes: `AFFECTED ↔ NOT_AFFECTED`, `UNDER_INVESTIGATION → NOT_AFFECTED`
|
||||
|
||||
**Rule R3: Affected Range Boundary**
|
||||
- `in_affected_range` flips: `false → true` or `true → false`
|
||||
|
||||
**Rule R4: Intelligence/Policy Flip**
|
||||
- `kev` changes `false → true`
|
||||
- `epss_score` crosses configured threshold
|
||||
- `policy_flag` changes severity (warn → block)
|
||||
|
||||
## 6. SUPPRESSION RULES
|
||||
|
||||
**Suppression Conditions (ALL must apply):**
|
||||
1. `reachable == false`
|
||||
2. `vex_status == NOT_AFFECTED`
|
||||
3. `kev == false`
|
||||
4. No policy override
|
||||
|
||||
**Patch Churn Suppression:**
|
||||
- If version changes AND `in_affected_range` remains false in both AND no KEV/policy flip → suppress
|
||||
|
||||
## 7. CALL-STACK ANALYSIS
|
||||
|
||||
**C# Roslyn Skeleton:**
|
||||
```csharp
|
||||
public static class SmartDiff
|
||||
{
|
||||
public static async Task<HashSet<string>> ReachableSinks(string solutionPath, string[] entrypoints, string[] sinks)
|
||||
{
|
||||
var workspace = MSBuild.MSBuildWorkspace.Create();
|
||||
var solution = await workspace.OpenSolutionAsync(solutionPath);
|
||||
var index = new HashSet<string>();
|
||||
|
||||
foreach (var proj in solution.Projects)
|
||||
{
|
||||
var comp = await proj.GetCompilationAsync();
|
||||
if (comp is null) continue;
|
||||
|
||||
var epSymbols = comp.GlobalNamespace.GetMembers().SelectMany(Descend)
|
||||
.OfType<IMethodSymbol>().Where(m => entrypoints.Contains(m.ToDisplayString())).ToList();
|
||||
var sinkSymbols = comp.GlobalNamespace.GetMembers().SelectMany(Descend)
|
||||
.OfType<IMethodSymbol>().Where(m => sinks.Contains(m.ToDisplayString())).ToList();
|
||||
|
||||
foreach (var ep in epSymbols)
|
||||
foreach (var sink in sinkSymbols)
|
||||
{
|
||||
var refs = await SymbolFinder.FindReferencesAsync(sink, solution);
|
||||
if (refs.SelectMany(r => r.Locations).Any())
|
||||
index.Add($"{ep.ToDisplayString()} -> {sink.ToDisplayString()}");
|
||||
}
|
||||
}
|
||||
return index;
|
||||
|
||||
static IEnumerable<ISymbol> Descend(INamespaceOrTypeSymbol sym)
|
||||
{
|
||||
foreach (var m in sym.GetMembers())
|
||||
{
|
||||
yield return m;
|
||||
if (m is INamespaceOrTypeSymbol nt)
|
||||
foreach (var x in Descend(nt)) yield return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Go SSA Skeleton:**
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/tools/go/callgraph/cha"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := &packages.Config{Mode: packages.LoadAllSyntax, Tests: false}
|
||||
pkgs, _ := packages.Load(cfg, "./...")
|
||||
prog, pkgsSSA := ssa.NewProgram(pkgs[0].Fset, ssa.BuilderMode(0))
|
||||
for _, p := range pkgsSSA { prog.CreatePackage(p, p.Syntax, p.TypesInfo, true) }
|
||||
prog.Build()
|
||||
|
||||
cg := cha.CallGraph(prog)
|
||||
fmt.Println("nodes:", len(cg.Nodes))
|
||||
}
|
||||
```
|
||||
|
||||
## 8. SINK TAXONOMY
|
||||
|
||||
```yaml
|
||||
sinks:
|
||||
- CMD_EXEC
|
||||
- UNSAFE_DESER
|
||||
- SQL_RAW
|
||||
- SSRF
|
||||
- FILE_WRITE
|
||||
- PATH_TRAVERSAL
|
||||
- TEMPLATE_INJECTION
|
||||
- CRYPTO_WEAK
|
||||
- AUTHZ_BYPASS
|
||||
```
|
||||
|
||||
## 9. POLICY SCORING FORMULA
|
||||
|
||||
**Priority Score:**
|
||||
```
|
||||
score =
|
||||
+ 1000 if new.kev
|
||||
+ 500 if new.reachable
|
||||
+ 200 if reason includes RANGE_FLIP to affected
|
||||
+ 150 if VEX_FLIP to AFFECTED
|
||||
+ 0..100 based on EPSS (epss * 100)
|
||||
+ policy weight: +300 if decision BLOCK, +100 if WARN
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,455 @@
|
||||
# Testing and Quality Guardrails Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 29-Nov-2025 - Acceptance Tests Pack and Guardrails
|
||||
- 29-Nov-2025 - SCA Failure Catalogue for StellaOps Tests
|
||||
- 30-Nov-2025 - Ecosystem Reality Test Cases for StellaOps
|
||||
- 14-Dec-2025 - Create a small ground‑truth corpus
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. ACCEPTANCE TEST PACK SCHEMA
|
||||
|
||||
### 1.1 Required Artifacts (MVP for DONE)
|
||||
|
||||
- Advisory summary under `docs/process/`
|
||||
- Checklist stub referencing AT1–AT10
|
||||
- Fixture pack path: `tests/acceptance/packs/guardrails/` (no network)
|
||||
- Links into sprint tracker (`SPRINT_0300_0001_0001_documentation_process.md`)
|
||||
|
||||
### 1.2 Determinism & Offline
|
||||
|
||||
- Freeze scanner/db versions; record in `inputs.lock`
|
||||
- All fixtures reproducible from seeds
|
||||
- Include DSSE envelopes for pack manifests
|
||||
|
||||
## 2. SCA FAILURE CATALOGUE (FC1-FC10)
|
||||
|
||||
### 2.1 Required Artifacts
|
||||
|
||||
- Catalogue plus fixture pack root: `tests/fixtures/sca/catalogue/`
|
||||
- Sprint Execution Log entry when published
|
||||
|
||||
### 2.2 Fixture Requirements
|
||||
|
||||
- Pin scanner versions and feeds
|
||||
- Include `inputs.lock` and DSSE manifest per case
|
||||
- Normalize results (ordering, casing) for stable comparisons
|
||||
|
||||
## 3. ECOSYSTEM REALITY TEST CASES (ET1-ET10)
|
||||
|
||||
**Fixture Path**: `tests/fixtures/sca/catalogue/`
|
||||
|
||||
**Requirements**:
|
||||
- Map each incident to acceptance tests and fixture paths
|
||||
- Pin tool versions and feeds; no live network
|
||||
- Populate fixtures and acceptance specs
|
||||
|
||||
## 4. GROUND-TRUTH CORPUS SCHEMA
|
||||
|
||||
### 4.1 Service Structure
|
||||
|
||||
Each service under `/toys/svc-XX-<name>/`:
|
||||
|
||||
```
|
||||
app/
|
||||
infra/ # Dockerfile, compose, network policy
|
||||
tests/ # positive + negative reachability tests
|
||||
labels.yaml # ground truth
|
||||
evidence/ # generated by tests (trace, tags, manifests)
|
||||
fix/ # minimal patch proving remediation
|
||||
```
|
||||
|
||||
### 4.2 labels.yaml Schema
|
||||
|
||||
```yaml
|
||||
service: svc-01-password-reset
|
||||
vulns:
|
||||
- id: V1
|
||||
cve: CVE-2022-XXXXX
|
||||
type: dep_runtime|dep_build|code|config|os_pkg|supply_chain
|
||||
package: string
|
||||
version: string
|
||||
reachable: true|false
|
||||
reachability_level: R0|R1|R2|R3|R4
|
||||
entrypoint: string # route:/reset, topic:jobs, cli:command
|
||||
preconditions: [string] # flags/env/auth
|
||||
path_tags: [string]
|
||||
proof:
|
||||
artifacts: [string]
|
||||
tags: [string]
|
||||
fix:
|
||||
type: upgrade|config|code
|
||||
patch_path: string
|
||||
expected_delta: string
|
||||
negative_proof: string # if unreachable
|
||||
```
|
||||
|
||||
### 4.3 Reachability Tiers
|
||||
|
||||
- **R0 Present**: component exists in SBOM, not imported/loaded
|
||||
- **R1 Loaded**: imported/linked/initialized, no executed path
|
||||
- **R2 Executed**: vulnerable function executed (deterministic trace)
|
||||
- **R3 Tainted execution**: execution with externally influenced input
|
||||
- **R4 Exploitable**: controlled, non-harmful PoC (optional)
|
||||
|
||||
### 4.4 Evidence Requirements per Tier
|
||||
|
||||
- **R0**: SBOM + file hash/package metadata
|
||||
- **R1**: runtime startup logs or module load trace tag
|
||||
- **R2**: callsite tag + stack trace snippet
|
||||
- **R3**: R2 + taint marker showing external data reached call
|
||||
- **R4**: only if safe/necessary; non-weaponized, sandboxed
|
||||
|
||||
### 4.5 Canonical Tag Format
|
||||
|
||||
```
|
||||
TAG:route:<method> <path>
|
||||
TAG:topic:<name>
|
||||
TAG:call:<sink>
|
||||
TAG:taint:<boundary>
|
||||
TAG:flag:<name>=<value>
|
||||
```
|
||||
|
||||
### 4.6 Evidence Artifact Schema
|
||||
|
||||
**evidence/trace.json**:
|
||||
```json
|
||||
{
|
||||
"ts": "UTC ISO-8601",
|
||||
"corr": "correlation-id",
|
||||
"tags": ["TAG:route:POST /reset", "TAG:taint:http.body.email", "TAG:call:Crypto.MD5"]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.7 Evidence Manifest
|
||||
|
||||
**evidence/manifest.json**:
|
||||
```json
|
||||
{
|
||||
"git_sha": "string",
|
||||
"image_digest": "string",
|
||||
"tool_versions": {"scanner": "string", "db": "string"},
|
||||
"timestamps": {"started_at": "UTC ISO-8601", "completed_at": "UTC ISO-8601"},
|
||||
"evidence_hashes": {"trace.json": "sha256:...", "tags.log": "sha256:..."}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. CORE TEST METRICS
|
||||
|
||||
| Metric | Definition |
|
||||
|--------|------------|
|
||||
| Recall (by class) | % of labeled vulns detected (runtime deps, OS pkgs, code, config) |
|
||||
| Precision | 1 - false positive rate |
|
||||
| Reachability accuracy | % correct R0/R1/R2/R3 classifications |
|
||||
| Overreach | Predicted reachable but labeled R0/R1 |
|
||||
| Underreach | Labeled R2/R3 but predicted non-reachable |
|
||||
| TTFS | Time-to-first-signal (first evidence-backed blocking issue) |
|
||||
| Fix validation | % of applied fixes producing expected delta |
|
||||
|
||||
## 6. TEST QUALITY GATES (CI ENFORCEMENT THRESHOLDS)
|
||||
|
||||
```yaml
|
||||
thresholds:
|
||||
runtime_dependency_recall: >= 0.95
|
||||
unreachable_false_positives: <= 0.05
|
||||
reachability_underreport: <= 0.10
|
||||
ttfs_regression: <= +10% vs main
|
||||
fix_validation_pass_rate: 100%
|
||||
```
|
||||
|
||||
## 7. SERVICE DEFINITION OF DONE
|
||||
|
||||
A service PR is DONE only if it includes:
|
||||
|
||||
- [ ] `labels.yaml` validated by `schemas/labels.schema.json`
|
||||
- [ ] Docker build reproducible (digest pinned, lockfiles committed)
|
||||
- [ ] Positive tests generating evidence proving reachability tiers
|
||||
- [ ] Negative tests proving "unreachable" claims
|
||||
- [ ] `fix/` patch removing/mitigating weakness with measurable delta
|
||||
- [ ] `evidence/manifest.json` capturing tool versions, git sha, image digest, timestamps, evidence hashes
|
||||
|
||||
## 8. REVIEWER REJECTION CRITERIA
|
||||
|
||||
Reject PR if any fail:
|
||||
|
||||
- [ ] Labels complete, schema-valid, stable IDs preserved
|
||||
- [ ] Proof artifacts deterministic and generated by tests
|
||||
- [ ] Reachability tier justified and matches evidence
|
||||
- [ ] Unreachable claims have negative proofs
|
||||
- [ ] Docker build uses pinned digests + committed lockfiles
|
||||
- [ ] `fix/` produces measurable delta without new unlabeled issues
|
||||
- [ ] No network egress required; tests hermetic
|
||||
|
||||
## 9. TEST HARNESS PATTERNS
|
||||
|
||||
### 9.1 xUnit Test Template
|
||||
|
||||
```csharp
|
||||
public class ReachabilityAcceptanceTests : IClassFixture<PostgresFixture>
|
||||
{
|
||||
private readonly PostgresFixture _db;
|
||||
|
||||
public ReachabilityAcceptanceTests(PostgresFixture db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("svc-01-password-reset", "V1", ReachabilityLevel.R2)]
|
||||
[InlineData("svc-02-file-upload", "V1", ReachabilityLevel.R0)]
|
||||
public async Task VerifyReachabilityClassification(
|
||||
string serviceId,
|
||||
string vulnId,
|
||||
ReachabilityLevel expectedLevel)
|
||||
{
|
||||
// Arrange
|
||||
var labels = await LoadLabels($"toys/{serviceId}/labels.yaml");
|
||||
var expectedVuln = labels.Vulns.First(v => v.Id == vulnId);
|
||||
|
||||
// Act
|
||||
var result = await _scanner.ScanAsync(serviceId);
|
||||
var actualVuln = result.Findings.First(f => f.VulnId == vulnId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedLevel, actualVuln.ReachabilityLevel);
|
||||
Assert.NotEmpty(actualVuln.Evidence);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Testcontainers Pattern
|
||||
|
||||
```csharp
|
||||
public class PostgresFixture : IAsyncLifetime
|
||||
{
|
||||
private PostgreSqlContainer? _container;
|
||||
public string ConnectionString { get; private set; } = null!;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_container = new PostgreSqlBuilder()
|
||||
.WithImage("postgres:16-alpine")
|
||||
.WithDatabase("stellaops_test")
|
||||
.WithUsername("test")
|
||||
.WithPassword("test")
|
||||
.Build();
|
||||
|
||||
await _container.StartAsync();
|
||||
ConnectionString = _container.GetConnectionString();
|
||||
|
||||
// Run migrations
|
||||
await RunMigrations(ConnectionString);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (_container != null)
|
||||
await _container.DisposeAsync();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 10. FIXTURE ORGANIZATION
|
||||
|
||||
```
|
||||
tests/
|
||||
fixtures/
|
||||
sca/
|
||||
catalogue/
|
||||
FC001_openssl_version_range/
|
||||
inputs.lock
|
||||
sbom.cdx.json
|
||||
expected_findings.json
|
||||
dsse_manifest.json
|
||||
acceptance/
|
||||
packs/
|
||||
guardrails/
|
||||
AT001_reachability_present/
|
||||
AT002_reachability_loaded/
|
||||
AT003_reachability_executed/
|
||||
micro/
|
||||
motion/
|
||||
error/
|
||||
offline/
|
||||
toys/
|
||||
svc-01-password-reset/
|
||||
app/
|
||||
infra/
|
||||
tests/
|
||||
labels.yaml
|
||||
evidence/
|
||||
fix/
|
||||
```
|
||||
|
||||
## 11. DETERMINISTIC TEST REQUIREMENTS
|
||||
|
||||
### 11.1 Time Handling
|
||||
|
||||
- Freeze timers to `2025-12-04T12:00:00Z` in stories/e2e
|
||||
- Use `FakeTimeProvider` in .NET tests
|
||||
- Playwright: `useFakeTimers`
|
||||
|
||||
### 11.2 Random Number Generation
|
||||
|
||||
- Seed RNG with `0x5EED2025` unless scenario-specific
|
||||
- Never use `Random()` without explicit seed
|
||||
|
||||
### 11.3 Network Isolation
|
||||
|
||||
- No network calls in test execution
|
||||
- Offline assets bundled
|
||||
- Testcontainers for external dependencies
|
||||
- Mock external APIs
|
||||
|
||||
### 11.4 Snapshot Testing
|
||||
|
||||
- All fixtures stored under `tests/fixtures/`
|
||||
- Golden outputs checked into git
|
||||
- Stable ordering for arrays/objects
|
||||
- Strip volatile fields (timestamps, UUIDs) unless semantic
|
||||
|
||||
## 12. COVERAGE REQUIREMENTS
|
||||
|
||||
### 12.1 Unit Tests
|
||||
|
||||
- **Target**: ≥85% line coverage for core modules
|
||||
- **Critical paths**: 100% coverage required
|
||||
- **Exceptions**: UI glue code, generated code
|
||||
|
||||
### 12.2 Integration Tests
|
||||
|
||||
- **Database operations**: All repositories tested with Testcontainers
|
||||
- **API endpoints**: All endpoints tested with WebApplicationFactory
|
||||
- **External integrations**: Mocked or stubbed
|
||||
|
||||
### 12.3 End-to-End Tests
|
||||
|
||||
- **Critical workflows**: User registration → scan → triage → decision
|
||||
- **Happy paths**: All major features
|
||||
- **Error paths**: Authentication failures, network errors, data validation
|
||||
|
||||
## 13. PERFORMANCE TESTING
|
||||
|
||||
### 13.1 Benchmark Tests
|
||||
|
||||
```csharp
|
||||
[MemoryDiagnoser]
|
||||
public class ScannerBenchmarks
|
||||
{
|
||||
[Benchmark]
|
||||
public async Task ScanMediumImage()
|
||||
{
|
||||
// 100k LOC .NET service
|
||||
await _scanner.ScanAsync("medium-service");
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ComputeReachability()
|
||||
{
|
||||
await _reachability.ComputeAsync(_testGraph);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13.2 Performance Targets
|
||||
|
||||
| Operation | Target |
|
||||
|-----------|--------|
|
||||
| Medium service scan | < 2 minutes |
|
||||
| Reachability compute | < 30 seconds |
|
||||
| Query GET finding | < 200ms p95 |
|
||||
| SBOM ingestion | < 5 seconds |
|
||||
|
||||
## 14. MUTATION TESTING
|
||||
|
||||
### 14.1 Stryker Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"stryker-config": {
|
||||
"mutate": [
|
||||
"src/**/*.cs",
|
||||
"!src/**/*.Designer.cs",
|
||||
"!src/**/Migrations/**"
|
||||
],
|
||||
"test-runner": "dotnet",
|
||||
"threshold-high": 90,
|
||||
"threshold-low": 70,
|
||||
"threshold-break": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 14.2 Mutation Score Targets
|
||||
|
||||
- **Critical modules**: ≥90%
|
||||
- **Standard modules**: ≥70%
|
||||
- **Break build**: <60%
|
||||
|
||||
## 15. SECURITY TESTING
|
||||
|
||||
### 15.1 OWASP Top 10 Coverage
|
||||
|
||||
- [ ] SQL Injection
|
||||
- [ ] XSS (Cross-Site Scripting)
|
||||
- [ ] CSRF (Cross-Site Request Forgery)
|
||||
- [ ] Authentication bypasses
|
||||
- [ ] Authorization bypasses
|
||||
- [ ] Sensitive data exposure
|
||||
- [ ] XML External Entities (XXE)
|
||||
- [ ] Broken Access Control
|
||||
- [ ] Security Misconfiguration
|
||||
- [ ] Insecure Deserialization
|
||||
|
||||
### 15.2 Dependency Scanning
|
||||
|
||||
```bash
|
||||
# SBOM generation
|
||||
dotnet sbom-tool generate -b ./bin -bc ./src -pn StellaOps -pv 1.0.0
|
||||
|
||||
# Vulnerability scanning
|
||||
dotnet list package --vulnerable --include-transitive
|
||||
```
|
||||
|
||||
## 16. CI/CD INTEGRATION
|
||||
|
||||
### 16.1 GitHub Actions Workflow
|
||||
|
||||
```yaml
|
||||
name: Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.0.x'
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal --collect:"XPlat Code Coverage"
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v4
|
||||
```
|
||||
|
||||
### 16.2 Quality Gates
|
||||
|
||||
- All tests pass
|
||||
- Coverage ≥85%
|
||||
- No high/critical vulnerabilities
|
||||
- Mutation score ≥70%
|
||||
- Performance regressions <10%
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,390 @@
|
||||
# Triage and Unknowns Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 30-Nov-2025 - Unknowns Decay & Triage Heuristics
|
||||
- 14-Dec-2025 - Dissect triage and evidence workflows
|
||||
- 04-Dec-2025 - Ranking Unknowns in Reachability Graphs
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. EVIDENCE-FIRST PRINCIPLES
|
||||
|
||||
1. **Evidence before detail**: Opening alert shows best available evidence immediately
|
||||
2. **Fast first signal**: UI renders credible "first signal" quickly
|
||||
3. **Determinism reduces hesitation**: Sorting, graphs, diffs stable across refreshes
|
||||
4. **Offline by design**: If evidence exists locally, render without network
|
||||
5. **Audit-ready by default**: Every decision reproducible, attributable, exportable
|
||||
|
||||
## 2. MINIMAL EVIDENCE BUNDLE (PER FINDING)
|
||||
|
||||
1. **Reachability proof**: Function-level path or package-level import chain
|
||||
2. **Call-stack snippet**: 5–10 frames around sink/source with file:line anchors
|
||||
3. **Provenance**: Attestation/DSSE + build ancestry (image → layer → artifact → commit)
|
||||
4. **VEX/CSAF status**: affected/not-affected/under-investigation + reason
|
||||
5. **Diff**: SBOM or VEX delta since last scan (smart-diff)
|
||||
|
||||
## 3. KPIS
|
||||
|
||||
### 3.1 TTFS (Time-to-First-Signal)
|
||||
|
||||
**Definition**: p50/p95 from alert creation to first rendered evidence
|
||||
|
||||
**Target**: p95 < 1.5s (with 100ms RTT, 1% loss)
|
||||
|
||||
### 3.2 Clicks-to-Closure
|
||||
|
||||
**Definition**: Median interactions per decision type
|
||||
|
||||
**Target**: median < 6 clicks
|
||||
|
||||
### 3.3 Evidence Completeness Score
|
||||
|
||||
**Definition**: 0–4 (reachability, call-stack, provenance, VEX present)
|
||||
|
||||
**Target**: ≥90% of decisions include all evidence + reason + replay token
|
||||
|
||||
### 3.4 Offline Friendliness
|
||||
|
||||
**Definition**: % evidence resolvable with no network
|
||||
|
||||
**Target**: ≥95% with local bundle
|
||||
|
||||
### 3.5 Audit Log Completeness
|
||||
|
||||
**Requirement**: Every decision has evidence hash set, actor, policy context, replay token
|
||||
|
||||
## 4. KEYBOARD SHORTCUTS
|
||||
|
||||
- `J`: Jump to first incomplete evidence pane
|
||||
- `Y`: Copy DSSE (attestation block or Rekor entry ref)
|
||||
- `R`: Toggle reachability view (path list ↔ compact graph ↔ textual proof)
|
||||
- `/`: Search within graph (node/func/package)
|
||||
- `S`: Deterministic sort (reachability→severity→age→component)
|
||||
- `A`, `N`, `U`: Quick VEX set (Affected / Not-affected / Under-investigation)
|
||||
- `?`: Keyboard help overlay
|
||||
|
||||
## 5. UX FLOW
|
||||
|
||||
### 5.1 Alert Row
|
||||
|
||||
- TTFS timer
|
||||
- Reachability badge
|
||||
- Decision state
|
||||
- Diff-dot
|
||||
|
||||
### 5.2 Open Alert → Evidence Tab (Not Details)
|
||||
|
||||
**Top strip**: 3 proof pills (Reachability ✓ / Call-stack ✓ / Provenance ✓)
|
||||
|
||||
Click to expand inline
|
||||
|
||||
### 5.3 Decision Drawer (Pinned Right)
|
||||
|
||||
- VEX/CSAF radio (A/N/U)
|
||||
- Reason presets → "Record decision"
|
||||
- Audit-ready summary (hashes, timestamps, policy)
|
||||
|
||||
### 5.4 Diff Tab
|
||||
|
||||
SBOM/VEX delta, grouped by "meaningful risk shift"
|
||||
|
||||
### 5.5 Activity Tab
|
||||
|
||||
Immutable audit log; export as signed bundle
|
||||
|
||||
## 6. GRAPH PERFORMANCE (LARGE CALL GRAPHS)
|
||||
|
||||
### 6.1 Minimal-Latency Snapshots
|
||||
|
||||
- Pre-render static PNG/SVG thumbnails server-side
|
||||
|
||||
### 6.2 Progressive Neighborhood Expansion
|
||||
|
||||
- Load 1-hop first, expand on demand
|
||||
- First TTFS < 500ms
|
||||
|
||||
### 6.3 Stable Node Ordering
|
||||
|
||||
- Deterministic layout with consistent anchors
|
||||
|
||||
### 6.4 Chunked Graph Edges
|
||||
|
||||
- Capped fan-out
|
||||
- Collapse identical library paths into reachability macro-edge
|
||||
|
||||
**Targets**:
|
||||
- Preview < 300ms
|
||||
- Interactive hydration < 2.0s for large graphs
|
||||
|
||||
## 7. OFFLINE DESIGN
|
||||
|
||||
### 7.1 Local Evidence Cache
|
||||
|
||||
Store (SBOM slices, path proofs, DSSE attestations, compiled call-stacks) in signed bundle beside SARIF/VEX
|
||||
|
||||
### 7.2 Deferred Enrichment
|
||||
|
||||
Mark fields needing internet; queue background "enricher" when network returns
|
||||
|
||||
### 7.3 Predictable Fallbacks
|
||||
|
||||
Show embedded DSSE + "verification pending" if provenance server missing
|
||||
|
||||
## 8. AUDIT & REPLAY
|
||||
|
||||
### 8.1 Deterministic Replay Token
|
||||
|
||||
```
|
||||
replay_token = hash(feed_manifests + rules + lattice_policy + inputs)
|
||||
```
|
||||
|
||||
### 8.2 One-Click "Reproduce"
|
||||
|
||||
CLI snippet pinned to exact versions and policies
|
||||
|
||||
### 8.3 Evidence Hash-Set
|
||||
|
||||
Content-address each proof artifact; audit entry stores hashes + signer
|
||||
|
||||
## 9. TELEMETRY IMPLEMENTATION
|
||||
|
||||
```typescript
|
||||
ttfs.start → (alert creation)
|
||||
ttfs.signal → (first evidence card paint)
|
||||
close.clicks → (decision recorded)
|
||||
```
|
||||
|
||||
Log evidence bitset (reach, stack, prov, vex) at decision time
|
||||
|
||||
## 10. API REQUIREMENTS
|
||||
|
||||
### 10.1 Endpoints
|
||||
|
||||
```
|
||||
GET /alerts?filters… → list view
|
||||
GET /alerts/{id}/evidence → evidence payload (reachability, call stack, provenance, hashes)
|
||||
POST /alerts/{id}/decisions → record decision event (append-only)
|
||||
GET /alerts/{id}/audit → audit timeline
|
||||
GET /alerts/{id}/diff?baseline=… → SBOM/VEX diff
|
||||
GET /bundles/{id}, POST /bundles/verify → offline bundle download/verify
|
||||
```
|
||||
|
||||
### 10.2 Evidence Payload Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"alert_id": "a123",
|
||||
"reachability": { "status": "available|loading|unavailable|error", "hash": "sha256:…", "proof": {...} },
|
||||
"callstack": { "status": "...", "hash": "...", "frames": [...] },
|
||||
"provenance": { "status": "...", "hash": "...", "dsse": {...} },
|
||||
"vex": { "status": "...", "current": {...}, "history": [...] },
|
||||
"hashes": ["sha256:…", ...]
|
||||
}
|
||||
```
|
||||
|
||||
**Guidelines**:
|
||||
- Deterministic ordering for arrays and nodes
|
||||
- Explicit `status` per evidence section
|
||||
- Include `hash` per artifact
|
||||
|
||||
## 11. DECISION EVENT SCHEMA
|
||||
|
||||
Store per decision:
|
||||
|
||||
- `alert_id`, `artifact_id` (image digest/commit hash)
|
||||
- `actor_id`, `timestamp`
|
||||
- `decision_status` (Affected/Not affected/Under investigation)
|
||||
- `reason_code` (preset) + `reason_text`
|
||||
- `evidence_hashes[]` (content-addressed)
|
||||
- `policy_context` (ruleset version, policy id)
|
||||
- `replay_token` (hash of inputs)
|
||||
|
||||
## 12. OFFLINE BUNDLE FORMAT
|
||||
|
||||
Single file (`.stella.bundle.tgz`) containing:
|
||||
|
||||
- Alert metadata snapshot
|
||||
- Evidence artifacts (reachability proofs, call stacks, provenance attestations)
|
||||
- SBOM slice(s) for diffs
|
||||
- VEX decision history
|
||||
- Manifest with content hashes
|
||||
- **Must be signed and verifiable**
|
||||
|
||||
## 13. PERFORMANCE BUDGETS
|
||||
|
||||
- **TTFS**: <200ms skeleton, <500ms first evidence pill, <1.5s p95 full evidence
|
||||
- **Graph**: Preview <300ms, interactive <2.0s
|
||||
- **Interaction response**: ≤100ms
|
||||
- **Animation frame budget**: 16ms avg / 50ms p95
|
||||
- **Keyboard coverage**: ≥90% of triage actions
|
||||
- **Offline replay**: 100% of decisions re-render from bundle
|
||||
|
||||
## 14. ERROR HANDLING
|
||||
|
||||
Never show empty states without explanation. Distinguish:
|
||||
|
||||
- "not computed yet"
|
||||
- "not possible due to missing inputs"
|
||||
- "blocked by permissions"
|
||||
- "offline—enrichment pending"
|
||||
- "verification failed"
|
||||
|
||||
## 15. RBAC
|
||||
|
||||
Gate:
|
||||
- Viewing provenance attestations
|
||||
- Recording decisions
|
||||
- Exporting audit bundles
|
||||
|
||||
All decision events immutable; corrections are new events (append-only)
|
||||
|
||||
## 16. UNKNOWNS DECAY & TRIAGE HEURISTICS
|
||||
|
||||
### 16.1 Problem
|
||||
|
||||
Stale "unknown" findings create noise; need deterministic decay and triage rules
|
||||
|
||||
### 16.2 Requirements
|
||||
|
||||
- Confidence decay card
|
||||
- Triage queue UI
|
||||
- Export artifacts for planning
|
||||
|
||||
### 16.3 Determinism
|
||||
|
||||
- Decay windows and thresholds must be deterministic
|
||||
- Exports reproducible without live dependencies
|
||||
|
||||
### 16.4 Decay Logic
|
||||
|
||||
**Decay Windows**: Define time-based decay windows
|
||||
|
||||
**Thresholds**: Set confidence thresholds for promotion/demotion
|
||||
|
||||
**UI/Export Snapshot Expectations**: Deterministic decay logic description
|
||||
|
||||
## 17. UNKNOWNS RANKING ALGORITHM
|
||||
|
||||
### 17.1 Score Formula
|
||||
|
||||
```
|
||||
Score = clamp01(
|
||||
wP·P + # Popularity impact
|
||||
wE·E + # Exploit consequence potential
|
||||
wU·U + # Uncertainty density
|
||||
wC·C + # Graph centrality
|
||||
wS·S # Evidence staleness
|
||||
)
|
||||
```
|
||||
|
||||
### 17.2 Default Weights
|
||||
|
||||
```
|
||||
wP = 0.25 (deployment impact)
|
||||
wE = 0.25 (potential consequence)
|
||||
wU = 0.25 (uncertainty density)
|
||||
wC = 0.15 (graph centrality)
|
||||
wS = 0.10 (evidence staleness)
|
||||
```
|
||||
|
||||
### 17.3 Heuristics
|
||||
|
||||
```
|
||||
P = min(1, log10(1 + deployments)/log10(1 + 100))
|
||||
U = sum of flags, capped at 1.0:
|
||||
+0.30 if no provenance anchor
|
||||
+0.25 if version_range
|
||||
+0.20 if conflicting_feeds
|
||||
+0.15 if missing_vector
|
||||
+0.10 if unreachable source advisory
|
||||
S = min(1, age_days / 14)
|
||||
```
|
||||
|
||||
### 17.4 Band Assignment
|
||||
|
||||
```
|
||||
Score ≥ 0.70 → HOT (immediate rescan + VEX escalation)
|
||||
0.40 ≤ Score < 0.70 → WARM (scheduled rescan 12-72h)
|
||||
Score < 0.40 → COLD (weekly batch)
|
||||
```
|
||||
|
||||
## 18. UNKNOWNS DATABASE SCHEMA
|
||||
|
||||
```sql
|
||||
CREATE TABLE unknowns (
|
||||
unknown_id uuid PRIMARY KEY,
|
||||
pkg_id text,
|
||||
pkg_version text,
|
||||
digest_anchor bytea,
|
||||
unknown_flags jsonb,
|
||||
popularity_p float,
|
||||
potential_e float,
|
||||
uncertainty_u float,
|
||||
centrality_c float,
|
||||
staleness_s float,
|
||||
score float,
|
||||
band text CHECK(band IN ('HOT','WARM','COLD')),
|
||||
graph_slice_hash bytea,
|
||||
evidence_set_hash bytea,
|
||||
normalization_trace jsonb,
|
||||
callgraph_attempt_hash bytea,
|
||||
created_at timestamptz,
|
||||
updated_at timestamptz
|
||||
);
|
||||
|
||||
CREATE TABLE deploy_refs (
|
||||
pkg_id text,
|
||||
image_id text,
|
||||
env text,
|
||||
first_seen timestamptz,
|
||||
last_seen timestamptz
|
||||
);
|
||||
|
||||
CREATE TABLE graph_metrics (
|
||||
pkg_id text PRIMARY KEY,
|
||||
degree_c float,
|
||||
betweenness_c float,
|
||||
last_calc_at timestamptz
|
||||
);
|
||||
```
|
||||
|
||||
## 19. TRIAGE QUEUE UI
|
||||
|
||||
### 19.1 Queue Views
|
||||
|
||||
- **HOT**: Red badge, sort by score desc
|
||||
- **WARM**: Yellow badge, sort by score desc
|
||||
- **COLD**: Gray badge, sort by age asc
|
||||
|
||||
### 19.2 Bulk Actions
|
||||
|
||||
- Mark as reviewed
|
||||
- Escalate to HOT
|
||||
- Suppress (with reason)
|
||||
- Export selected
|
||||
|
||||
### 19.3 Filters
|
||||
|
||||
- By band
|
||||
- By package
|
||||
- By environment
|
||||
- By date range
|
||||
|
||||
## 20. DECISION WORKFLOW CHECKLIST
|
||||
|
||||
For any triage decision:
|
||||
|
||||
- [ ] Evidence reviewed (reachability, call-stack, provenance, VEX)
|
||||
- [ ] Decision status selected (A/N/U)
|
||||
- [ ] Reason provided (preset or custom)
|
||||
- [ ] Replay token generated
|
||||
- [ ] Evidence hashes captured
|
||||
- [ ] Audit event recorded
|
||||
- [ ] Decision immutable (append-only)
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
@@ -0,0 +1,723 @@
|
||||
# UX and Time-to-Evidence Technical Reference
|
||||
|
||||
**Source Advisories**:
|
||||
- 01-Dec-2025 - Tracking UX Health with Time‑to‑Evidence
|
||||
- 12-Dec-2025 - Measure UX Efficiency Through TTFS
|
||||
- 13-Dec-2025 - Define a north star metric for TTFS
|
||||
- 14-Dec-2025 - Add a dedicated "first_signal" event
|
||||
- 04-Dec-2025 - Designing Traceable Evidence in Security UX
|
||||
- 05-Dec-2025 - Designing Triage UX That Stays Quiet on Purpose
|
||||
- 30-Nov-2025 - UI Micro-Interactions for StellaOps
|
||||
- 11-Dec-2025 - Stella DevOps UX Implementation Guide
|
||||
|
||||
**Last Updated**: 2025-12-14
|
||||
|
||||
---
|
||||
|
||||
## 1. PERFORMANCE TARGETS & SLOS
|
||||
|
||||
### 1.1 Time-to-Evidence (TTE)
|
||||
|
||||
**Definition**: `TTE = t_first_proof_rendered − t_open_finding`
|
||||
|
||||
**Primary SLO**: P95 ≤ 15s (stretch: P99 ≤ 30s)
|
||||
|
||||
**Guardrail**: P50 < 3s
|
||||
|
||||
**By proof type**:
|
||||
- Simple proof (SBOM row): P95 ≤ 5s
|
||||
- Complex proof (reachability graph): P95 ≤ 15s
|
||||
|
||||
**Backend budget**: 12s backend + 3s UI/render margin = 15s P95
|
||||
|
||||
**Query performance**: O(log n) on indexed columns
|
||||
|
||||
### 1.2 Time-to-First-Signal (TTFS)
|
||||
|
||||
**Definition**: Time from user action/CI start → first meaningful signal rendered/logged
|
||||
|
||||
**Primary SLO**: P50 < 2s, P95 < 5s (all surfaces: UI, CLI, CI)
|
||||
|
||||
**Warm path**: P50 < 700ms, P95 < 2500ms
|
||||
|
||||
**Cold path**: P95 ≤ 4000ms
|
||||
|
||||
**Component budgets**:
|
||||
- Frontend: ≤150ms (skeleton + last known state)
|
||||
- Edge/API: ≤250ms (signal frame fast path from cache)
|
||||
- Core services: ≤500–1500ms (pre-indexed failures, warm summaries)
|
||||
- Slow work: async (scan, lattice merge, provenance)
|
||||
|
||||
### 1.3 General UX Performance
|
||||
|
||||
- **Interaction response**: ≤100ms
|
||||
- **Animation frame budget**: 16ms avg / 50ms P95
|
||||
- **LCP placeholder**: shown immediately
|
||||
- **Layout shift**: <0.05
|
||||
- **Motion durations**: 80/140/200/260/320ms
|
||||
- **Reduced-motion**: 0-80ms clamp
|
||||
|
||||
### 1.4 Cache Performance
|
||||
|
||||
- **Cache-hit response**: P95 ≤ 250ms
|
||||
- **Cold response**: P95 ≤ 500ms
|
||||
- **Endpoint error rate**: < 0.1% under expected concurrency
|
||||
|
||||
## 2. METRICS DEFINITIONS & FORMULAS
|
||||
|
||||
### 2.1 TTE Metrics
|
||||
|
||||
```typescript
|
||||
// Core TTE calculation
|
||||
tte_ms = proof_rendered.timestamp - finding_open.timestamp
|
||||
|
||||
// Dimensions
|
||||
{
|
||||
tenant: string,
|
||||
finding_id: string,
|
||||
proof_kind: 'sbom' | 'reachability' | 'vex',
|
||||
source: 'local' | 'remote' | 'cache',
|
||||
page: string
|
||||
}
|
||||
```
|
||||
|
||||
**SQL Rollup (hourly)**:
|
||||
```sql
|
||||
SELECT
|
||||
proof_kind,
|
||||
percentile_cont(0.95) WITHIN GROUP (ORDER BY tte_ms) AS p95_ms
|
||||
FROM tte_events
|
||||
WHERE ts >= now() - interval '1 hour'
|
||||
GROUP BY proof_kind;
|
||||
```
|
||||
|
||||
### 2.2 TTFS Metrics
|
||||
|
||||
```typescript
|
||||
// Core TTFS calculation
|
||||
ttfs_ms = signal_rendered.timestamp - start.timestamp
|
||||
|
||||
// Dimensions
|
||||
{
|
||||
surface: 'ui' | 'cli' | 'ci',
|
||||
cache_hit: boolean,
|
||||
signal_source: 'snapshot' | 'cold_start' | 'failure_index',
|
||||
kind: string,
|
||||
repo_size_bucket: string,
|
||||
provider: string,
|
||||
branch: string,
|
||||
run_type: 'PR' | 'main',
|
||||
network_state: string
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 Secondary Metrics
|
||||
|
||||
- **Open→Action time**: Time from opening run to first user action
|
||||
- **Bounce rate**: Close page within 10s without interaction
|
||||
- **MTTR proxy**: Time from failure to first rerun or fix commit
|
||||
- **Signal availability rate**: % of run views showing first signal within 3s
|
||||
- **Signal accuracy score**: Engineer confirms "helpful vs not" (sampled)
|
||||
- **Extractor failure rate**: Parsing errors / missing mappings / timeouts
|
||||
|
||||
### 2.4 DORA Metrics
|
||||
|
||||
- **Deployment Frequency**: Deploys per day/week
|
||||
- **Lead Time for Changes**: Commit → deployment completion
|
||||
- **Change Failure Rate**: Failed deployments / total deployments
|
||||
- **Time to Restore**: Incident start → resolution
|
||||
|
||||
### 2.5 Quality Metrics
|
||||
|
||||
- **Error budget burn**: Minutes over target per day
|
||||
- **Top regressions**: Last 7 days vs prior 7
|
||||
- **Extraction failure rate**: < 1% for sampled runs
|
||||
|
||||
## 3. EVENT SCHEMAS
|
||||
|
||||
### 3.1 TTE Events
|
||||
|
||||
**finding_open**:
|
||||
```typescript
|
||||
{
|
||||
event: 'finding_open',
|
||||
findingId: string,
|
||||
tenantId: string,
|
||||
userId: string,
|
||||
userRole: 'admin' | 'dev' | 'triager',
|
||||
entryPoint: 'list' | 'search' | 'notification' | 'deep_link',
|
||||
uiVersion: string,
|
||||
buildSha: string,
|
||||
t: number // performance.now()
|
||||
}
|
||||
```
|
||||
|
||||
**proof_rendered**:
|
||||
```typescript
|
||||
{
|
||||
event: 'proof_rendered',
|
||||
findingId: string,
|
||||
proofKind: 'sbom' | 'reachability' | 'vex' | 'logs' | 'other',
|
||||
source: 'local_cache' | 'backend_api' | '3rd_party',
|
||||
proofHeight: number, // pixel offset from top
|
||||
t: number // performance.now()
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 TTFS Events
|
||||
|
||||
**ttfs_start**:
|
||||
```typescript
|
||||
{
|
||||
event: 'ttfs_start',
|
||||
runId: string,
|
||||
surface: 'ui' | 'cli' | 'ci',
|
||||
provider: string,
|
||||
repo: string,
|
||||
branch: string,
|
||||
runType: 'PR' | 'main',
|
||||
device: string,
|
||||
release: string,
|
||||
networkState: string,
|
||||
t: number
|
||||
}
|
||||
```
|
||||
|
||||
**ttfs_signal_rendered**:
|
||||
```typescript
|
||||
{
|
||||
event: 'ttfs_signal_rendered',
|
||||
runId: string,
|
||||
surface: 'ui' | 'cli' | 'ci',
|
||||
cacheHit: boolean,
|
||||
signalSource: 'snapshot' | 'cold_start' | 'failure_index',
|
||||
kind: string,
|
||||
t: number
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 FirstSignal Event Contract
|
||||
|
||||
```typescript
|
||||
interface FirstSignal {
|
||||
version: '1.0',
|
||||
signalId: string,
|
||||
jobId: string,
|
||||
timestamp: string, // ISO-8601
|
||||
kind: 'queued' | 'started' | 'phase' | 'blocked' | 'failed' | 'succeeded' | 'canceled' | 'unavailable',
|
||||
phase: 'resolve' | 'fetch' | 'restore' | 'analyze' | 'policy' | 'report' | 'unknown',
|
||||
scope: {
|
||||
type: 'repo' | 'image' | 'artifact',
|
||||
id: string
|
||||
},
|
||||
summary: string,
|
||||
etaSeconds?: number,
|
||||
lastKnownOutcome?: {
|
||||
signatureId: string,
|
||||
errorCode: string,
|
||||
token: string,
|
||||
excerpt: string,
|
||||
confidence: 'low' | 'medium' | 'high',
|
||||
firstSeenAt: string,
|
||||
hitCount: number
|
||||
},
|
||||
nextActions?: Array<{
|
||||
type: 'open_logs' | 'open_job' | 'docs' | 'retry' | 'cli_command',
|
||||
label: string,
|
||||
target: string
|
||||
}>,
|
||||
diagnostics: {
|
||||
cacheHit: boolean,
|
||||
source: 'snapshot' | 'failure_index' | 'cold_start',
|
||||
correlationId: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 UI Telemetry Schema
|
||||
|
||||
**ui.micro.* events**:
|
||||
```typescript
|
||||
{
|
||||
version: string,
|
||||
tenant: string,
|
||||
surface: string,
|
||||
component: string,
|
||||
action: string,
|
||||
latency_ms: number,
|
||||
outcome: string,
|
||||
reduced_motion: boolean,
|
||||
offline_mode: boolean,
|
||||
error_code?: string
|
||||
}
|
||||
```
|
||||
*Schema location*: `docs/modules/ui/telemetry/ui-micro.schema.json`
|
||||
|
||||
## 4. API CONTRACTS
|
||||
|
||||
### 4.1 First Signal Endpoint
|
||||
|
||||
**GET** `/api/runs/{runId}/first-signal`
|
||||
|
||||
**Headers**:
|
||||
- `If-None-Match: W/"..."` (supported)
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"runId": "123",
|
||||
"firstSignal": {
|
||||
"type": "stage_failed",
|
||||
"stage": "build",
|
||||
"step": "dotnet restore",
|
||||
"message": "401 Unauthorized: token expired",
|
||||
"at": "2025-12-11T09:22:31Z",
|
||||
"artifact": {
|
||||
"kind": "log",
|
||||
"range": { "start": 1880, "end": 1896 }
|
||||
}
|
||||
},
|
||||
"summaryEtag": "W/\"a1b2c3\""
|
||||
}
|
||||
```
|
||||
|
||||
**Status codes**:
|
||||
- `200`: Full first signal object
|
||||
- `304`: Not modified
|
||||
- `404`: Run not found
|
||||
- `204`: Run exists but signal not available yet
|
||||
|
||||
**Response headers**:
|
||||
- `ETag`
|
||||
- `Cache-Control`
|
||||
- `X-Correlation-Id`
|
||||
- `Cache-Status: hit|miss|bypass`
|
||||
|
||||
### 4.2 Summary Endpoint
|
||||
|
||||
**GET** `/api/runs/{runId}/summary`
|
||||
|
||||
Returns: Status, first failing stage/job, timestamps, blocking policies, artifact counts
|
||||
|
||||
### 4.3 SSE Events Endpoint
|
||||
|
||||
**GET** `/api/runs/{runId}/events` (Server-Sent Events)
|
||||
|
||||
**Event payloads**:
|
||||
- `status` (kind+phase+message)
|
||||
- `hint` (token+errorCode+confidence)
|
||||
- `policy` (blocked + policyId)
|
||||
- `complete` (terminal)
|
||||
|
||||
## 5. FRONTEND PATTERNS & COMPONENT SPECIFICATIONS
|
||||
|
||||
### 5.1 UI Contract (Evidence First)
|
||||
|
||||
**Above the fold**:
|
||||
- Always show compact **Proof panel** first (not hidden behind tabs)
|
||||
- **Skeletons over spinners**: Reserve space; render partial proof as ready
|
||||
- **Plain text copy affordance**: "Copy SBOM line / path" button next to proof
|
||||
- **Defer non-proof widgets**: CVSS badges, remediation prose, charts load *after* proof
|
||||
- **Empty-state truth**: "No proof available yet" + loader for *that* proof type only
|
||||
|
||||
### 5.2 Progressive Rendering Pattern
|
||||
|
||||
**Immediate render**:
|
||||
1. Title, status badge, pipeline metadata (run id, commit, branch)
|
||||
2. Skeleton for details area
|
||||
|
||||
**First signal fetch**:
|
||||
3. Render `FirstSignalCard` immediately when available
|
||||
4. Fire telemetry event when card is in DOM and visible
|
||||
|
||||
**Lazy-load**:
|
||||
5. Stage graph
|
||||
6. Full logs viewer
|
||||
7. Artifacts list
|
||||
8. Security findings
|
||||
9. Trends, flaky tests, etc.
|
||||
|
||||
### 5.3 Component Specifications
|
||||
|
||||
#### FirstSignalCard Component
|
||||
- Standalone, minimal dependencies
|
||||
- Shows: summary + at least one next action button (Open job/logs)
|
||||
- Updates in-place on deltas from SSE
|
||||
- Falls back to polling when SSE fails
|
||||
|
||||
#### EvidencePanel Component
|
||||
```typescript
|
||||
interface EvidencePanel {
|
||||
tabs: ['SBOM', 'Reachability', 'VEX', 'Logs', 'Other'],
|
||||
firstProofType: ProofKind,
|
||||
copyEnabled: boolean,
|
||||
emptyStateMessage?: string
|
||||
}
|
||||
```
|
||||
|
||||
#### ProofSpine Component
|
||||
- Displays: hashes, Rekor link
|
||||
- Verification status: `Verified` | `Unverified` | `Failed verification` | `Expired/Outdated`
|
||||
- "Verify locally" copy button with exact commands
|
||||
|
||||
### 5.4 Prefetch Strategy
|
||||
|
||||
From runs list view:
|
||||
- Use `IntersectionObserver` to prefetch summaries/first signals for items in viewport
|
||||
- Store results in in-memory cache (`Map<runId, FirstSignal>`)
|
||||
- Respect ETag to avoid redundant payloads
|
||||
|
||||
## 6. TELEMETRY REQUIREMENTS
|
||||
|
||||
### 6.1 Client-Side Telemetry
|
||||
|
||||
**Frontend events**:
|
||||
```typescript
|
||||
// On route enter
|
||||
metrics.emit('finding_open', { findingId, t: performance.now() });
|
||||
|
||||
// When first proof node/line hits DOM
|
||||
metrics.emit('proof_rendered', { findingId, proofKind, t: performance.now() });
|
||||
```
|
||||
|
||||
**Sampling**:
|
||||
- **Staging**: 100%
|
||||
- **Production**: ≥25% of sessions (ideal: 100%)
|
||||
|
||||
**Clock handling**:
|
||||
- Use `performance.now()` for TTE (monotonic within tab)
|
||||
- Don't mix backend clocks into TTE calculation
|
||||
|
||||
### 6.2 Backend Telemetry
|
||||
|
||||
**Endpoint metrics**:
|
||||
- `signal_endpoint_latency_ms`
|
||||
- `signal_payload_bytes`
|
||||
- `signal_error_rate`
|
||||
|
||||
**Server-side timing logs** (debug-level):
|
||||
- Cache read time
|
||||
- DB read time
|
||||
- Cold path time
|
||||
|
||||
**Tracing**:
|
||||
- Correlation ID propagated in:
|
||||
- API response header
|
||||
- Worker logs
|
||||
- Events
|
||||
|
||||
### 6.3 Dashboard Requirements
|
||||
|
||||
**Core widgets**:
|
||||
1. TTE distribution (P50/P90/P95/P99) per day, split by proof_kind
|
||||
2. TTE by page/surface (list→detail, deep links, bookmarks)
|
||||
3. TTE by user segment (new vs power users, roles)
|
||||
4. Error budget: "Minutes over SLO per day"
|
||||
5. Correlation: TTE vs session length, TTE vs "clicked ignore/snooze"
|
||||
|
||||
**Operational panels**:
|
||||
- Update granularity: Real-time or ≤15 min
|
||||
- Retention: ≥90 days
|
||||
- Breakdowns: backend_region, build_version
|
||||
|
||||
**TTFS dashboards**:
|
||||
- By surface (ui/cli/ci)
|
||||
- Cache hit rate
|
||||
- Endpoint latency percentiles
|
||||
- Repo size bucket
|
||||
- Kind/phase
|
||||
|
||||
**Alerts**:
|
||||
- Page when `p95(ttfs_ms) > 5000` for 5 mins
|
||||
- Page when `signal_endpoint_error_rate > 1%`
|
||||
- Alert when **P95 TTE > 15s** for 15 minutes
|
||||
|
||||
## 7. DATABASE SCHEMAS
|
||||
|
||||
### 7.1 TTE Events Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE tte_events (
|
||||
id SERIAL PRIMARY KEY,
|
||||
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
tenant TEXT NOT NULL,
|
||||
finding_id TEXT NOT NULL,
|
||||
proof_kind TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
tte_ms INT NOT NULL,
|
||||
page TEXT,
|
||||
user_role TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX ON tte_events (ts DESC);
|
||||
CREATE INDEX ON tte_events (proof_kind, ts DESC);
|
||||
```
|
||||
|
||||
### 7.2 First Signal Snapshots
|
||||
|
||||
```sql
|
||||
CREATE TABLE first_signal_snapshots (
|
||||
job_id TEXT PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
kind TEXT NOT NULL,
|
||||
phase TEXT NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
eta_seconds INT NULL,
|
||||
payload_json JSONB NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX ON first_signal_snapshots (updated_at DESC);
|
||||
```
|
||||
|
||||
### 7.3 Failure Signatures
|
||||
|
||||
```sql
|
||||
CREATE TABLE failure_signatures (
|
||||
signature_id TEXT PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
scope_type TEXT NOT NULL,
|
||||
scope_id TEXT NOT NULL,
|
||||
toolchain_hash TEXT NOT NULL,
|
||||
error_code TEXT NULL,
|
||||
token TEXT NOT NULL,
|
||||
excerpt TEXT NULL,
|
||||
confidence TEXT NOT NULL,
|
||||
first_seen_at TIMESTAMPTZ NOT NULL,
|
||||
last_seen_at TIMESTAMPTZ NOT NULL,
|
||||
hit_count INT NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE INDEX ON failure_signatures (scope_type, scope_id, toolchain_hash);
|
||||
CREATE INDEX ON failure_signatures (token);
|
||||
```
|
||||
|
||||
## 8. MOTION & ANIMATION TOKENS
|
||||
|
||||
### 8.1 Duration Tokens
|
||||
|
||||
| Token | Value | Use Case |
|
||||
|-------|-------|----------|
|
||||
| `duration-xs` | 80ms | Quick hover, focus |
|
||||
| `duration-sm` | 140ms | Button press, small transitions |
|
||||
| `duration-md` | 200ms | Modal open/close, panel slide |
|
||||
| `duration-lg` | 260ms | Page transitions |
|
||||
| `duration-xl` | 320ms | Complex animations |
|
||||
|
||||
**Reduced-motion override**: Clamp all to 0-80ms
|
||||
|
||||
### 8.2 Easing Tokens
|
||||
|
||||
- `standard`: Default transition
|
||||
- `decel`: Element entering (start fast, slow down)
|
||||
- `accel`: Element exiting (slow start, speed up)
|
||||
- `emphasized`: Important state changes
|
||||
|
||||
### 8.3 Distance Scales
|
||||
|
||||
- `XS`: 4px
|
||||
- `SM`: 8px
|
||||
- `MD`: 16px
|
||||
- `LG`: 24px
|
||||
- `XL`: 32px
|
||||
|
||||
**Location**: `src/Web/StellaOps.Web/src/styles/tokens/motion.{ts,scss}`
|
||||
|
||||
## 9. ACCESSIBILITY REQUIREMENTS
|
||||
|
||||
### 9.1 WCAG 2.1 AA Compliance
|
||||
|
||||
- Focus order: logical and consistent
|
||||
- Keyboard: all interactive elements accessible
|
||||
- Contrast:
|
||||
- Text: ≥ 4.5:1
|
||||
- UI elements: ≥ 3:1
|
||||
- Reduced motion: honored via `prefers-reduced-motion`
|
||||
- Status messaging: `aria-live=polite` for updates
|
||||
|
||||
### 9.2 Reduced-Motion Rules
|
||||
|
||||
When `prefers-reduced-motion: reduce`:
|
||||
- Durations clamp to 0-80ms
|
||||
- Disable parallax/auto-animations
|
||||
- Focus/hover states remain visible
|
||||
- No animated GIF/Lottie autoplay
|
||||
|
||||
### 9.3 Screen Reader Support
|
||||
|
||||
- **Undo window**: 8s with keyboard focus and `aria-live=polite`
|
||||
- **Loading states**: Announce state changes
|
||||
- **Error messages**: Informative, not generic
|
||||
|
||||
## 10. EVIDENCE & PROOF SPECIFICATIONS
|
||||
|
||||
### 10.1 Evidence Bundle Minimum Requirements
|
||||
|
||||
**Component presence**:
|
||||
- SBOM fragment (SPDX/CycloneDX) with component identity and provenance
|
||||
- Signed attestation for SBOM artifact
|
||||
|
||||
**Vulnerability match**:
|
||||
- Matching rule details (CPE/purl/range) + scanner identity/version
|
||||
- Signed vulnerability report attestation
|
||||
|
||||
**Reachable vulnerability**:
|
||||
- Call path: entrypoint → frames → vulnerable symbol
|
||||
- Hash/digest of call graph slice (tamper-evident)
|
||||
- Tool info + limitations (reflection/dynamic dispatch uncertainty)
|
||||
|
||||
**Not affected via VEX**:
|
||||
- VEX statement (OpenVEX/CSAF) + signer
|
||||
- Justification for `not_affected`
|
||||
- Align to CISA minimum requirements
|
||||
|
||||
**Gate decision**:
|
||||
- Input digests (SBOM digest, scan attestation digests, VEX doc digests)
|
||||
- Policy version + rule ID
|
||||
- Deterministic **decision hash** over (policy + input digests)
|
||||
|
||||
### 10.2 Evidence Object Structure
|
||||
|
||||
```typescript
|
||||
interface Evidence {
|
||||
sbom_snippet_attestation: DSSEEnvelope,
|
||||
reachability_proof: {
|
||||
entrypoint: string,
|
||||
frames: CallFrame[],
|
||||
file_hashes: string[],
|
||||
graph_digest: string
|
||||
},
|
||||
attestation_chain: DSSESummary[],
|
||||
transparency_receipt: {
|
||||
logIndex: number,
|
||||
uuid: string,
|
||||
inclusionProof: string,
|
||||
checkpoint: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 10.3 Proof Panel Requirements
|
||||
|
||||
**Four artifacts**:
|
||||
1. **SBOM snippet (signed)**: DSSE attestation, verify with cosign
|
||||
2. **Call-stack slice**: Entrypoint → vulnerable symbol, status pill (`Reachable`, `Potentially reachable`, `Unreachable`)
|
||||
3. **Attestation chain**: DSSE envelope summary, verification status, "Verify locally" command
|
||||
4. **Transparency receipt**: Rekor inclusion proof, "Verify inclusion" command
|
||||
|
||||
**One-click export**:
|
||||
- "Export Evidence (.tar.gz)" bundling: SBOM slice, call-stack JSON, DSSE attestation, Rekor proof JSON
|
||||
|
||||
## 11. CONFIGURATION & FEATURE FLAGS
|
||||
|
||||
### 11.1 TTFS Feature Flags
|
||||
|
||||
```yaml
|
||||
ttfs:
|
||||
first_signal_enabled: true # Default ON in staging
|
||||
cache_enabled: true
|
||||
failure_index_enabled: true
|
||||
sse_enabled: true
|
||||
policy_preeval_enabled: true
|
||||
```
|
||||
|
||||
### 11.2 Cache Configuration
|
||||
|
||||
```yaml
|
||||
cache:
|
||||
backend: valkey | postgres | none # TTFS_CACHE_BACKEND
|
||||
ttl_seconds: 86400 # TTFS_CACHE_TTL_SECONDS
|
||||
key_pattern: "signal:job:{jobId}"
|
||||
```
|
||||
|
||||
### 11.3 Air-Gapped Profile
|
||||
|
||||
- Skip Valkey; use Postgres-only
|
||||
- Use `first_signal_snapshots` table
|
||||
- NOTIFY/LISTEN for streaming updates
|
||||
|
||||
## 12. TESTING REQUIREMENTS
|
||||
|
||||
### 12.1 Acceptance Criteria (TTE)
|
||||
|
||||
- [ ] First paint shows real proof snippet (not summary)
|
||||
- [ ] "Copy proof" button works within 1 click
|
||||
- [ ] TTE P95 in staging ≤ 10s; in prod ≤ 15s
|
||||
- [ ] If proof missing, explicit empty-state + retry path
|
||||
- [ ] Telemetry sampled ≥ 50% of sessions (or 100% for internal)
|
||||
|
||||
### 12.2 Acceptance Tests (TTFS)
|
||||
|
||||
- Run with early fail → first signal < 1s, shows exact command + exit code
|
||||
- Run with policy gate fail → rule name + fix hint visible first
|
||||
- Offline/slow network → cached summary still renders actionable hint
|
||||
|
||||
### 12.3 Determinism Requirements
|
||||
|
||||
- Freeze timers to `2025-12-04T12:00:00Z` in stories/e2e
|
||||
- Seed RNG with `0x5EED2025` unless scenario-specific
|
||||
- All fixtures stored under `tests/fixtures/micro/`
|
||||
- No network calls; offline assets bundled
|
||||
- Playwright runs with `--disable-animations` and reduced-motion emulation
|
||||
|
||||
### 12.4 Load Tests
|
||||
|
||||
`/jobs/{id}/signal`:
|
||||
- Cache-hit P95 ≤ 250ms
|
||||
- Cold path P95 ≤ 500ms
|
||||
- Error rate < 0.1% under expected concurrency
|
||||
|
||||
## 13. REDACTION & SECURITY
|
||||
|
||||
### 13.1 Excerpt Redaction Rules
|
||||
|
||||
- Strip: bearer tokens, API keys, access tokens, private URLs
|
||||
- Cap excerpt length: 240 chars
|
||||
- Normalize whitespace
|
||||
- Never include excerpts in telemetry attributes
|
||||
|
||||
### 13.2 Tenant Isolation
|
||||
|
||||
Cache keys include tenant boundary:
|
||||
```
|
||||
tenant:{tenantId}:signal:job:{jobId}
|
||||
```
|
||||
|
||||
Failure signatures looked up within same tenant only.
|
||||
|
||||
### 13.3 Secret Scanning
|
||||
|
||||
Runtime guardrails:
|
||||
- If excerpt contains forbidden patterns → replace with "[redacted]"
|
||||
- Security review sign-off required for snapshot + signature + telemetry
|
||||
|
||||
## 14. LOCALIZATION
|
||||
|
||||
### 14.1 Micro-Copy Requirements
|
||||
|
||||
- Keys and ICU messages for micro-interaction copy
|
||||
- Defaults: EN
|
||||
- Fallbacks present
|
||||
- No hard-coded strings in components
|
||||
- i18n extraction shows zero TODO keys
|
||||
|
||||
### 14.2 Snapshot Verification
|
||||
|
||||
Verify translated skeleton/error/undo copy in snapshots.
|
||||
|
||||
## 15. DELIVERABLES MAP
|
||||
|
||||
| Category | Location | Description |
|
||||
|----------|----------|-------------|
|
||||
| Motion tokens | `src/Web/StellaOps.Web/src/styles/tokens/motion.{ts,scss}` | Duration, easing, distance scales + reduced-motion overrides |
|
||||
| Storybook stories | `apps/storybook/src/stories/micro/*` | Slow, error, offline, reduced-motion, undo flows |
|
||||
| Playwright suite | `tests/e2e/micro-interactions.spec.ts` | MI2/MI3/MI4/MI8 coverage |
|
||||
| Telemetry schema | `docs/modules/ui/telemetry/ui-micro.schema.json` | Event schema + validators |
|
||||
| Component map | `docs/modules/ui/micro-interactions-map.md` | Components → interaction type → token usage |
|
||||
| Fixtures | `tests/fixtures/micro/` | Deterministic test fixtures |
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Target Platform**: .NET 10, PostgreSQL ≥16, Angular v17
|
||||
Reference in New Issue
Block a user