docs consolidation and others

This commit is contained in:
master
2026-01-06 19:02:21 +02:00
parent d7bdca6d97
commit 4789027317
849 changed files with 16551 additions and 66770 deletions

View File

@@ -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`

View File

@@ -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`

View File

@@ -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`

View File

@@ -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*

View File

@@ -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**

View File

@@ -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

View File

@@ -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`