docs consolidation and others
This commit is contained in:
@@ -1,299 +0,0 @@
|
||||
# Advisory Architecture Alignment Report
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-19
|
||||
**Status:** ACTIVE
|
||||
**Related Sprint:** SPRINT_5000_0001_0001
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This report validates that **StellaOps achieves 90%+ alignment** with the reference advisory architecture specifying CycloneDX 1.7, VEX-first decisioning, in-toto attestations, and signal-based contracts.
|
||||
|
||||
**Overall Alignment Score: 95%**
|
||||
|
||||
| Category | Alignment | Status |
|
||||
|----------|-----------|--------|
|
||||
| DSSE/in-toto Attestations | 100% | ✅ Fully Aligned |
|
||||
| VEX Multi-Format Support | 100% | ✅ Fully Aligned |
|
||||
| CVSS v4.0 | 100% | ✅ Fully Aligned |
|
||||
| EPSS Integration | 100% | ✅ Fully Aligned |
|
||||
| Deterministic Scoring | 100% | ✅ Fully Aligned |
|
||||
| Reachability Analysis | 100% | ✅ Fully Aligned |
|
||||
| Call-Stack Witnesses | 100% | ✅ Fully Aligned |
|
||||
| Smart-Diff | 100% | ✅ Fully Aligned |
|
||||
| Unknowns Handling | 100% | ✅ Fully Aligned |
|
||||
| CycloneDX Version | 100% | ✅ Using 1.7 |
|
||||
|
||||
---
|
||||
|
||||
## Component-by-Component Alignment
|
||||
|
||||
### 1. DSSE/in-toto Attestations
|
||||
|
||||
**Advisory Requirement:**
|
||||
> All security artifacts must be wrapped in DSSE-signed in-toto attestations with specific predicate types.
|
||||
|
||||
**StellaOps Implementation:** ✅ **19 Predicate Types**
|
||||
|
||||
| Predicate Type | Module | Status |
|
||||
|----------------|--------|--------|
|
||||
| `https://in-toto.io/attestation/slsa/v1.0` | Attestor | ✅ |
|
||||
| `stella.ops/sbom@v1` | Scanner | ✅ |
|
||||
| `stella.ops/vex@v1` | Excititor | ✅ |
|
||||
| `stella.ops/callgraph@v1` | Scanner.Reachability | ✅ |
|
||||
| `stella.ops/reachabilityWitness@v1` | Scanner.Reachability | ✅ |
|
||||
| `stella.ops/policy-decision@v1` | Policy.Engine | ✅ |
|
||||
| `stella.ops/score-attestation@v1` | Policy.Scoring | ✅ |
|
||||
| `stella.ops/witness@v1` | Scanner.Reachability | ✅ |
|
||||
| `stella.ops/drift@v1` | Scanner.ReachabilityDrift | ✅ |
|
||||
| `stella.ops/unknown@v1` | Scanner.Unknowns | ✅ |
|
||||
| `stella.ops/triage@v1` | Scanner.Triage | ✅ |
|
||||
| `stella.ops/vuln-surface@v1` | Scanner.VulnSurfaces | ✅ |
|
||||
| `stella.ops/trigger@v1` | Scanner.VulnSurfaces | ✅ |
|
||||
| `stella.ops/explanation@v1` | Scanner.Reachability | ✅ |
|
||||
| `stella.ops/boundary@v1` | Scanner.SmartDiff | ✅ |
|
||||
| `stella.ops/evidence@v1` | Scanner.SmartDiff | ✅ |
|
||||
| `stella.ops/approval@v1` | Policy.Engine | ✅ |
|
||||
| `stella.ops/component@v1` | Scanner.Emit | ✅ |
|
||||
| `stella.ops/richgraph@v1` | Scanner.Reachability | ✅ |
|
||||
|
||||
**Evidence:**
|
||||
- `src/Signer/StellaOps.Signer/StellaOps.Signer.Core/PredicateTypes.cs`
|
||||
- `src/Attestor/StellaOps.Attestor.Envelope/DsseEnvelope.cs`
|
||||
|
||||
---
|
||||
|
||||
### 2. VEX Multi-Format Support
|
||||
|
||||
**Advisory Requirement:**
|
||||
> Support OpenVEX, CycloneDX VEX, and CSAF formats with aggregation and precedence.
|
||||
|
||||
**StellaOps Implementation:** ✅ **4 Format Families**
|
||||
|
||||
| Format | Parser | Precedence |
|
||||
|--------|--------|------------|
|
||||
| OpenVEX 0.2.0+ | `OpenVexParser` | Highest |
|
||||
| CycloneDX 1.4-1.7 VEX | `CycloneDxVexParser` | High |
|
||||
| CSAF 2.0 | `CsafParser` | Medium |
|
||||
| OSV | `OsvParser` | Baseline |
|
||||
|
||||
**Evidence:**
|
||||
- `src/Excititor/__Libraries/StellaOps.Excititor.VexParsing/`
|
||||
- `src/Policy/__Libraries/StellaOps.Policy/Lattice/VexLattice.cs`
|
||||
- Lattice aggregation with justified_negation_bias
|
||||
|
||||
---
|
||||
|
||||
### 3. CVSS v4.0
|
||||
|
||||
**Advisory Requirement:**
|
||||
> Support CVSS v4.0 with full vector parsing and MacroVector computation.
|
||||
|
||||
**StellaOps Implementation:** ✅ **Full Support**
|
||||
|
||||
| Capability | Implementation |
|
||||
|------------|----------------|
|
||||
| Vector Parsing | `Cvss4Parser.cs` |
|
||||
| MacroVector | `MacroVectorComputer.cs` |
|
||||
| Environmental Modifiers | `Cvss4EnvironmentalScorer.cs` |
|
||||
| Threat Metrics | `Cvss4ThreatScorer.cs` |
|
||||
|
||||
**Evidence:**
|
||||
- `src/Signals/StellaOps.Signals/Cvss/Cvss4Parser.cs`
|
||||
- `src/Signals/StellaOps.Signals/Cvss/MacroVectorComputer.cs`
|
||||
|
||||
---
|
||||
|
||||
### 4. EPSS Integration
|
||||
|
||||
**Advisory Requirement:**
|
||||
> Track EPSS with model_date provenance (not version numbers).
|
||||
|
||||
**StellaOps Implementation:** ✅ **Correct Model Dating**
|
||||
|
||||
| Capability | Implementation |
|
||||
|------------|----------------|
|
||||
| Daily Ingestion | `EpssIngestJob.cs` |
|
||||
| Model Date Tracking | `model_date` field in all EPSS entities |
|
||||
| Change Detection | `EpssChangeDetector.cs` |
|
||||
| Air-Gap Bundle | `EpssBundleSource.cs` |
|
||||
|
||||
**Evidence:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Storage/Epss/`
|
||||
- `docs/architecture/epss-versioning-clarification.md`
|
||||
|
||||
---
|
||||
|
||||
### 5. Deterministic Scoring
|
||||
|
||||
**Advisory Requirement:**
|
||||
> Scores must be reproducible given same inputs (canonical JSON, sorted keys, UTC timestamps).
|
||||
|
||||
**StellaOps Implementation:** ✅ **3 Scoring Engines**
|
||||
|
||||
| Engine | Purpose |
|
||||
|--------|---------|
|
||||
| `Cvss4Scorer` | Base vulnerability scoring |
|
||||
| `ReachabilityScorer` | Path-based risk adjustment |
|
||||
| `UnknownRanker` | 5-dimensional uncertainty scoring |
|
||||
|
||||
**Determinism Guarantees:**
|
||||
- `StellaOps.Canonical.Json` for sorted-key serialization
|
||||
- `ScannerTimestamps.Normalize()` for UTC normalization
|
||||
- Hash-tracked input snapshots (`ScoringRulesSnapshot`)
|
||||
|
||||
**Evidence:**
|
||||
- `src/__Libraries/StellaOps.Canonical.Json/CanonJson.cs`
|
||||
- `src/Policy/__Libraries/StellaOps.Policy/Scoring/`
|
||||
|
||||
---
|
||||
|
||||
### 6. Reachability Analysis
|
||||
|
||||
**Advisory Requirement:**
|
||||
> Static + dynamic call graph analysis with entrypoint-to-sink reachability.
|
||||
|
||||
**StellaOps Implementation:** ✅ **Hybrid Analysis**
|
||||
|
||||
| Ecosystem | Extractor | Status |
|
||||
|-----------|-----------|--------|
|
||||
| .NET | `DotNetCallGraphExtractor` (Roslyn) | ✅ |
|
||||
| Java | `JavaBytecodeFingerprinter` (ASM/Cecil) | ✅ |
|
||||
| Node.js | `JavaScriptMethodFingerprinter` | ✅ |
|
||||
| Python | `PythonAstFingerprinter` | ✅ |
|
||||
| Go | `GoCallGraphExtractor` (external tool) | 🔄 In Progress |
|
||||
| Binary | `NativeCallStackAnalyzer` | ✅ |
|
||||
|
||||
**Evidence:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.CallGraph/`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Reachability/`
|
||||
|
||||
---
|
||||
|
||||
### 7. Call-Stack Witnesses
|
||||
|
||||
**Advisory Requirement:**
|
||||
> DSSE-signed witnesses proving entrypoint → sink paths.
|
||||
|
||||
**StellaOps Implementation:** ✅ **Full Witness System**
|
||||
|
||||
| Component | Implementation |
|
||||
|-----------|----------------|
|
||||
| Path Witness | `PathWitness.cs`, `PathWitnessBuilder.cs` |
|
||||
| DSSE Signing | `WitnessDsseSigner.cs` |
|
||||
| Verification | `WitnessVerifier.cs` |
|
||||
| Storage | `PostgresWitnessRepository.cs` |
|
||||
|
||||
**Evidence:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Reachability/Witnesses/`
|
||||
- `docs/contracts/witness-v1.md`
|
||||
|
||||
---
|
||||
|
||||
### 8. Smart-Diff
|
||||
|
||||
**Advisory Requirement:**
|
||||
> Detect material risk changes between scan runs.
|
||||
|
||||
**StellaOps Implementation:** ✅ **4 Detection Rules**
|
||||
|
||||
| Rule | Implementation |
|
||||
|------|----------------|
|
||||
| New Finding | `NewFindingDetector` |
|
||||
| Score Increase | `ScoreIncreaseDetector` |
|
||||
| VEX Status Change | `VexStatusChangeDetector` |
|
||||
| Reachability Change | `ReachabilityChangeDetector` |
|
||||
|
||||
**Evidence:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.SmartDiff/`
|
||||
|
||||
---
|
||||
|
||||
### 9. Unknowns Handling
|
||||
|
||||
**Advisory Requirement:**
|
||||
> Track uncertainty with multi-dimensional scoring.
|
||||
|
||||
**StellaOps Implementation:** ✅ **11 Unknown Types, 5 Dimensions**
|
||||
|
||||
**Unknown Types:**
|
||||
1. `missing_vex` - No VEX statement
|
||||
2. `ambiguous_indirect_call` - Unresolved call target
|
||||
3. `unanalyzed_dependency` - Dependency not scanned
|
||||
4. `stale_sbom` - SBOM age threshold exceeded
|
||||
5. `missing_reachability` - No reachability data
|
||||
6. `unmatched_cpe` - CPE lookup failed
|
||||
7. `conflict_vex` - Conflicting VEX statements
|
||||
8. `native_code` - Unanalyzed native component
|
||||
9. `generated_code` - Generated code boundary
|
||||
10. `dynamic_dispatch` - Runtime-resolved call
|
||||
11. `external_boundary` - External service call
|
||||
|
||||
**Scoring Dimensions:**
|
||||
1. Blast radius (dependents, network-facing, privilege)
|
||||
2. Evidence scarcity
|
||||
3. Exploit pressure (EPSS, KEV)
|
||||
4. Containment signals
|
||||
5. Time decay
|
||||
|
||||
**Evidence:**
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Unknowns/`
|
||||
- `docs/architecture/signal-contract-mapping.md` (Signal-14 section)
|
||||
|
||||
---
|
||||
|
||||
### 10. CycloneDX Version
|
||||
|
||||
**Advisory Requirement:**
|
||||
> Use CycloneDX 1.7 as baseline SBOM envelope.
|
||||
|
||||
**StellaOps Implementation:** ✅ **Using 1.7**
|
||||
|
||||
| Aspect | Status |
|
||||
|--------|--------|
|
||||
| Package Version | CycloneDX.Core 11.0+ |
|
||||
| Spec Version | 1.7 |
|
||||
| Upgrade Status | COMPLETED |
|
||||
|
||||
**Status:** Upgraded from 1.6 to 1.7 in Sprint 3200 (November 2024). All scanner output now generates CycloneDX 1.7 by default, with backward compatibility for 1.6 ingestion.
|
||||
|
||||
---
|
||||
|
||||
## Areas Where StellaOps Exceeds Advisory
|
||||
|
||||
1. **More Predicate Types:** 19 vs. advisory's implied 5-8
|
||||
2. **Offline/Air-Gap Support:** Full bundle-based operation
|
||||
3. **Regional Crypto:** GOST, SM2/SM3, PQ-safe modes
|
||||
4. **Multi-Tenant:** Enterprise-grade tenant isolation
|
||||
5. **BLAKE3 Hashing:** Faster, more secure than SHA-256
|
||||
6. **Sigstore Rekor Integration:** Transparency log support
|
||||
7. **Native Binary Analysis:** PE/ELF/Mach-O identity extraction
|
||||
|
||||
---
|
||||
|
||||
## Remaining Gaps
|
||||
|
||||
| Gap | Priority | Mitigation | Timeline |
|
||||
|-----|----------|------------|----------|
|
||||
| _(None - All gaps resolved)_ | — | — | — |
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
StellaOps demonstrates **100% alignment** with the reference advisory architecture. All requirements are met, including CycloneDX 1.7 support.
|
||||
|
||||
**Recommendation:** Full production deployment approved. All advisory architecture requirements satisfied.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [CycloneDX Specification](https://cyclonedx.org/specification/)
|
||||
- [in-toto Attestation Framework](https://github.com/in-toto/attestation)
|
||||
- [FIRST.org EPSS](https://www.first.org/epss/)
|
||||
- [OpenVEX Specification](https://github.com/openvex/spec)
|
||||
- `docs/architecture/signal-contract-mapping.md`
|
||||
- `docs/architecture/epss-versioning-clarification.md`
|
||||
@@ -1,236 +0,0 @@
|
||||
# Console Admin RBAC Architecture
|
||||
|
||||
## 1. Purpose
|
||||
- Provide a unified, Authority-backed admin surface for tenants, users, roles, clients, tokens, and audit.
|
||||
- Expose the same capabilities to UI and CLI while preserving offline-first operation.
|
||||
- Normalize scope and role bundles, including missing Scanner roles, for consistent RBAC across modules.
|
||||
|
||||
## 2. Scope
|
||||
- Authority admin APIs and data model used by the Console Admin workspace.
|
||||
- Role and scope taxonomy, including scanner roles.
|
||||
- Audit, fresh-auth, and offline export/import workflow.
|
||||
- UI integration contract (routes, scopes, and API paths).
|
||||
|
||||
Non-goals:
|
||||
- Replacing external IdP user lifecycle workflows (SAML/OIDC remains primary for enterprise identity).
|
||||
- Exposing privileged mTLS-only admin endpoints directly to the browser.
|
||||
|
||||
## 3. Core Architecture
|
||||
### 3.1 Authority admin tiers
|
||||
- **/admin**: mTLS + authority.admin scope for automation and ops tooling.
|
||||
- **/console/admin**: DPoP + UI scopes for browser and CLI admin flows.
|
||||
|
||||
Both tiers share the same data model and audit log but enforce different auth policies.
|
||||
|
||||
### 3.2 Entities and ownership
|
||||
Authority remains the source of truth for:
|
||||
- **Tenant**: id, display name, status, isolation mode, default roles.
|
||||
- **Installation**: installation id, tenant binding, bootstrap metadata.
|
||||
- **Role**: id, display name, scopes[], audiences[], flags (interactive-only, requires fresh-auth).
|
||||
- **User**: subject, status, display name, tenant assignments, roles per tenant.
|
||||
- **Client**: client id, grant types, auth method, allowed scopes, audiences, tenant hint.
|
||||
- **Token record**: access/refresh/device metadata, revocation status.
|
||||
- **Audit events**: immutable admin and auth events.
|
||||
|
||||
### 3.3 Fresh-auth
|
||||
High-risk operations require a fresh-auth window:
|
||||
- Tenant suspend/resume
|
||||
- Token revocation (bulk or admin)
|
||||
- Role bundle edits
|
||||
- Client secret or key rotation
|
||||
- Branding apply
|
||||
|
||||
Authority uses auth_time + fresh-auth TTL to gate these operations.
|
||||
|
||||
## 4. Scope and Role Taxonomy
|
||||
### 4.1 Console admin scopes
|
||||
New admin scopes (Authority-managed):
|
||||
- `authority:tenants.read`, `authority:tenants.write`
|
||||
- `authority:users.read`, `authority:users.write`
|
||||
- `authority:roles.read`, `authority:roles.write`
|
||||
- `authority:clients.read`, `authority:clients.write`
|
||||
- `authority:tokens.read`, `authority:tokens.revoke`
|
||||
- `authority:audit.read`
|
||||
- `authority:branding.read`, `authority:branding.write`
|
||||
- `ui.admin` (console access for admin views)
|
||||
|
||||
### 4.2 Scanner scope and role bundles (missing today)
|
||||
Define scanner scopes and role bundles to align UI, CLI, and API:
|
||||
- Scopes: `scanner:read`, `scanner:scan`, `scanner:export`, `scanner:write`
|
||||
- Role bundles:
|
||||
- `role/scanner-viewer` -> `scanner:read`
|
||||
- `role/scanner-operator` -> `scanner:read`, `scanner:scan`, `scanner:export`
|
||||
- `role/scanner-admin` -> `scanner:read`, `scanner:scan`, `scanner:export`, `scanner:write`
|
||||
|
||||
Compatibility:
|
||||
- Gateway maps `scanner:read|scan|export|write` to any legacy scanner scope strings until full cutover.
|
||||
|
||||
### 4.3 Module role bundle catalog
|
||||
Role bundles are grouped by module and map to existing Authority scopes unless noted.
|
||||
|
||||
| Module | Role bundle | Scopes |
|
||||
| --- | --- | --- |
|
||||
| Console | `role/console-viewer` | `ui.read` |
|
||||
| Console | `role/console-admin` | `ui.read`, `ui.admin`, `authority:tenants.read`, `authority:users.read`, `authority:roles.read`, `authority:clients.read`, `authority:tokens.read`, `authority:audit.read`, `authority:branding.read` |
|
||||
| Console | `role/console-superadmin` | `ui.read`, `ui.admin`, `authority:tenants.*`, `authority:users.*`, `authority:roles.*`, `authority:clients.*`, `authority:tokens.*`, `authority:audit.read`, `authority:branding.*` |
|
||||
| Scanner | `role/scanner-viewer` | `scanner:read`, `findings:read`, `aoc:verify` |
|
||||
| Scanner | `role/scanner-operator` | `scanner:read`, `scanner:scan`, `scanner:export`, `findings:read`, `aoc:verify` |
|
||||
| Scanner | `role/scanner-admin` | `scanner:read`, `scanner:scan`, `scanner:export`, `scanner:write`, `findings:read`, `aoc:verify` |
|
||||
| Policy | `role/policy-author` | `policy:read`, `policy:author`, `policy:simulate`, `findings:read` |
|
||||
| Policy | `role/policy-reviewer` | `policy:read`, `policy:review`, `policy:simulate`, `findings:read` |
|
||||
| Policy | `role/policy-approver` | `policy:read`, `policy:review`, `policy:approve`, `policy:simulate`, `findings:read` |
|
||||
| Policy | `role/policy-operator` | `policy:read`, `policy:operate`, `policy:run`, `policy:activate`, `policy:publish`, `policy:promote`, `policy:simulate`, `findings:read` |
|
||||
| Policy | `role/policy-auditor` | `policy:read`, `policy:audit`, `findings:read` |
|
||||
| Concelier | `role/concelier-reader` | `advisory:read`, `aoc:verify` |
|
||||
| Concelier | `role/concelier-ingest` | `advisory:ingest`, `advisory:read`, `aoc:verify` |
|
||||
| Concelier | `role/concelier-operator` | `concelier.jobs.trigger`, `advisory:read`, `aoc:verify` |
|
||||
| Concelier | `role/concelier-admin` | `concelier.jobs.trigger`, `concelier.merge`, `advisory:read`, `aoc:verify` |
|
||||
| Excititor | `role/excititor-reader` | `vex:read`, `aoc:verify` |
|
||||
| Excititor | `role/excititor-ingest` | `vex:ingest`, `vex:read` |
|
||||
| Notify | `role/notify-viewer` | `notify.viewer` |
|
||||
| Notify | `role/notify-operator` | `notify.viewer`, `notify.operator` |
|
||||
| Notify | `role/notify-admin` | `notify.viewer`, `notify.operator`, `notify.admin` |
|
||||
| Scheduler | `role/scheduler-viewer` | `scheduler:read` (new) |
|
||||
| Scheduler | `role/scheduler-operator` | `scheduler:read`, `scheduler:operate` (new) |
|
||||
| Scheduler | `role/scheduler-admin` | `scheduler:read`, `scheduler:operate`, `scheduler:admin` (new) |
|
||||
| Orchestrator | `role/orch-viewer` | `orch:read`, `findings:read` |
|
||||
| Orchestrator | `role/orch-operator` | `orch:read`, `orch:operate`, `findings:read` |
|
||||
| Orchestrator | `role/orch-admin` | `orch:read`, `orch:operate`, `orch:quota`, `orch:backfill`, `findings:read` |
|
||||
| Graph | `role/graph-viewer` | `graph:read`, `graph:export` |
|
||||
| Graph | `role/graph-operator` | `graph:read`, `graph:export`, `graph:simulate` |
|
||||
| Graph | `role/graph-admin` | `graph:read`, `graph:export`, `graph:simulate`, `graph:write`, `graph:admin` |
|
||||
| Vuln Explorer | `role/vuln-viewer` | `vuln:view`, `findings:read` |
|
||||
| Vuln Explorer | `role/vuln-investigator` | `vuln:view`, `vuln:investigate`, `findings:read` |
|
||||
| Vuln Explorer | `role/vuln-operator` | `vuln:view`, `vuln:investigate`, `vuln:operate`, `findings:read` |
|
||||
| Vuln Explorer | `role/vuln-auditor` | `vuln:view`, `vuln:audit`, `findings:read` |
|
||||
| Export Center | `role/export-viewer` | `export.viewer` |
|
||||
| Export Center | `role/export-operator` | `export.viewer`, `export.operator` |
|
||||
| Export Center | `role/export-admin` | `export.viewer`, `export.operator`, `export.admin` |
|
||||
| Advisory AI | `role/advisory-ai-viewer` | `advisory-ai:view`, `aoc:verify` |
|
||||
| Advisory AI | `role/advisory-ai-operator` | `advisory-ai:view`, `advisory-ai:operate`, `aoc:verify` |
|
||||
| Advisory AI | `role/advisory-ai-admin` | `advisory-ai:view`, `advisory-ai:operate`, `advisory-ai:admin`, `aoc:verify` |
|
||||
| Signals | `role/signals-viewer` | `signals:read`, `aoc:verify` |
|
||||
| Signals | `role/signals-uploader` | `signals:read`, `signals:write`, `aoc:verify` |
|
||||
| Signals | `role/signals-admin` | `signals:read`, `signals:write`, `signals:admin`, `aoc:verify` |
|
||||
| Evidence Locker | `role/evidence-reader` | `evidence:read` |
|
||||
| Evidence Locker | `role/evidence-creator` | `evidence:read`, `evidence:create` |
|
||||
| Evidence Locker | `role/evidence-legal` | `evidence:read`, `evidence:hold` |
|
||||
| Observability | `role/observability-viewer` | `obs:read`, `timeline:read`, `attest:read` |
|
||||
| Observability | `role/observability-investigator` | `obs:read`, `timeline:read`, `timeline:write`, `evidence:read`, `evidence:create`, `attest:read` |
|
||||
| Observability | `role/observability-incident-commander` | `obs:read`, `obs:incident`, `timeline:read`, `timeline:write`, `evidence:read`, `evidence:create`, `attest:read` |
|
||||
| Issuer Directory | `role/issuer-directory-viewer` | `issuer-directory:read` |
|
||||
| Issuer Directory | `role/issuer-directory-operator` | `issuer-directory:read`, `issuer-directory:write` |
|
||||
| Issuer Directory | `role/issuer-directory-admin` | `issuer-directory:read`, `issuer-directory:write`, `issuer-directory:admin` |
|
||||
| Task Packs | `role/packs-viewer` | `packs.read` |
|
||||
| Task Packs | `role/packs-operator` | `packs.read`, `packs.run` |
|
||||
| Task Packs | `role/packs-publisher` | `packs.read`, `packs.write` |
|
||||
| Task Packs | `role/packs-approver` | `packs.read`, `packs.approve` |
|
||||
| Airgap | `role/airgap-viewer` | `airgap:status:read` |
|
||||
| Airgap | `role/airgap-operator` | `airgap:status:read`, `airgap:import` |
|
||||
| Airgap | `role/airgap-admin` | `airgap:status:read`, `airgap:import`, `airgap:seal` |
|
||||
| Exceptions | `role/exceptions-viewer` | `exceptions:read` |
|
||||
| Exceptions | `role/exceptions-approver` | `exceptions:read`, `exceptions:approve` |
|
||||
| Exceptions | `role/exceptions-editor` | `exceptions:read`, `exceptions:write` |
|
||||
| Attestor | `role/attestor-viewer` | `attest:read`, `aoc:verify` |
|
||||
| Attestor | `role/attestor-operator` | `attest:read`, `attest:create`, `aoc:verify` |
|
||||
| Attestor | `role/attestor-admin` | `attest:read`, `attest:create`, `attest:admin`, `aoc:verify` |
|
||||
| Signer | `role/signer-viewer` | `signer:read`, `aoc:verify` |
|
||||
| Signer | `role/signer-operator` | `signer:read`, `signer:sign`, `aoc:verify` |
|
||||
| Signer | `role/signer-admin` | `signer:read`, `signer:sign`, `signer:rotate`, `signer:admin`, `aoc:verify` |
|
||||
| SBOM | `role/sbom-viewer` | `sbom:read`, `aoc:verify` |
|
||||
| SBOM | `role/sbom-creator` | `sbom:read`, `sbom:write`, `aoc:verify` |
|
||||
| SBOM | `role/sbom-attestor` | `sbom:read`, `sbom:write`, `sbom:attest`, `attest:create`, `aoc:verify` |
|
||||
| Release | `role/release-viewer` | `release:read`, `policy:read`, `findings:read` |
|
||||
| Release | `role/release-manager` | `release:read`, `release:write`, `policy:read`, `findings:read` |
|
||||
| Release | `role/release-publisher` | `release:read`, `release:write`, `release:publish`, `policy:read`, `findings:read` |
|
||||
| Release | `role/release-admin` | `release:read`, `release:write`, `release:publish`, `release:bypass`, `policy:read`, `findings:read` |
|
||||
| Zastava | `role/zastava-viewer` | `zastava:read` |
|
||||
| Zastava | `role/zastava-operator` | `zastava:read`, `zastava:trigger` |
|
||||
| Zastava | `role/zastava-admin` | `zastava:read`, `zastava:trigger`, `zastava:admin` |
|
||||
|
||||
**Missing scopes (must be added to Authority)**:
|
||||
|
||||
Scanner scopes are not yet defined in Authority. They are proposed as `scanner:read`, `scanner:scan`, `scanner:export`, and `scanner:write` and must be added to Authority constants, discovery metadata, and gateway enforcement.
|
||||
|
||||
Scheduler scopes are not yet defined in Authority. They are proposed as `scheduler:read`, `scheduler:operate`, and `scheduler:admin` and must be added to Authority constants, discovery metadata, and gateway enforcement.
|
||||
|
||||
Authority admin scopes (partial): `authority:tenants.read` exists. Must add: `authority:tenants.write`, `authority:users.read`, `authority:users.write`, `authority:roles.read`, `authority:roles.write`, `authority:clients.read`, `authority:clients.write`, `authority:tokens.read`, `authority:tokens.revoke`, `authority:branding.read`, `authority:branding.write`.
|
||||
|
||||
UI admin scope: `ui.admin` must be added to Authority constants.
|
||||
|
||||
Attestor scopes: `attest:read` exists. Must add: `attest:create`, `attest:admin`.
|
||||
|
||||
Signer scopes (all new): `signer:read`, `signer:sign`, `signer:rotate`, `signer:admin`.
|
||||
|
||||
SBOM scopes (all new): `sbom:read`, `sbom:write`, `sbom:attest`.
|
||||
|
||||
Release scopes (all new): `release:read`, `release:write`, `release:publish`, `release:bypass`.
|
||||
|
||||
Zastava scopes (all new): `zastava:read`, `zastava:trigger`, `zastava:admin`.
|
||||
|
||||
Graph admin scope: `graph:admin` must be added to Authority constants.
|
||||
|
||||
Exception write scope: `exceptions:write` must be added to Authority constants (exceptions:read and exceptions:approve exist).
|
||||
|
||||
## 5. Console Admin API Surface
|
||||
### 5.1 Tenants
|
||||
- `GET /console/admin/tenants`
|
||||
- `POST /console/admin/tenants`
|
||||
- `PATCH /console/admin/tenants/{tenantId}`
|
||||
- `POST /console/admin/tenants/{tenantId}/suspend`
|
||||
- `POST /console/admin/tenants/{tenantId}/resume`
|
||||
|
||||
Scopes: `authority:tenants.read|write`
|
||||
|
||||
### 5.2 Users
|
||||
- `GET /console/admin/users?tenantId=...`
|
||||
- `POST /console/admin/users` (local users only)
|
||||
- `PATCH /console/admin/users/{userId}`
|
||||
- `POST /console/admin/users/{userId}/disable`
|
||||
- `POST /console/admin/users/{userId}/enable`
|
||||
|
||||
Scopes: `authority:users.read|write`
|
||||
|
||||
### 5.3 Roles and scopes
|
||||
- `GET /console/admin/roles`
|
||||
- `POST /console/admin/roles`
|
||||
- `PATCH /console/admin/roles/{roleId}`
|
||||
- `POST /console/admin/roles/{roleId}/preview-impact`
|
||||
|
||||
Scopes: `authority:roles.read|write`
|
||||
|
||||
### 5.4 Clients
|
||||
- `GET /console/admin/clients`
|
||||
- `POST /console/admin/clients`
|
||||
- `PATCH /console/admin/clients/{clientId}`
|
||||
- `POST /console/admin/clients/{clientId}/rotate`
|
||||
|
||||
Scopes: `authority:clients.read|write`
|
||||
|
||||
### 5.5 Tokens and audit
|
||||
- `GET /console/admin/tokens?tenantId=...`
|
||||
- `POST /console/admin/tokens/revoke`
|
||||
- `GET /console/admin/audit?tenantId=...`
|
||||
|
||||
Scopes: `authority:tokens.read|revoke`, `authority:audit.read`
|
||||
|
||||
## 6. Audit and Observability
|
||||
- Every admin mutation emits `authority.admin.*` events with tenant, actor, and trace id.
|
||||
- Audit export provides deterministic ordering and ISO-8601 timestamps.
|
||||
- Token revocations emit revocation bundle update markers for downstream caches.
|
||||
|
||||
## 7. Offline-first Administration
|
||||
- Admin changes can be exported as signed bundles for air-gapped import.
|
||||
- The Console produces a change manifest; Authority applies it via `/admin/bundles/apply` (mTLS).
|
||||
- UI labels changes as pending when Authority is offline.
|
||||
|
||||
## 8. UI Integration Contract
|
||||
- Admin workspace routes live under `/console/admin/*`.
|
||||
- Admin UI uses `/console/admin` APIs with DPoP; no mTLS endpoints are called by the browser.
|
||||
- `ui.admin` plus specific `authority:*` scopes are required to render and mutate data.
|
||||
|
||||
## 9. References
|
||||
- `docs/modules/authority/architecture.md`
|
||||
- `docs/modules/ui/architecture.md`
|
||||
- `docs/UI_GUIDE.md`
|
||||
- `docs/contracts/web-gateway-tenant-rbac.md`
|
||||
@@ -1,71 +0,0 @@
|
||||
# Console Branding Architecture
|
||||
|
||||
## 1. Purpose
|
||||
- Provide tenant-aware branding (logo, colors, title) without rebuilding the UI.
|
||||
- Keep branding changes auditable, deterministic, and offline-friendly.
|
||||
- Allow defaults to be injected via config.json and overridden per tenant after login.
|
||||
|
||||
## 2. Scope
|
||||
- Branding data model and storage in Authority.
|
||||
- API surface for read/update/preview.
|
||||
- UI application of theme tokens and assets.
|
||||
- Offline export/import and audit handling.
|
||||
|
||||
Non-goals:
|
||||
- Arbitrary CSS injection from untrusted sources.
|
||||
- Runtime font downloads from public CDNs (offline-first constraint).
|
||||
|
||||
## 3. Branding Data Model
|
||||
Authority stores a tenant-scoped branding record:
|
||||
- `brandingId`
|
||||
- `tenantId`
|
||||
- `displayName` (header title)
|
||||
- `logo` (data URI or asset reference)
|
||||
- `favicon` (data URI or asset reference)
|
||||
- `themeTokens` (CSS variable map for light/dark/high-contrast)
|
||||
- `updatedBy`, `updatedAtUtc`
|
||||
- `hash` (sha256 of canonical JSON for cache invalidation)
|
||||
|
||||
Constraints:
|
||||
- Logo and favicon limited to 256KB each.
|
||||
- Only `image/svg+xml`, `image/png`, or `image/jpeg` accepted.
|
||||
- Theme tokens restricted to a whitelist (no arbitrary CSS).
|
||||
|
||||
## 4. Configuration Layering
|
||||
1. **Static defaults** from `/config.json`.
|
||||
2. **Tenant branding** from Authority after login.
|
||||
3. **Session overrides** for preview mode (not persisted).
|
||||
|
||||
If Authority is unreachable, the UI uses the static defaults.
|
||||
|
||||
## 5. API Surface
|
||||
### 5.1 Read branding
|
||||
- `GET /console/branding` (active tenant)
|
||||
- Scopes: `ui.read`, `authority:branding.read`
|
||||
|
||||
### 5.2 Update branding (admin only)
|
||||
- `PUT /console/admin/branding`
|
||||
- Scopes: `ui.admin`, `authority:branding.write`
|
||||
- Requires fresh-auth
|
||||
|
||||
### 5.3 Preview branding
|
||||
- `POST /console/admin/branding/preview`
|
||||
- Scopes: `ui.admin`, `authority:branding.write`
|
||||
- Returns computed tokens and sanitized assets without persisting
|
||||
|
||||
## 6. UI Application
|
||||
- Branding service fetches `/console/branding` after login.
|
||||
- Applies CSS variables on `document.documentElement`.
|
||||
- Updates header/logo assets and document title.
|
||||
- Supports theme-specific overrides using `data-theme` selectors.
|
||||
|
||||
## 7. Audit and Offline
|
||||
- Branding updates emit `authority.branding.updated` events.
|
||||
- Branding bundles are exported with a detached signature for offline import.
|
||||
- Console shows last applied branding hash for verification.
|
||||
|
||||
## 8. References
|
||||
- `docs/UI_GUIDE.md`
|
||||
- `docs/modules/ui/architecture.md`
|
||||
- `docs/modules/authority/architecture.md`
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
# Architecture Enforcement Rules
|
||||
|
||||
This document describes the automated architecture rules enforced by `tests/architecture/StellaOps.Architecture.Tests`. These rules run on every PR and gate merges, ensuring consistent adherence to StellaOps architectural boundaries.
|
||||
|
||||
## Overview
|
||||
|
||||
Architecture tests use [NetArchTest.Rules](https://github.com/BenMorris/NetArchTest) to enforce structural constraints at compile time. Rules are categorized into four areas:
|
||||
|
||||
1. **Lattice Engine Placement** – Ensures lattice/scoring logic stays in Scanner
|
||||
2. **Module Dependencies** – Enforces proper layering between Core, Storage, WebServices, and Workers
|
||||
3. **Forbidden Packages** – Blocks deprecated or non-compliant dependencies
|
||||
4. **Naming Conventions** – Ensures consistent project/assembly naming
|
||||
|
||||
---
|
||||
|
||||
## 1. Lattice Engine Placement Rules
|
||||
|
||||
**Purpose**: The lattice engine computes vulnerability scoring, VEX decisions, and reachability proofs. These computations must remain in Scanner to preserve "prune at source" semantics—no other module should re-derive decisions.
|
||||
|
||||
| Rule ID | Description | Assemblies Affected | Enforcement |
|
||||
|---------|-------------|---------------------|-------------|
|
||||
| `Lattice_Concelier_NoReference` | Concelier assemblies must NOT reference Scanner lattice engine | `StellaOps.Concelier.*` | Fail if any reference to `StellaOps.Scanner.Lattice` |
|
||||
| `Lattice_Excititor_NoReference` | Excititor assemblies must NOT reference Scanner lattice engine | `StellaOps.Excititor.*` | Fail if any reference to `StellaOps.Scanner.Lattice` |
|
||||
| `Lattice_Scanner_MayReference` | Scanner.WebService MAY reference Scanner lattice engine | `StellaOps.Scanner.WebService` | Allowed (no constraint) |
|
||||
| `Lattice_PreservePruneSource` | Excititor does not compute lattice decisions (verified via type search) | `StellaOps.Excititor.*` | Fail if types named `*LatticeEngine*`, `*VexDecision*`, or `*ScoreCalculator*` exist |
|
||||
|
||||
**Rationale**: If Excititor or Concelier computed their own lattice decisions, findings could drift from Scanner's authoritative scoring. Downstream consumers must accept pre-computed verdicts.
|
||||
|
||||
---
|
||||
|
||||
## 2. Module Dependency Rules
|
||||
|
||||
**Purpose**: Enforce clean architecture layering. Core business logic must not depend on infrastructure; services must not cross-call each other.
|
||||
|
||||
| Rule ID | Description | Source | Forbidden Target |
|
||||
|---------|-------------|--------|------------------|
|
||||
| `Dependency_Core_NoInfrastructure` | Core libraries must not depend on infrastructure | `*.Core` | `*.Storage.*`, `*.Postgres`, `*.WebService` |
|
||||
| `Dependency_WebService_NoWebService` | WebServices may not depend on other WebServices | `*.WebService` | Other `*.WebService` assemblies |
|
||||
| `Dependency_Worker_NoWebService` | Workers must not depend directly on WebServices | `*.Worker` | `*.WebService` |
|
||||
|
||||
**Rationale**:
|
||||
- Core libraries define contracts and business rules; they must remain portable.
|
||||
- WebServices should communicate via HTTP/gRPC, not direct assembly references.
|
||||
- Workers may share Core and Storage, but reaching into another service's WebService layer violates service boundaries.
|
||||
|
||||
---
|
||||
|
||||
## 3. Forbidden Package Rules
|
||||
|
||||
**Purpose**: Block usage of deprecated, non-compliant, or strategically-replaced dependencies.
|
||||
|
||||
| Rule ID | Description | Forbidden Namespace/Type | Rationale |
|
||||
|---------|-------------|-------------------------|-----------|
|
||||
| `Forbidden_Redis` | No direct Redis library usage | `StackExchange.Redis`, `ServiceStack.Redis` | StellaOps uses Valkey; Redis clients may introduce incompatible commands |
|
||||
| `Forbidden_MongoDB` | No MongoDB usage | `MongoDB.Driver`, `MongoDB.Bson` | MongoDB storage was deprecated in Sprint 4400; all persistence is PostgreSQL |
|
||||
| `Forbidden_BouncyCastle_Core` | No direct BouncyCastle in core assemblies | `Org.BouncyCastle.*` | Cryptography must be plugin-based (`StellaOps.Cryptography.Plugin.*`); core assemblies reference only `StellaOps.Cryptography.Abstractions` |
|
||||
|
||||
**Exception**: `StellaOps.Cryptography.Plugin.BouncyCastle` is the designated wrapper and may reference BouncyCastle directly.
|
||||
|
||||
---
|
||||
|
||||
## 4. Naming Convention Rules
|
||||
|
||||
**Purpose**: Ensure consistent assembly naming for discoverability and tooling.
|
||||
|
||||
| Rule ID | Pattern | Enforcement |
|
||||
|---------|---------|-------------|
|
||||
| `Naming_TestProjects` | Test projects must end with `.Tests` | Assemblies matching `StellaOps.*Tests*` must end with `.Tests` |
|
||||
| `Naming_Plugins` | Plugins must follow `StellaOps.<Module>.Plugin.*` or `StellaOps.<Module>.Connector.*` | Assemblies with "Plugin" or "Connector" in name must match pattern |
|
||||
|
||||
**Rationale**: Consistent naming enables CI glob patterns (`**/*.Tests.csproj`) and plugin discovery (`Assembly.Load("StellaOps.*.Plugin.*")`).
|
||||
|
||||
---
|
||||
|
||||
## Running Architecture Tests
|
||||
|
||||
```bash
|
||||
# From repository root
|
||||
dotnet test tests/architecture/StellaOps.Architecture.Tests --logger "console;verbosity=detailed"
|
||||
```
|
||||
|
||||
**CI Integration**: Architecture tests run in the Unit test lane on every PR. They are PR-gating—failures block merge.
|
||||
|
||||
---
|
||||
|
||||
## Adding New Rules
|
||||
|
||||
1. Open `tests/architecture/StellaOps.Architecture.Tests/`
|
||||
2. Add test method to the appropriate `*RulesTests.cs` file
|
||||
3. Use NetArchTest fluent API:
|
||||
```csharp
|
||||
[Fact]
|
||||
public void NewRule_Description()
|
||||
{
|
||||
var result = Types.InAssembly(typeof(SomeType).Assembly)
|
||||
.That()
|
||||
.HaveDependencyOn("Forbidden.Namespace")
|
||||
.Should()
|
||||
.NotExist()
|
||||
.GetResult();
|
||||
|
||||
result.IsSuccessful.Should().BeTrue(
|
||||
"Assemblies should not reference Forbidden.Namespace");
|
||||
}
|
||||
```
|
||||
4. Document the rule in this file
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [docs/07_HIGH_LEVEL_ARCHITECTURE.md](../ARCHITECTURE_OVERVIEW.md) – High-level architecture overview
|
||||
- [docs/modules/scanner/architecture.md](../modules/scanner/architecture.md) – Scanner module architecture (lattice engine details)
|
||||
- [AGENTS.md](../../AGENTS.md) – Project-wide agent guidelines and module boundaries
|
||||
- [NetArchTest Documentation](https://github.com/BenMorris/NetArchTest)
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-06-30 · Sprint 5100.0007.0007*
|
||||
@@ -1,441 +0,0 @@
|
||||
# EPSS Versioning Clarification
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-19
|
||||
**Status:** ACTIVE
|
||||
**Related Sprint:** SPRINT_5000_0001_0001
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document clarifies terminology around **EPSS (Exploit Prediction Scoring System)** versioning. Unlike CVSS which has numbered versions (v2.0, v3.0, v3.1, v4.0), **EPSS does not use version numbers**. Instead, EPSS uses **daily model dates** to track the scoring model.
|
||||
|
||||
**Key Point:** References to "EPSS v4" in advisory documentation are **conceptual** and refer to the current EPSS methodology from FIRST.org, not an official version number.
|
||||
|
||||
**StellaOps Implementation:** ✅ **Correct** - Tracks EPSS by `model_date` as specified by FIRST.org
|
||||
|
||||
---
|
||||
|
||||
## Background: EPSS vs. CVSS Versioning
|
||||
|
||||
### CVSS (Common Vulnerability Scoring System)
|
||||
|
||||
CVSS uses **numbered major versions**:
|
||||
|
||||
```
|
||||
CVSS v2.0 (2007)
|
||||
↓
|
||||
CVSS v3.0 (2015)
|
||||
↓
|
||||
CVSS v3.1 (2019)
|
||||
↓
|
||||
CVSS v4.0 (2023)
|
||||
```
|
||||
|
||||
Each version has a distinct scoring formula, vector syntax, and metric definitions. CVSS vectors explicitly state the version:
|
||||
- `CVSS:2.0/AV:N/AC:L/...`
|
||||
- `CVSS:3.1/AV:N/AC:L/...`
|
||||
- `CVSS:4.0/AV:N/AC:L/...`
|
||||
|
||||
---
|
||||
|
||||
### EPSS (Exploit Prediction Scoring System)
|
||||
|
||||
EPSS uses **daily model dates** instead of version numbers:
|
||||
|
||||
```
|
||||
EPSS Model 2023-01-15
|
||||
↓
|
||||
EPSS Model 2023-06-20
|
||||
↓
|
||||
EPSS Model 2024-03-10
|
||||
↓
|
||||
EPSS Model 2025-12-19 (today)
|
||||
```
|
||||
|
||||
**Why daily models?**
|
||||
- EPSS is a **machine learning model** retrained daily
|
||||
- Scoring improves continuously based on new exploit data
|
||||
- No discrete "versions" - gradual model evolution
|
||||
- Each day's model produces slightly different scores
|
||||
|
||||
**FIRST.org Official Documentation:**
|
||||
- Uses `model_date` field (e.g., "2025-12-19")
|
||||
- No references to "EPSS v1", "EPSS v2", etc.
|
||||
- Scores include percentile ranking (relative to all CVEs on that date)
|
||||
|
||||
---
|
||||
|
||||
## EPSS Data Format (from FIRST.org)
|
||||
|
||||
### CSV Format (from https://epss.cyentia.com/epss_scores-YYYY-MM-DD.csv.gz)
|
||||
|
||||
```csv
|
||||
#model_version:v2023.03.01
|
||||
#score_date:2025-12-19
|
||||
cve,epss,percentile
|
||||
CVE-2024-12345,0.850000,0.990000
|
||||
CVE-2024-12346,0.020000,0.150000
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
- `model_version`: Model architecture version (e.g., v2023.03.01) - **not** EPSS version
|
||||
- `score_date`: Date scores were generated (daily)
|
||||
- `epss`: Probability [0.0, 1.0] of exploitation in next 30 days
|
||||
- `percentile`: Ranking [0.0, 1.0] relative to all scored CVEs
|
||||
|
||||
**Note:** `model_version` refers to the ML model architecture, not "EPSS v4"
|
||||
|
||||
---
|
||||
|
||||
## StellaOps Implementation
|
||||
|
||||
### Database Schema
|
||||
|
||||
**Table:** `concelier.epss_scores` (time-series, partitioned by month)
|
||||
|
||||
```sql
|
||||
CREATE TABLE concelier.epss_scores (
|
||||
tenant_id TEXT NOT NULL,
|
||||
cve_id TEXT NOT NULL,
|
||||
model_date DATE NOT NULL, -- ← Daily model date, not version number
|
||||
score DOUBLE PRECISION NOT NULL, -- 0.0-1.0
|
||||
percentile DOUBLE PRECISION NOT NULL, -- 0.0-1.0
|
||||
import_run_id TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (tenant_id, cve_id, model_date)
|
||||
) PARTITION BY RANGE (model_date);
|
||||
```
|
||||
|
||||
**Table:** `concelier.epss_current` (latest projection, ~300k rows)
|
||||
|
||||
```sql
|
||||
CREATE TABLE concelier.epss_current (
|
||||
tenant_id TEXT NOT NULL,
|
||||
cve_id TEXT NOT NULL,
|
||||
model_date DATE NOT NULL, -- Latest model date
|
||||
score DOUBLE PRECISION NOT NULL,
|
||||
percentile DOUBLE PRECISION NOT NULL,
|
||||
PRIMARY KEY (tenant_id, cve_id)
|
||||
);
|
||||
```
|
||||
|
||||
### Code Implementation
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Core/Epss/EpssEvidence.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record EpssEvidence
|
||||
{
|
||||
/// <summary>
|
||||
/// EPSS score [0.0, 1.0] representing probability of exploitation in next 30 days
|
||||
/// </summary>
|
||||
public required double Score { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Percentile [0.0, 1.0] ranking relative to all scored CVEs
|
||||
/// </summary>
|
||||
public required double Percentile { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Date of the EPSS model used to generate this score (daily updates)
|
||||
/// </summary>
|
||||
public required DateOnly ModelDate { get; init; } // ← Model date, not version
|
||||
|
||||
/// <summary>
|
||||
/// Immutable snapshot captured at scan time
|
||||
/// </summary>
|
||||
public required DateTimeOffset CapturedAt { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Storage/Epss/EpssProvider.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class EpssProvider : IEpssProvider
|
||||
{
|
||||
public async Task<EpssEvidence?> GetAsync(
|
||||
string tenantId,
|
||||
string cveId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Query: SELECT score, percentile, model_date FROM epss_current
|
||||
// WHERE tenant_id = @tenantId AND cve_id = @cveId
|
||||
}
|
||||
|
||||
public async Task<DateOnly?> GetLatestModelDateAsync(
|
||||
string tenantId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Returns the latest model_date in epss_current
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FIRST.org EPSS Specification Alignment
|
||||
|
||||
### Official EPSS Properties (from FIRST.org)
|
||||
|
||||
| Property | Type | Description | StellaOps Field |
|
||||
|----------|------|-------------|-----------------|
|
||||
| CVE ID | String | CVE identifier | `cve_id` |
|
||||
| EPSS Score | Float [0, 1] | Probability of exploitation in 30 days | `score` |
|
||||
| Percentile | Float [0, 1] | Ranking vs. all CVEs | `percentile` |
|
||||
| **Model Date** | Date (YYYY-MM-DD) | Date scores were generated | `model_date` ✅ |
|
||||
|
||||
**FIRST.org API Response (JSON):**
|
||||
|
||||
```json
|
||||
{
|
||||
"cve": "CVE-2024-12345",
|
||||
"epss": "0.850000",
|
||||
"percentile": "0.990000",
|
||||
"date": "2025-12-19"
|
||||
}
|
||||
```
|
||||
|
||||
**StellaOps Alignment:** ✅ **100% Compliant**
|
||||
- Uses `model_date` field (DATE type)
|
||||
- Stores score and percentile as specified
|
||||
- Daily ingestion at 00:05 UTC
|
||||
- Append-only time-series for historical tracking
|
||||
|
||||
---
|
||||
|
||||
## Where "EPSS v4" Terminology Comes From
|
||||
|
||||
### Common Confusion Sources
|
||||
|
||||
1. **CVSS v4 analogy:**
|
||||
- People familiar with "CVSS v4" assume similar naming for EPSS
|
||||
- **Reality:** EPSS doesn't follow this pattern
|
||||
|
||||
2. **Model architecture versions:**
|
||||
- FIRST.org references like "v2023.03.01" in CSV headers
|
||||
- These are **model architecture versions**, not "EPSS versions"
|
||||
- Model architecture changes infrequently (major ML model updates)
|
||||
|
||||
3. **Marketing/documentation shortcuts:**
|
||||
- "EPSS v4" used as shorthand for "current EPSS"
|
||||
- **Advisory context:** Likely means "EPSS as of 2025" or "current EPSS framework"
|
||||
|
||||
### Official FIRST.org Position
|
||||
|
||||
From **FIRST.org EPSS FAQ**:
|
||||
|
||||
> **Q: What version of EPSS is this?**
|
||||
>
|
||||
> A: EPSS does not have discrete versions like CVSS. The model is continuously updated with daily retraining. We provide a `model_date` field to track when scores were generated.
|
||||
|
||||
**Source:** [FIRST.org EPSS Documentation](https://www.first.org/epss/)
|
||||
|
||||
---
|
||||
|
||||
## StellaOps Documentation References to "EPSS v4"
|
||||
|
||||
### Locations Using "EPSS v4" Terminology
|
||||
|
||||
1. **Implementation Plan:** `docs/implplan/IMPL_3410_epss_v4_integration_master_plan.md`
|
||||
- Title references "EPSS v4"
|
||||
- **Interpretation:** "Current EPSS framework as of 2024-2025"
|
||||
- **Action:** Add clarification note
|
||||
|
||||
2. **Integration Guide:** `docs/guides/epss-integration-v4.md`
|
||||
- References "EPSS v4"
|
||||
- **Interpretation:** Same as above
|
||||
- **Action:** Add clarification section
|
||||
|
||||
3. **Sprint Files:** Multiple sprints reference "EPSS v4"
|
||||
- `SPRINT_3410_0001_0001_epss_ingestion_storage.md`
|
||||
- `SPRINT_3410_0002_0001_epss_scanner_integration.md`
|
||||
- `SPRINT_3413_0001_0001_epss_live_enrichment.md`
|
||||
- **Action:** Add footnote explaining terminology
|
||||
|
||||
### Recommended Clarification Template
|
||||
|
||||
```markdown
|
||||
### EPSS Versioning Note
|
||||
|
||||
**Terminology Clarification:** This document references "EPSS v4" as shorthand for the
|
||||
current EPSS methodology from FIRST.org. EPSS does not use numbered versions like CVSS.
|
||||
Instead, EPSS scores are tracked by daily `model_date`. StellaOps correctly implements
|
||||
EPSS using model dates as specified by FIRST.org.
|
||||
|
||||
For more details, see: `docs/architecture/epss-versioning-clarification.md`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advisory Alignment
|
||||
|
||||
### Advisory Requirement
|
||||
|
||||
> **EPSS v4** - daily model; 0-1 probability
|
||||
|
||||
**Interpretation:**
|
||||
- "EPSS v4" likely means "current EPSS framework"
|
||||
- Daily model ✅ Matches FIRST.org specification
|
||||
- 0-1 probability ✅ Matches FIRST.org specification
|
||||
|
||||
### StellaOps Compliance
|
||||
|
||||
✅ **Fully Compliant**
|
||||
- Daily ingestion from FIRST.org
|
||||
- Score range [0.0, 1.0] ✅
|
||||
- Percentile tracking ✅
|
||||
- Model date tracking ✅
|
||||
- Immutable at-scan evidence ✅
|
||||
- Air-gapped weekly bundles ✅
|
||||
- Historical time-series ✅
|
||||
|
||||
**Gap:** ❌ **None** - Implementation is correct per FIRST.org spec
|
||||
|
||||
**Terminology Note:** "EPSS v4" in advisory is conceptual; StellaOps correctly uses `model_date`
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### For StellaOps Documentation
|
||||
|
||||
1. **Add clarification notes** to documents referencing "EPSS v4":
|
||||
```markdown
|
||||
Note: "EPSS v4" is shorthand for current EPSS methodology. EPSS uses daily model_date, not version numbers.
|
||||
```
|
||||
|
||||
2. **Update sprint titles** (optional):
|
||||
- Current: "SPRINT_3410_0001_0001 · EPSS Ingestion & Storage"
|
||||
- Keep as-is (clear enough in context)
|
||||
- Add clarification in Overview section
|
||||
|
||||
3. **Create this clarification document** ✅ **DONE**
|
||||
- Reference from other docs
|
||||
- Include in architecture index
|
||||
|
||||
### For Advisory Alignment
|
||||
|
||||
1. **Document compliance** in alignment report:
|
||||
- StellaOps correctly implements EPSS per FIRST.org spec
|
||||
- Uses `model_date` field (not version numbers)
|
||||
- Advisory "EPSS v4" interpreted as "current EPSS"
|
||||
|
||||
2. **No code changes needed** ✅
|
||||
- Implementation is already correct
|
||||
- Documentation clarification is sufficient
|
||||
|
||||
---
|
||||
|
||||
## EPSS Scoring Integration in StellaOps
|
||||
|
||||
### Usage in Triage
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageRiskResult.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageRiskResult
|
||||
{
|
||||
public double? EpssScore { get; set; } // 0.0-1.0 probability
|
||||
public double? EpssPercentile { get; set; } // 0.0-1.0 ranking
|
||||
public DateOnly? EpssModelDate { get; set; } // Daily model date ✅
|
||||
}
|
||||
```
|
||||
|
||||
### Usage in Scoring
|
||||
|
||||
**Location:** `src/Signals/StellaOps.Signals/Services/ScoreExplanationService.cs`
|
||||
|
||||
```csharp
|
||||
// EPSS Contribution (lines 73-86)
|
||||
if (request.EpssScore.HasValue)
|
||||
{
|
||||
var epssContribution = request.EpssScore.Value * weights.EpssMultiplier;
|
||||
// Default multiplier: 10.0 (so 0.0-1.0 EPSS → 0-10 points)
|
||||
|
||||
explanation.Factors.Add(new ScoreFactor
|
||||
{
|
||||
Category = "ExploitProbability",
|
||||
Name = "EPSS Score",
|
||||
Value = request.EpssScore.Value,
|
||||
Contribution = epssContribution,
|
||||
Description = $"EPSS score {request.EpssScore.Value:P1} (model date: {request.EpssModelDate})"
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Usage in Unknowns
|
||||
|
||||
**Location:** `src/Unknowns/__Libraries/StellaOps.Unknowns.Core/Services/UnknownRanker.cs`
|
||||
|
||||
```csharp
|
||||
private double CalculateExploitPressure(UnknownRanking ranking)
|
||||
{
|
||||
// Default EPSS if unknown: 0.35 (median, conservative)
|
||||
var epss = ranking.EpssScore ?? 0.35;
|
||||
var kev = ranking.IsKev ? 0.30 : 0.0;
|
||||
return Math.Clamp(epss + kev, 0, 1);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## External References
|
||||
|
||||
### FIRST.org EPSS Resources
|
||||
|
||||
- **Main Page:** https://www.first.org/epss/
|
||||
- **CSV Download:** https://epss.cyentia.com/epss_scores-YYYY-MM-DD.csv.gz
|
||||
- **API Endpoint:** https://api.first.org/data/v1/epss?cve=CVE-YYYY-NNNNN
|
||||
- **Methodology Paper:** https://www.first.org/epss/articles/prob_percentile_bins.html
|
||||
- **FAQ:** https://www.first.org/epss/faq
|
||||
|
||||
### Academic Citations
|
||||
|
||||
- Jacobs, J., et al. (2021). "EPSS: A Data-Driven Vulnerability Prioritization Framework"
|
||||
- FIRST.org (2023). "EPSS Model v2023.03.01 Release Notes"
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Key Takeaways:**
|
||||
|
||||
1. ❌ **EPSS does NOT have numbered versions** (no "v1", "v2", "v3", "v4")
|
||||
2. ✅ **EPSS uses daily model dates** (`model_date` field)
|
||||
3. ✅ **StellaOps implementation is correct** per FIRST.org specification
|
||||
4. ⚠️ **"EPSS v4" is conceptual** - refers to current EPSS methodology
|
||||
5. ✅ **No code changes needed** - documentation clarification only
|
||||
|
||||
**Advisory Alignment:**
|
||||
- Advisory requirement: "EPSS v4 - daily model; 0-1 probability"
|
||||
- StellaOps implementation: ✅ **Fully compliant** with FIRST.org spec
|
||||
- Gap: ❌ **None** - terminology clarification only
|
||||
|
||||
**Recommended Action:**
|
||||
- Document this clarification
|
||||
- Add notes to existing docs referencing "EPSS v4"
|
||||
- Include in alignment report
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Changes | Author |
|
||||
|---------|------|---------|--------|
|
||||
| 1.0 | 2025-12-19 | Initial clarification document | Claude Code |
|
||||
|
||||
---
|
||||
|
||||
## Related Documents
|
||||
|
||||
- `docs/implplan/SPRINT_5000_0001_0001_advisory_alignment.md` - Parent sprint
|
||||
- `docs/architecture/signal-contract-mapping.md` - Signal contract mapping
|
||||
- `docs/guides/epss-integration-v4.md` - EPSS integration guide (to be updated)
|
||||
- `docs/implplan/IMPL_3410_epss_v4_integration_master_plan.md` - EPSS implementation plan (to be updated)
|
||||
- `docs/risk/formulas.md` - Scoring formulas including EPSS
|
||||
|
||||
---
|
||||
|
||||
**END OF DOCUMENT**
|
||||
@@ -1,215 +0,0 @@
|
||||
# Integration Catalog Architecture
|
||||
|
||||
> **Module:** Integrations (`src/Integrations/StellaOps.Integrations.WebService`)
|
||||
> **Sprint:** SPRINT_20251229_010_PLATFORM_integration_catalog_core
|
||||
> **Last Updated:** 2025-12-30
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Integration Catalog is a centralized registry for managing external integrations in StellaOps. It provides a unified API for configuring, testing, and monitoring connections to registries, SCM providers, CI systems, runtime hosts, and feed sources.
|
||||
|
||||
**Architecture Note:** Integration Catalog is a dedicated service (`src/Integrations`), NOT part of Gateway. Gateway handles HTTP ingress/routing only. Integration domain logic, plugins, and persistence live in the Integrations module.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
src/Integrations/
|
||||
├── StellaOps.Integrations.WebService/ # ASP.NET Core host
|
||||
├── __Libraries/
|
||||
│ ├── StellaOps.Integrations.Core/ # Domain models, enums, events
|
||||
│ ├── StellaOps.Integrations.Contracts/ # Plugin contracts and DTOs
|
||||
│ └── StellaOps.Integrations.Persistence/ # PostgreSQL repositories
|
||||
└── __Plugins/
|
||||
├── StellaOps.Integrations.Plugin.GitHubApp/
|
||||
├── StellaOps.Integrations.Plugin.Harbor/
|
||||
└── StellaOps.Integrations.Plugin.InMemory/
|
||||
```
|
||||
|
||||
## Plugin Architecture
|
||||
|
||||
Each integration provider is implemented as a plugin that implements `IIntegrationConnectorPlugin`:
|
||||
|
||||
```csharp
|
||||
public interface IIntegrationConnectorPlugin : IAvailabilityPlugin
|
||||
{
|
||||
IntegrationType Type { get; }
|
||||
IntegrationProvider Provider { get; }
|
||||
Task<TestConnectionResult> TestConnectionAsync(IntegrationConfig config, CancellationToken ct);
|
||||
Task<HealthCheckResult> CheckHealthAsync(IntegrationConfig config, CancellationToken ct);
|
||||
}
|
||||
```
|
||||
|
||||
Plugins are loaded at startup from:
|
||||
1. The configured `PluginsDirectory` (default: `plugins/`)
|
||||
2. The WebService assembly (for built-in plugins)
|
||||
|
||||
## Integration Types
|
||||
|
||||
| Type | Description | Examples |
|
||||
|------|-------------|----------|
|
||||
| **Registry** | Container image registries | Docker Hub, Harbor, ECR, ACR, GCR, GHCR, Quay, Artifactory |
|
||||
| **SCM** | Source code management | GitHub, GitLab, Gitea, Bitbucket, Azure DevOps |
|
||||
| **CI** | Continuous integration | GitHub Actions, GitLab CI, Gitea Actions, Jenkins, CircleCI |
|
||||
| **Host** | Runtime observation | Zastava (eBPF, ETW, dyld probes) |
|
||||
| **Feed** | Vulnerability feeds | Concelier, Excititor mirrors |
|
||||
| **Artifact** | SBOM/VEX uploads | Direct artifact submission |
|
||||
|
||||
## Entity Schema
|
||||
|
||||
```csharp
|
||||
public sealed class Integration
|
||||
{
|
||||
// Identity
|
||||
public Guid IntegrationId { get; init; }
|
||||
public string TenantId { get; init; }
|
||||
public string Name { get; init; }
|
||||
public string? Description { get; set; }
|
||||
|
||||
// Classification
|
||||
public IntegrationType Type { get; init; }
|
||||
public IntegrationProvider Provider { get; init; }
|
||||
|
||||
// Configuration
|
||||
public string? BaseUrl { get; set; }
|
||||
public string? AuthRef { get; set; } // Never raw secrets
|
||||
public JsonDocument Configuration { get; set; }
|
||||
|
||||
// Organization
|
||||
public string? Environment { get; set; } // prod, staging, dev
|
||||
public string? Tags { get; set; }
|
||||
public string? OwnerId { get; set; }
|
||||
|
||||
// Lifecycle
|
||||
public IntegrationStatus Status { get; private set; }
|
||||
public bool Paused { get; private set; }
|
||||
public string? PauseReason { get; private set; }
|
||||
|
||||
// Health
|
||||
public DateTimeOffset? LastTestedAt { get; private set; }
|
||||
public bool? LastTestSuccess { get; private set; }
|
||||
public int ConsecutiveFailures { get; private set; }
|
||||
|
||||
// Audit
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
public string CreatedBy { get; init; }
|
||||
public DateTimeOffset? ModifiedAt { get; private set; }
|
||||
public string? ModifiedBy { get; private set; }
|
||||
public int Version { get; private set; }
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle States
|
||||
|
||||
```
|
||||
┌─────────┐
|
||||
│ Draft │ ──── SubmitForVerification() ────►
|
||||
└─────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ PendingVerification│ ──── Test Success ────►
|
||||
└───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│ Active │ ◄──── Resume() ────┐
|
||||
└──────────┘ │
|
||||
│ │
|
||||
Consecutive ┌─────────┐
|
||||
Failures ≥ 3 │ Paused │
|
||||
│ └─────────┘
|
||||
▼ ▲
|
||||
┌───────────┐ │
|
||||
│ Degraded │ ──── Pause() ───────┘
|
||||
└───────────┘
|
||||
│
|
||||
Failures ≥ 5
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│ Failed │
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
Base path: `/api/v1/integrations`
|
||||
|
||||
| Method | Path | Scope | Description |
|
||||
|--------|------|-------|-------------|
|
||||
| GET | `/` | `integrations.read` | List integrations with filtering |
|
||||
| GET | `/{id}` | `integrations.read` | Get integration by ID |
|
||||
| POST | `/` | `integrations.admin` | Create integration |
|
||||
| PUT | `/{id}` | `integrations.admin` | Update integration |
|
||||
| DELETE | `/{id}` | `integrations.admin` | Delete integration |
|
||||
| POST | `/{id}/test` | `integrations.admin` | Test connection |
|
||||
| POST | `/{id}/pause` | `integrations.admin` | Pause integration |
|
||||
| POST | `/{id}/resume` | `integrations.admin` | Resume integration |
|
||||
| POST | `/{id}/activate` | `integrations.admin` | Activate integration |
|
||||
| GET | `/{id}/health` | `integrations.read` | Get health status |
|
||||
|
||||
## AuthRef Pattern
|
||||
|
||||
**Critical:** The Integration Catalog never stores raw credentials. All secrets are referenced via `AuthRef` strings that point to Authority's secret store.
|
||||
|
||||
```
|
||||
AuthRef format: ref://<scope>/<provider>/<key>
|
||||
Example: ref://integrations/github/acme-org-token
|
||||
```
|
||||
|
||||
The AuthRef is resolved at runtime when making API calls to the integration provider. This ensures:
|
||||
|
||||
1. Secrets are stored centrally with proper encryption
|
||||
2. Secret rotation doesn't require integration updates
|
||||
3. Audit trails track secret access separately
|
||||
4. Offline bundles can use different AuthRefs
|
||||
|
||||
## Event Pipeline
|
||||
|
||||
Integration lifecycle events are published for consumption by Scheduler and Orchestrator:
|
||||
|
||||
| Event | Trigger | Consumers |
|
||||
|-------|---------|-----------|
|
||||
| `integration.created` | New integration | Scheduler (schedule health checks) |
|
||||
| `integration.updated` | Configuration change | Scheduler (reschedule) |
|
||||
| `integration.deleted` | Integration removed | Scheduler (cancel jobs) |
|
||||
| `integration.paused` | Operator paused | Orchestrator (pause jobs) |
|
||||
| `integration.resumed` | Operator resumed | Orchestrator (resume jobs) |
|
||||
| `integration.healthy` | Test passed | Signals (status update) |
|
||||
| `integration.unhealthy` | Test failed | Signals, Notify (alert) |
|
||||
|
||||
## Audit Trail
|
||||
|
||||
All integration actions are logged:
|
||||
|
||||
- Create/Update/Delete with actor and timestamp
|
||||
- Connection tests with success/failure
|
||||
- Pause/Resume with reason and ticket reference
|
||||
- Activate with approver
|
||||
|
||||
Audit logs are stored in the append-only audit store for compliance.
|
||||
|
||||
## Determinism & Offline
|
||||
|
||||
- Integration lists are ordered deterministically by name
|
||||
- Timestamps are UTC ISO-8601
|
||||
- Pagination uses stable cursor semantics
|
||||
- Health polling respects offline mode (skip network checks)
|
||||
- Feed integrations support allowlists for air-gap environments
|
||||
|
||||
## RBAC Scopes
|
||||
|
||||
| Scope | Permission |
|
||||
|-------|------------|
|
||||
| `integrations.read` | View integrations and health |
|
||||
| `integrations.admin` | Create, update, delete, test, pause, resume |
|
||||
|
||||
## Future Extensions
|
||||
|
||||
1. **Provider-specific testers**: HTTP health checks, registry auth validation, SCM webhook verification
|
||||
2. **PostgreSQL persistence**: Replace in-memory repository for production
|
||||
3. **Messaging events**: Publish to Valkey/Kafka instead of no-op
|
||||
4. **Health history**: Track uptime percentage and latency over time
|
||||
5. **Bulk operations**: Import/export integrations for environment promotion
|
||||
@@ -1,964 +0,0 @@
|
||||
# Signal Contract Mapping: Advisory ↔ StellaOps
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-19
|
||||
**Status:** ACTIVE
|
||||
**Related Sprint:** SPRINT_5000_0001_0001
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document provides a comprehensive mapping between the reference advisory's **Signal-based message contracts (10/12/14/16/18)** and the **StellaOps implementation**. While StellaOps uses domain-specific terminology, all signal concepts are fully implemented with equivalent or superior functionality.
|
||||
|
||||
**Key Insight:** StellaOps implements the same architectural patterns as the advisory but uses domain-specific entity names instead of generic "Signal-X" labels. This provides better type safety, code readability, and domain modeling while maintaining conceptual alignment.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Table
|
||||
|
||||
| Advisory Signal | StellaOps Equivalent | Module | Key Files |
|
||||
|----------------|---------------------|---------|-----------|
|
||||
| **Signal-10** (SBOM Intake) | `CallgraphIngestRequest`, `ISbomIngestionService` | Scanner, Signals | `SbomIngestionService.cs`, `CallgraphIngestRequest.cs` |
|
||||
| **Signal-12** (Evidence/Attestation) | in-toto `Statement` + DSSE | Attestor, Signer | `InTotoStatement.cs`, `DsseEnvelope.cs`, 19 predicate types |
|
||||
| **Signal-14** (Triage Fact) | `TriageFinding` + related entities | Scanner.Triage | `TriageFinding.cs`, `TriageReachabilityResult.cs`, `TriageRiskResult.cs`, `TriageEffectiveVex.cs` |
|
||||
| **Signal-16** (Diff Delta) | `TriageSnapshot`, `MaterialRiskChange`, `DriftCause` | Scanner.SmartDiff, ReachabilityDrift | `MaterialRiskChangeDetector.cs`, `ReachabilityDriftDetector.cs`, `TriageSnapshot.cs` |
|
||||
| **Signal-18** (Decision) | `TriageDecision` + DSSE signatures | Scanner.Triage | `TriageDecision.cs`, `TriageEvidenceArtifact.cs` |
|
||||
|
||||
---
|
||||
|
||||
## Signal-10: SBOM Intake
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"bom": "(cyclonedx:1.7)",
|
||||
"subject": {
|
||||
"image": "ghcr.io/org/app@sha256:...",
|
||||
"digest": "sha256:..."
|
||||
},
|
||||
"source": "scanner-instance-1",
|
||||
"scanProfile": "default",
|
||||
"createdAt": "2025-12-19T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Initial SBOM ingestion with subject identification
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Contract:** `CallgraphIngestRequest`
|
||||
|
||||
**Location:** `src/Signals/StellaOps.Signals/Models/CallgraphIngestRequest.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record CallgraphIngestRequest
|
||||
{
|
||||
public required string TenantId { get; init; }
|
||||
public required string ArtifactDigest { get; init; } // Maps to "subject.digest"
|
||||
public required string Language { get; init; }
|
||||
public required string Component { get; init; }
|
||||
public required string? Version { get; init; }
|
||||
public required string ArtifactContentBase64 { get; init; } // Maps to "bom" (encoded)
|
||||
public string? SchemaVersion { get; init; }
|
||||
public IReadOnlyDictionary<string, string>? Metadata { get; init; } // Includes "source", "scanProfile"
|
||||
}
|
||||
```
|
||||
|
||||
**Service Interface:** `ISbomIngestionService`
|
||||
|
||||
**Location:** `src/Scanner/StellaOps.Scanner.WebService/Services/ISbomIngestionService.cs`
|
||||
|
||||
```csharp
|
||||
public interface ISbomIngestionService
|
||||
{
|
||||
Task<SbomIngestionResult> IngestCycloneDxAsync(
|
||||
string tenantId,
|
||||
Stream cycloneDxJson,
|
||||
SbomIngestionOptions options,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<SbomIngestionResult> IngestSpdxAsync(
|
||||
string tenantId,
|
||||
Stream spdxJson,
|
||||
SbomIngestionOptions options,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
```
|
||||
|
||||
**Data Flow:**
|
||||
|
||||
```
|
||||
[Scanner] → SbomIngestionService → [CycloneDxComposer/SpdxComposer]
|
||||
↓
|
||||
PostgreSQL (scanner.sboms)
|
||||
↓
|
||||
Event: "sbom.ingested"
|
||||
↓
|
||||
[Downstream processors]
|
||||
```
|
||||
|
||||
**API Endpoints:**
|
||||
|
||||
- `POST /api/scanner/sboms/ingest` - Direct SBOM ingestion
|
||||
- `POST /api/signals/callgraph/ingest` - Call graph + SBOM ingestion
|
||||
|
||||
**Related Files:**
|
||||
- `src/Scanner/StellaOps.Scanner.WebService/Endpoints/SbomEndpoints.cs`
|
||||
- `src/Signals/StellaOps.Signals/Services/CallgraphIngestionService.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs`
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ BOM content: CycloneDX 1.7
|
||||
- ✅ Subject identification: `ArtifactDigest` (SHA-256)
|
||||
- ✅ Source tracking: `Metadata["source"]`
|
||||
- ✅ Profile support: `SbomIngestionOptions.ScanProfile`
|
||||
- ✅ Timestamp: `CreatedAt` in database entity
|
||||
|
||||
---
|
||||
|
||||
## Signal-12: Evidence/Attestation (in-toto Statement)
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": {"digest": {"sha256": "..."}},
|
||||
"type": "attestation",
|
||||
"predicateType": "https://slsa.dev/provenance/v1",
|
||||
"predicate": {...},
|
||||
"materials": [],
|
||||
"tool": "scanner@1.0.0",
|
||||
"runId": "run-123",
|
||||
"startedAt": "2025-12-19T10:00:00Z",
|
||||
"finishedAt": "2025-12-19T10:05:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Evidence envelopes for attestations (DSSE-wrapped)
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Contract:** `InTotoStatement` (abstract base)
|
||||
|
||||
**Location:** `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/Statements/InTotoStatement.cs`
|
||||
|
||||
```csharp
|
||||
public abstract record InTotoStatement
|
||||
{
|
||||
[JsonPropertyName("_type")]
|
||||
public string Type => "https://in-toto.io/Statement/v1";
|
||||
|
||||
[JsonPropertyName("subject")]
|
||||
public required IReadOnlyList<Subject> Subject { get; init; }
|
||||
|
||||
[JsonPropertyName("predicateType")]
|
||||
public abstract string PredicateType { get; }
|
||||
}
|
||||
```
|
||||
|
||||
**DSSE Envelope:** `DsseEnvelope`
|
||||
|
||||
**Location:** `src/Attestor/StellaOps.Attestor.Envelope/DsseEnvelope.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record DsseEnvelope
|
||||
{
|
||||
[JsonPropertyName("payload")]
|
||||
public required string Payload { get; init; } // Base64url(canonical JSON of Statement)
|
||||
|
||||
[JsonPropertyName("payloadType")]
|
||||
public required string PayloadType { get; init; } // "application/vnd.in-toto+json"
|
||||
|
||||
[JsonPropertyName("signatures")]
|
||||
public required IReadOnlyList<DsseSignature> Signatures { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
**Predicate Types Registry:** 19 types supported
|
||||
|
||||
**Location:** `src/Signer/StellaOps.Signer/StellaOps.Signer.Core/PredicateTypes.cs`
|
||||
|
||||
```csharp
|
||||
public static class PredicateTypes
|
||||
{
|
||||
// SLSA (Standard)
|
||||
public const string SlsaProvenanceV02 = "https://slsa.dev/provenance/v0.2";
|
||||
public const string SlsaProvenanceV1 = "https://slsa.dev/provenance/v1";
|
||||
|
||||
// StellaOps Custom
|
||||
public const string StellaOpsSbom = "stella.ops/sbom@v1";
|
||||
public const string StellaOpsVex = "stella.ops/vex@v1";
|
||||
public const string StellaOpsEvidence = "stella.ops/evidence@v1";
|
||||
public const string StellaOpsPathWitness = "stella.ops/pathWitness@v1";
|
||||
public const string StellaOpsReachabilityWitness = "stella.ops/reachabilityWitness@v1";
|
||||
public const string StellaOpsReachabilityDrift = "stellaops.dev/predicates/reachability-drift@v1";
|
||||
public const string StellaOpsPolicyDecision = "stella.ops/policy-decision@v1";
|
||||
// ... 12 more predicate types
|
||||
}
|
||||
```
|
||||
|
||||
**Signing Service:** `CryptoDsseSigner`
|
||||
|
||||
**Location:** `src/Signer/StellaOps.Signer/StellaOps.Signer.Infrastructure/Signing/CryptoDsseSigner.cs`
|
||||
|
||||
**Data Flow:**
|
||||
|
||||
```
|
||||
[Component] → ProofChainSigner → [Build in-toto Statement]
|
||||
↓
|
||||
Canonical JSON serialization
|
||||
↓
|
||||
DSSE PAE construction
|
||||
↓
|
||||
CryptoDsseSigner (KMS/Keyless)
|
||||
↓
|
||||
DsseEnvelope (signed)
|
||||
↓
|
||||
PostgreSQL (attestor.envelopes)
|
||||
↓
|
||||
Optional: Rekor transparency log
|
||||
```
|
||||
|
||||
**Sample Attestation Files:**
|
||||
- `src/Attestor/StellaOps.Attestor.Types/samples/build-provenance.v1.json`
|
||||
- `src/Attestor/StellaOps.Attestor.Types/samples/vex-attestation.v1.json`
|
||||
- `src/Attestor/StellaOps.Attestor.Types/samples/scan-results.v1.json`
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ Subject: `Subject` list with digests
|
||||
- ✅ Type: `https://in-toto.io/Statement/v1`
|
||||
- ✅ PredicateType: 19 supported types
|
||||
- ✅ Predicate: Custom per type
|
||||
- ✅ Tool: Embedded in predicate metadata
|
||||
- ✅ RunId: `TraceId` / `CorrelationId`
|
||||
- ✅ Timestamps: In predicate metadata
|
||||
- ✅ DSSE wrapping: Full implementation
|
||||
|
||||
---
|
||||
|
||||
## Signal-14: Triage Fact
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": "pkg:npm/lodash@4.17.0",
|
||||
"cve": "CVE-2024-12345",
|
||||
"findingId": "cve@package@symbol@subjectDigest",
|
||||
"location": {
|
||||
"file": "src/index.js",
|
||||
"package": "lodash",
|
||||
"symbol": "template"
|
||||
},
|
||||
"reachability": {
|
||||
"status": "reachable",
|
||||
"callStackId": "cs-abc123"
|
||||
},
|
||||
"epss": 0.85,
|
||||
"cvss": {
|
||||
"version": "4.0",
|
||||
"vector": "CVSS:4.0/AV:N/AC:L/...",
|
||||
"score": 7.5
|
||||
},
|
||||
"vexStatus": "affected",
|
||||
"notes": "...",
|
||||
"evidenceRefs": ["dsse://sha256:..."]
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Triage facts per CVE with reachability, scoring, and VEX status
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Entity:** `TriageFinding` (core entity tying all triage data)
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageFinding.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageFinding
|
||||
{
|
||||
public string FindingId { get; set; } // Stable ID: "cve@purl@scanId"
|
||||
public string TenantId { get; set; }
|
||||
public string AssetId { get; set; } // Maps to "subject"
|
||||
public string? Purl { get; set; } // Package URL
|
||||
public string? CveId { get; set; } // Maps to "cve"
|
||||
public string? RuleId { get; set; } // For non-CVE findings
|
||||
|
||||
public DateTimeOffset FirstSeenAt { get; set; }
|
||||
public DateTimeOffset LastSeenAt { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public TriageReachabilityResult? Reachability { get; set; }
|
||||
public TriageRiskResult? Risk { get; set; }
|
||||
public TriageEffectiveVex? EffectiveVex { get; set; }
|
||||
public ICollection<TriageEvidenceArtifact> EvidenceArtifacts { get; set; }
|
||||
public ICollection<TriageDecision> Decisions { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Reachability Component:** `TriageReachabilityResult`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageReachabilityResult.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageReachabilityResult
|
||||
{
|
||||
public string ResultId { get; set; }
|
||||
public string FindingId { get; set; }
|
||||
|
||||
public TriageReachability Reachability { get; set; } // Yes, No, Unknown
|
||||
public int Confidence { get; set; } // 0-100
|
||||
|
||||
public string? StaticProofRef { get; set; } // Maps to "callStackId"
|
||||
public string? RuntimeProofRef { get; set; }
|
||||
|
||||
public string InputsHash { get; set; } // For caching/diffing
|
||||
public DateTimeOffset ComputedAt { get; set; }
|
||||
|
||||
// Lattice evaluation
|
||||
public double? LatticeScore { get; set; }
|
||||
public string? LatticeState { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Risk/Scoring Component:** `TriageRiskResult`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageRiskResult.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageRiskResult
|
||||
{
|
||||
public string ResultId { get; set; }
|
||||
public string FindingId { get; set; }
|
||||
|
||||
// Scoring
|
||||
public double RiskScore { get; set; } // Combined score
|
||||
public double? CvssBaseScore { get; set; }
|
||||
public string? CvssVector { get; set; } // CVSS:4.0/AV:N/...
|
||||
public string? CvssVersion { get; set; } // "4.0"
|
||||
|
||||
public double? EpssScore { get; set; } // Maps to "epss"
|
||||
public double? EpssPercentile { get; set; }
|
||||
public DateOnly? EpssModelDate { get; set; }
|
||||
|
||||
// Policy decision
|
||||
public TriageVerdict Verdict { get; set; } // Ship, Block, Exception
|
||||
public string? PolicyId { get; set; }
|
||||
public string Lane { get; set; } // Critical, High, Medium, Low
|
||||
|
||||
public string InputsHash { get; set; }
|
||||
public string? LatticeExplanationJson { get; set; } // Maps to "notes"
|
||||
public DateTimeOffset ComputedAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**VEX Component:** `TriageEffectiveVex`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageEffectiveVex.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageEffectiveVex
|
||||
{
|
||||
public string VexId { get; set; }
|
||||
public string FindingId { get; set; }
|
||||
|
||||
public VexClaimStatus Status { get; set; } // Maps to "vexStatus"
|
||||
public VexJustification? Justification { get; set; }
|
||||
|
||||
public string? ProvenancePointer { get; set; } // Linkset reference
|
||||
public string? DsseEnvelopeHash { get; set; } // Maps to "evidenceRefs"
|
||||
|
||||
public DateTimeOffset EffectiveAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Evidence Artifacts:** `TriageEvidenceArtifact`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageEvidenceArtifact.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageEvidenceArtifact
|
||||
{
|
||||
public string ArtifactId { get; set; }
|
||||
public string FindingId { get; set; }
|
||||
|
||||
public string ContentHash { get; set; } // SHA-256
|
||||
public string? SignatureRef { get; set; } // DSSE envelope reference
|
||||
public string? CasUri { get; set; } // cas://reachability/graphs/{hash}
|
||||
|
||||
public string MediaType { get; set; }
|
||||
public long SizeBytes { get; set; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Database Schema:**
|
||||
- Table: `scanner.triage_findings` (core table)
|
||||
- Table: `scanner.triage_reachability_results` (1:1 with findings)
|
||||
- Table: `scanner.triage_risk_results` (1:1 with findings)
|
||||
- Table: `scanner.triage_effective_vex` (1:1 with findings)
|
||||
- Table: `scanner.triage_evidence_artifacts` (1:N with findings)
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ Subject: `AssetId` + `Purl`
|
||||
- ✅ CVE: `CveId`
|
||||
- ✅ Finding ID: `FindingId` (stable scheme)
|
||||
- ✅ Location: Embedded in evidence artifacts
|
||||
- ✅ Reachability: Full `TriageReachabilityResult` entity
|
||||
- ✅ EPSS: `EpssScore`, `EpssPercentile`, `EpssModelDate`
|
||||
- ✅ CVSS: `CvssBaseScore`, `CvssVector`, `CvssVersion`
|
||||
- ✅ VEX Status: `TriageEffectiveVex.Status`
|
||||
- ✅ Notes: `LatticeExplanationJson`
|
||||
- ✅ Evidence Refs: `TriageEvidenceArtifact` with `ContentHash`, `CasUri`
|
||||
|
||||
---
|
||||
|
||||
## Signal-16: Diff Delta
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": "ghcr.io/org/app",
|
||||
"fromVersion": "1.0.0",
|
||||
"toVersion": "1.1.0",
|
||||
"changed": {
|
||||
"packages": ["lodash@4.17.0→4.17.21"],
|
||||
"files": ["src/util.js"],
|
||||
"symbols": ["template"],
|
||||
"vulns": [{"cve": "CVE-2024-12345", "action": "fixed"}]
|
||||
},
|
||||
"explainableReasons": [
|
||||
{
|
||||
"reasonCode": "VEX_STATUS_FLIP",
|
||||
"params": {"from": "affected", "to": "fixed"},
|
||||
"evidenceRefs": ["dsse://..."]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Minimal deltas between SBOM snapshots with explainable reasons
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Entity:** `TriageSnapshot`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageSnapshot.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageSnapshot
|
||||
{
|
||||
public string SnapshotId { get; set; }
|
||||
public string TenantId { get; set; }
|
||||
public string AssetId { get; set; }
|
||||
|
||||
// Version tracking
|
||||
public string? FromVersion { get; set; } // Maps to "fromVersion"
|
||||
public string? ToVersion { get; set; } // Maps to "toVersion"
|
||||
public string FromScanId { get; set; }
|
||||
public string ToScanId { get; set; }
|
||||
|
||||
// Input/output hashes for diffing
|
||||
public string FromInputsHash { get; set; }
|
||||
public string ToInputsHash { get; set; }
|
||||
|
||||
// Precomputed diff
|
||||
public string? DiffJson { get; set; } // Maps to "changed"
|
||||
|
||||
// Trigger tracking
|
||||
public string? Trigger { get; set; } // Manual, Scheduled, EventDriven
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Smart-Diff Detector:** `MaterialRiskChangeDetector`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.SmartDiff/Detection/MaterialRiskChangeDetector.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class MaterialRiskChangeDetector
|
||||
{
|
||||
// Detection rules
|
||||
public IReadOnlyList<DetectedChange> Detect(
|
||||
RiskStateSnapshot previous,
|
||||
RiskStateSnapshot current)
|
||||
{
|
||||
// R1: Reachability flip
|
||||
// R2: VEX status flip
|
||||
// R3: Range boundary cross
|
||||
// R4: Intelligence/Policy flip
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Risk State Snapshot:** `RiskStateSnapshot`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.SmartDiff/Detection/RiskStateSnapshot.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record RiskStateSnapshot
|
||||
{
|
||||
public bool? Reachable { get; init; }
|
||||
public VexClaimStatus VexStatus { get; init; }
|
||||
public bool? InAffectedRange { get; init; }
|
||||
public bool Kev { get; init; }
|
||||
public double? EpssScore { get; init; }
|
||||
public PolicyDecision PolicyDecision { get; init; }
|
||||
public string? LatticeState { get; init; }
|
||||
|
||||
// SHA-256 hash for deterministic change detection
|
||||
public string ComputeHash() => SHA256.Hash(CanonicalJson);
|
||||
}
|
||||
```
|
||||
|
||||
**Reachability Drift Detector:** `ReachabilityDriftDetector`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/Services/ReachabilityDriftDetector.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class ReachabilityDriftDetector
|
||||
{
|
||||
public Task<DriftDetectionResult> DetectAsync(
|
||||
string baseScanId,
|
||||
string headScanId,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
```
|
||||
|
||||
**Drift Cause Explainer:** `DriftCauseExplainer`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/Services/DriftCauseExplainer.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class DriftCauseExplainer
|
||||
{
|
||||
// Explains why reachability changed
|
||||
public DriftCause Explain(
|
||||
CallGraphSnapshot baseGraph,
|
||||
CallGraphSnapshot headGraph,
|
||||
string sinkId);
|
||||
}
|
||||
|
||||
public sealed record DriftCause
|
||||
{
|
||||
public DriftCauseKind Kind { get; init; } // Maps to "reasonCode"
|
||||
public string Description { get; init; }
|
||||
public string? ChangedSymbol { get; init; }
|
||||
public string? ChangedFile { get; init; }
|
||||
public int? ChangedLine { get; init; }
|
||||
public string? CodeChangeId { get; init; } // Maps to "evidenceRefs"
|
||||
}
|
||||
|
||||
public enum DriftCauseKind
|
||||
{
|
||||
GuardRemoved, // "GUARD_REMOVED"
|
||||
NewPublicRoute, // "NEW_PUBLIC_ROUTE"
|
||||
VisibilityEscalated, // "VISIBILITY_ESCALATED"
|
||||
DependencyUpgraded, // "DEPENDENCY_UPGRADED"
|
||||
SymbolRemoved, // "SYMBOL_REMOVED"
|
||||
GuardAdded, // "GUARD_ADDED"
|
||||
Unknown // "UNKNOWN"
|
||||
}
|
||||
```
|
||||
|
||||
**API Endpoints:**
|
||||
- `GET /smart-diff/scans/{scanId}/changes` - Material risk changes
|
||||
- `GET /smart-diff/scans/{scanId}/sarif` - SARIF 2.1.0 format
|
||||
- `GET /smart-diff/images/{digest}/candidates` - VEX candidates
|
||||
|
||||
**Database Schema:**
|
||||
- Table: `scanner.triage_snapshots`
|
||||
- Table: `scanner.risk_state_snapshots`
|
||||
- Table: `scanner.material_risk_changes`
|
||||
- Table: `scanner.call_graph_snapshots`
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ Subject: `AssetId`
|
||||
- ✅ From/To Version: `FromVersion`, `ToVersion`
|
||||
- ✅ Changed packages: In `DiffJson` + package-level diffs
|
||||
- ✅ Changed symbols: Reachability drift detection
|
||||
- ✅ Changed vulns: Material risk changes
|
||||
- ✅ Explainable reasons: `DriftCause` with `Kind` (reason code)
|
||||
- ✅ Evidence refs: `CodeChangeId`, evidence artifacts
|
||||
|
||||
---
|
||||
|
||||
## Signal-18: Decision
|
||||
|
||||
### Advisory Specification
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": "pkg:npm/lodash@4.17.0",
|
||||
"decisionId": "dec-abc123",
|
||||
"severity": "HIGH",
|
||||
"priority": 85,
|
||||
"rationale": [
|
||||
"Reachable from public API",
|
||||
"EPSS above threshold (0.85)",
|
||||
"No VEX from vendor"
|
||||
],
|
||||
"actions": ["Block deployment", "Notify security team"],
|
||||
"dsseSignatures": ["dsse://sha256:..."]
|
||||
}
|
||||
```
|
||||
|
||||
**Purpose:** Policy decisions with rationale and signatures
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Primary Entity:** `TriageDecision`
|
||||
|
||||
**Location:** `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/TriageDecision.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageDecision
|
||||
{
|
||||
public string DecisionId { get; set; } // Maps to "decisionId"
|
||||
public string FindingId { get; set; } // Links to TriageFinding (subject)
|
||||
public string TenantId { get; set; }
|
||||
|
||||
// Decision details
|
||||
public TriageDecisionKind Kind { get; set; } // Mute, Acknowledge, Exception
|
||||
public string? Reason { get; set; } // Maps to "rationale"
|
||||
public string? ReasonCode { get; set; }
|
||||
|
||||
// Actor
|
||||
public string ActorSubject { get; set; }
|
||||
public string? ActorDisplayName { get; set; }
|
||||
|
||||
// Policy reference
|
||||
public string? PolicyRef { get; set; }
|
||||
|
||||
// Lifetime
|
||||
public DateTimeOffset EffectiveAt { get; set; }
|
||||
public DateTimeOffset? ExpiresAt { get; set; }
|
||||
public int? TtlDays { get; set; }
|
||||
|
||||
// Reversibility
|
||||
public bool Revoked { get; set; }
|
||||
public DateTimeOffset? RevokedAt { get; set; }
|
||||
public string? RevokedBy { get; set; }
|
||||
|
||||
// Signatures
|
||||
public string? DsseEnvelopeHash { get; set; } // Maps to "dsseSignatures"
|
||||
public string? SignatureRef { get; set; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**Risk Result (includes severity/priority):** From `TriageRiskResult`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageRiskResult
|
||||
{
|
||||
public double RiskScore { get; set; } // Maps to "priority" (0-100)
|
||||
public string Lane { get; set; } // Maps to "severity" (Critical/High/Medium/Low)
|
||||
public TriageVerdict Verdict { get; set; } // Maps to "actions" (Ship/Block/Exception)
|
||||
public string? LatticeExplanationJson { get; set; } // Maps to "rationale" (structured)
|
||||
}
|
||||
```
|
||||
|
||||
**Score Explanation Service:** `ScoreExplanationService`
|
||||
|
||||
**Location:** `src/Signals/StellaOps.Signals/Services/ScoreExplanationService.cs`
|
||||
|
||||
```csharp
|
||||
public sealed class ScoreExplanationService
|
||||
{
|
||||
// Generates structured rationale
|
||||
public ScoreExplanation Explain(ScoreExplanationRequest request)
|
||||
{
|
||||
// Returns breakdown of:
|
||||
// - CVSS contribution
|
||||
// - EPSS contribution
|
||||
// - Reachability contribution
|
||||
// - VEX reduction
|
||||
// - Gate discounts
|
||||
// - KEV bonus
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Decision Predicate Type:** `stella.ops/policy-decision@v1`
|
||||
|
||||
**Location:** Defined in `PredicateTypes.cs`, implemented in attestations
|
||||
|
||||
**Database Schema:**
|
||||
- Table: `scanner.triage_decisions`
|
||||
- Table: `scanner.triage_risk_results` (for severity/priority)
|
||||
|
||||
**API Endpoints:**
|
||||
- `POST /triage/decisions` - Create decision
|
||||
- `DELETE /triage/decisions/{decisionId}` - Revoke decision
|
||||
- `GET /triage/findings/{findingId}/decisions` - List decisions for finding
|
||||
|
||||
**Equivalence Proof:**
|
||||
- ✅ Subject: Linked via `FindingId` → `TriageFinding.Purl`
|
||||
- ✅ Decision ID: `DecisionId`
|
||||
- ✅ Severity: `TriageRiskResult.Lane`
|
||||
- ✅ Priority: `TriageRiskResult.RiskScore`
|
||||
- ✅ Rationale: `Reason` + `LatticeExplanationJson` (structured)
|
||||
- ✅ Actions: `Verdict` (Ship/Block/Exception)
|
||||
- ✅ DSSE Signatures: `DsseEnvelopeHash`, `SignatureRef`
|
||||
|
||||
---
|
||||
|
||||
## Idempotency Key Handling
|
||||
|
||||
### Advisory Pattern
|
||||
|
||||
```
|
||||
idemKey = hash(subjectDigest || type || runId || cve || windowStart)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**Event Envelope Idempotency:**
|
||||
|
||||
**Location:** `src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Domain/Events/EventEnvelope.cs`
|
||||
|
||||
```csharp
|
||||
public static string GenerateIdempotencyKey(
|
||||
OrchestratorEventType eventType,
|
||||
string? jobId,
|
||||
int attempt)
|
||||
{
|
||||
var jobPart = jobId ?? "none";
|
||||
return $"orch-{eventType.ToEventTypeName()}-{jobPart}-{attempt}";
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern:** `{domain}-{event_type}-{entity_id}-{attempt}`
|
||||
|
||||
**Orchestrator Event Idempotency:**
|
||||
|
||||
**Location:** `src/Scanner/StellaOps.Scanner.WebService/Contracts/OrchestratorEventContracts.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record OrchestratorEvent
|
||||
{
|
||||
public required string EventId { get; init; }
|
||||
public required string EventKind { get; init; }
|
||||
public required string IdempotencyKey { get; init; } // Explicitly tracked
|
||||
public required string CorrelationId { get; init; }
|
||||
public required string TraceId { get; init; }
|
||||
public required string SpanId { get; init; }
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Finding ID Stability (Signal-14):**
|
||||
|
||||
**Pattern:** `{cve}@{purl}@{scanId}`
|
||||
|
||||
**Location:** `TriageFinding.FindingId` generation logic
|
||||
|
||||
**Equivalence:**
|
||||
- ✅ Subject digest: Included in `scanId` or `AssetId`
|
||||
- ✅ Type: `EventKind` or `EventType`
|
||||
- ✅ Run ID: `TraceId`, `CorrelationId`, `attempt`
|
||||
- ✅ CVE: Included in finding ID
|
||||
- ✅ Window: Implicit in scan/job timing
|
||||
|
||||
---
|
||||
|
||||
## Evidence Reference Mechanisms
|
||||
|
||||
### Advisory Pattern
|
||||
|
||||
```
|
||||
evidenceRefs[i] = dsse://sha256:<payloadHash>
|
||||
```
|
||||
|
||||
**Storage:** DSSE payloads stored as blobs, indexed by `payloadHash` and `subjectDigest`
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Implementation
|
||||
|
||||
**CAS URI Pattern:**
|
||||
|
||||
```
|
||||
cas://reachability/graphs/{blake3_hash}
|
||||
cas://runtime/traces/{blake3_hash}
|
||||
```
|
||||
|
||||
**DSSE Reference Pattern:**
|
||||
|
||||
```
|
||||
{DsseEnvelopeHash} = SHA-256 of DSSE envelope
|
||||
{SignatureRef} = Reference to attestor.envelopes table
|
||||
```
|
||||
|
||||
**Evidence Artifact Entity:** `TriageEvidenceArtifact`
|
||||
|
||||
```csharp
|
||||
public sealed class TriageEvidenceArtifact
|
||||
{
|
||||
public string ContentHash { get; set; } // SHA-256 of content
|
||||
public string? SignatureRef { get; set; } // DSSE envelope reference
|
||||
public string? CasUri { get; set; } // CAS URI for content
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Reachability Evidence Chain:** `ReachabilityEvidenceChain`
|
||||
|
||||
**Location:** `src/__Libraries/StellaOps.Signals.Contracts/Models/Evidence/ReachabilityEvidenceChain.cs`
|
||||
|
||||
```csharp
|
||||
public sealed record ReachabilityEvidenceChain
|
||||
{
|
||||
public GraphEvidence? GraphEvidence { get; init; }
|
||||
public RuntimeEvidence? RuntimeEvidence { get; init; }
|
||||
public ImmutableArray<CodeAnchor> CodeAnchors { get; init; }
|
||||
public ImmutableArray<Unknown> Unknowns { get; init; }
|
||||
}
|
||||
|
||||
public sealed record GraphEvidence
|
||||
{
|
||||
public required string GraphHash { get; init; } // BLAKE3
|
||||
public required string GraphCasUri { get; init; } // cas://...
|
||||
public required string AnalyzerName { get; init; }
|
||||
public required string AnalyzerVersion { get; init; }
|
||||
public DateTimeOffset AnalyzedAt { get; init; }
|
||||
}
|
||||
|
||||
public sealed record RuntimeEvidence
|
||||
{
|
||||
public required string TraceHash { get; init; } // BLAKE3
|
||||
public required string TraceCasUri { get; init; } // cas://...
|
||||
public required string ProbeType { get; init; }
|
||||
public required int HitCount { get; init; }
|
||||
public DateTimeOffset LastSeenAt { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
**Storage:**
|
||||
- PostgreSQL: `attestor.envelopes` table for DSSE envelopes
|
||||
- PostgreSQL: `scanner.triage_evidence_artifacts` for evidence metadata
|
||||
- RustFS: CAS storage for evidence blobs (S3-compatible; MinIO legacy support available)
|
||||
|
||||
**Equivalence:**
|
||||
- ✅ Hash-addressed storage: SHA-256, BLAKE3
|
||||
- ✅ DSSE references: `DsseEnvelopeHash`, `SignatureRef`
|
||||
- ✅ CAS URIs: `cas://` scheme for content-addressable storage
|
||||
- ✅ Blob storage: S3-compatible object store
|
||||
- ✅ Index by subject: `FindingId` links to evidence
|
||||
|
||||
---
|
||||
|
||||
## API Endpoint Mapping
|
||||
|
||||
| Signal | Advisory Endpoint | StellaOps Endpoint |
|
||||
|--------|------------------|-------------------|
|
||||
| Signal-10 | `POST /sbom/intake` | `POST /api/scanner/sboms/ingest`<br>`POST /api/signals/callgraph/ingest` |
|
||||
| Signal-12 | `POST /attestations` | Implicit via signing services<br>`GET /api/attestor/envelopes/{hash}` |
|
||||
| Signal-14 | `GET /triage/facts/{findingId}` | `GET /api/scanner/triage/findings/{findingId}`<br>`GET /api/scanner/triage/findings/{findingId}/evidence` |
|
||||
| Signal-16 | `GET /diff/{from}/{to}` | `GET /api/smart-diff/scans/{scanId}/changes`<br>`GET /api/smart-diff/images/{digest}/candidates` |
|
||||
| Signal-18 | `POST /decisions` | `POST /api/triage/decisions`<br>`GET /api/triage/findings/{findingId}/decisions` |
|
||||
|
||||
---
|
||||
|
||||
## Component Architecture Alignment
|
||||
|
||||
### Advisory Architecture
|
||||
|
||||
```
|
||||
[ Sbomer ] → Signal-10 → [ Router ]
|
||||
[ Attestor ] → Signal-12 → [ Router ]
|
||||
[ Scanner.Worker ] → Signal-14 → [ Triage Store ]
|
||||
[ Reachability.Engine ] → updates Signal-14
|
||||
[ Smart-Diff ] → Signal-16 → [ Router ]
|
||||
[ Deterministic-Scorer ] → Signal-18 → [ Router/Notify ]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### StellaOps Architecture
|
||||
|
||||
```
|
||||
[ Scanner.Emit ] → SbomIngestionService → PostgreSQL (scanner.sboms)
|
||||
[ Attestor.ProofChain ] → DsseEnvelopeSigner → PostgreSQL (attestor.envelopes)
|
||||
[ Scanner.Triage ] → TriageFinding + related entities → PostgreSQL (scanner.triage_*)
|
||||
[ ReachabilityAnalyzer ] → PathWitnessBuilder → TriageReachabilityResult
|
||||
[ SmartDiff + ReachabilityDrift ] → MaterialRiskChangeDetector → TriageSnapshot
|
||||
[ Policy.Scoring engines ] → ScoreExplanationService → TriageRiskResult + TriageDecision
|
||||
[ Router.Gateway ] → TransportDispatchMiddleware → Inter-service routing
|
||||
[ TimelineIndexer ] → TimelineEventEnvelope → Event ordering & storage
|
||||
```
|
||||
|
||||
**Mapping:**
|
||||
- Sbomer ↔ Scanner.Emit
|
||||
- Attestor ↔ Attestor.ProofChain
|
||||
- Scanner.Worker ↔ Scanner.Triage
|
||||
- Reachability.Engine ↔ ReachabilityAnalyzer
|
||||
- Smart-Diff ↔ SmartDiff + ReachabilityDrift
|
||||
- Deterministic-Scorer ↔ Policy.Scoring engines
|
||||
- Router/Timeline ↔ Router.Gateway + TimelineIndexer
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Alignment Status:** ✅ **Fully Aligned (Conceptually)**
|
||||
|
||||
While StellaOps uses domain-specific entity names instead of generic "Signal-X" labels, all signal concepts are implemented with equivalent or superior functionality:
|
||||
|
||||
- ✅ **Signal-10:** SBOM intake via `CallgraphIngestRequest`, `ISbomIngestionService`
|
||||
- ✅ **Signal-12:** in-toto attestations with 19 predicate types, DSSE signing
|
||||
- ✅ **Signal-14:** Comprehensive triage entities (`TriageFinding`, `TriageReachabilityResult`, `TriageRiskResult`, `TriageEffectiveVex`)
|
||||
- ✅ **Signal-16:** Smart-diff with `TriageSnapshot`, `MaterialRiskChange`, explainable drift causes
|
||||
- ✅ **Signal-18:** `TriageDecision` with DSSE signatures and structured rationale
|
||||
|
||||
**Key Advantages of StellaOps Implementation:**
|
||||
1. **Type Safety:** Strong entity types vs. generic JSON blobs
|
||||
2. **Relational Integrity:** PostgreSQL foreign keys enforce referential integrity
|
||||
3. **Query Performance:** Indexed tables for fast lookups
|
||||
4. **Domain Clarity:** Names reflect business concepts (Triage, Risk, Evidence)
|
||||
5. **Extensibility:** Easy to add new fields/entities without breaking contracts
|
||||
|
||||
**Recommendation:** Maintain current architecture and entity naming. Provide this mapping document to demonstrate compliance with advisory signal patterns.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
### StellaOps Code Files
|
||||
- `src/Signals/StellaOps.Signals/Models/CallgraphIngestRequest.cs`
|
||||
- `src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/Statements/InTotoStatement.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.Triage/Entities/*.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.SmartDiff/Detection/MaterialRiskChangeDetector.cs`
|
||||
- `src/Scanner/__Libraries/StellaOps.Scanner.ReachabilityDrift/Services/*.cs`
|
||||
- `src/Signer/StellaOps.Signer/StellaOps.Signer.Core/PredicateTypes.cs`
|
||||
|
||||
### Advisory References
|
||||
- Advisory architecture document (CycloneDX 1.7 / VEX-first / in-toto)
|
||||
- Signal contracts specification (10/12/14/16/18)
|
||||
- DSSE specification: https://github.com/secure-systems-lab/dsse
|
||||
- in-toto attestation framework: https://github.com/in-toto/attestation
|
||||
|
||||
### StellaOps Documentation
|
||||
- `docs/ARCHITECTURE_OVERVIEW.md`
|
||||
- `docs/modules/scanner/architecture.md`
|
||||
- `docs/modules/attestor/transparency.md`
|
||||
- `docs/contracts/witness-v1.md`
|
||||
Reference in New Issue
Block a user