Risk Score Dashboard Flow
Audience: developers designing the future Findings-owned risk read surface, and security leaders evaluating StellaOps risk posture. Read the banner below first — the source surface this flow describes has been deleted.
Retirement EXECUTED (owner ruling Q-2, 2026-08-25;
SPRINT_20260722_010FND-10, 2026-08-27)./risk-scores/*and/exploit-maturity/*are gone, together withStellaOps.RiskEngine.WebService,.Workerand.Application. The route and provider detail below is a historical record of the deleted surface, kept because the future Findings-owned replacement is designed from it.RiskEngine.Core, its result store and theriskengineschema are KEPT and folded into the Findings family (stellaops_findings); any future read API is designed on/api/findings/v1/*for a real consumer. See../modules/findings/architecture.md.
Reconciliation status (2026-05-30, superseded for RiskEngine by the banner above): This flow was reconciled against
src/Findings(RiskEngine),src/Signals(Evidence-Weighted Score), and the Console risk client insrc/Web. At that date the RiskEngine was a per-subject scoring service exposed under/risk-scores/*and/exploit-maturity/*(code undersrc/Findings/StellaOps.RiskEngine.*). That host was deleted on 2026-08-27 and no host serves those routes now. Sections marked “deleted surface” below record its behavior. The org-wide / team / application risk dashboard with letter grades, executive rollups, 30-day trend lines, and remediation recommendations described below is NOT IMPLEMENTED as a single/api/v1/risk/summaryendpoint — the deletedStellaOps.RiskEngine.WebServicehad no such route, and no host has one now. Sections that describe that dashboard are retained as roadmap / aspirational and are flagged inline. The Console “risk” surface today maps to Policy governance risk-profiles (/api/v1/governance/risk-profiles) plus aggregated-status / severity-transition endpoints, not to an org risk-grade dashboard.
Overview
The Risk Score flow describes how the StellaOps RiskEngine computed a single numeric risk score in [0,1] for an opaque subject (typically a CVE or an asset id) by running a named provider over a deterministic dictionary of numeric signals. Scoring is deterministic and side-effect free, and exploit-maturity assessment consolidates EPSS / KEV / in-the-wild evidence into a maturity level. The providers and ExploitMaturityService remain in the kept StellaOps.RiskEngine.Core library, but no deployable registers or calls them.
Roadmap (NOT IMPLEMENTED): Aggregating those per-subject scores across images, teams, applications, environments, and the whole organization, with trend analysis and executive reporting, is the forward-looking goal of this flow. No aggregation, grading, or trend-history logic exists in
StellaOps.RiskEngine.*today.
Business Value: Quantified risk visibility enables resource prioritization, executive reporting, and measurable security improvement over time.
Actors
| Actor | Type | Role | Status |
|---|---|---|---|
| Security Leader | Human | Reviews risk posture | — |
| RiskEngine | Library (host deleted) | Computed per-subject risk scores and exploit-maturity levels; the providers are kept in StellaOps.RiskEngine.Core (part of the Findings module) | Host deleted 2026-08-27; library has no deployable caller |
| Signals | Service | Computes the Evidence-Weighted Score (EWS) that feeds reachability/exploit normalization | Implemented |
| Scanner | Service | Provides vulnerability findings | Implemented |
| Policy | Service | Owns governance risk-profiles surfaced by the Console risk client | Implemented |
| Console | System | Displays risk-profile lists, aggregated status, and severity transitions | Implemented |
| Platform | Service | Cross-tenant aggregation/rollup | NOT IMPLEMENTED (roadmap) |
Prerequisites
- Images scanned with findings (Scanner)
- Deleted surface only: a registered RiskEngine provider (see Providers below) and a caller token carrying
risk-engine:read(query) and/orrisk-engine:operate(submit jobs/simulations/batch). No served route requires these scopes, and both were withdrawn from the scope catalog on 2026-10-02. - Roadmap-only: historical data for trends, cross-tenant aggregation permissions
Risk Scoring Subject (deleted surface)
RiskEngine scored a single opaque subject per request. The aggregation dimensions below are roadmap; the only “aggregation” the deleted host offered was the optional POST /risk-scores/simulations/summary endpoint, which returned averageScore, minScore, maxScore, and the top-3 highest-scoring subjects across a batch of ScoreRequests passed in a single call (no persisted grouping by team/app/org).
| Dimension | Scope | Aggregation | Status |
|---|---|---|---|
| Subject | Single CVE / asset id | Direct provider score [0,1] | Deleted surface (host deleted 2026-08-27) |
| Simulation batch | Caller-supplied set of subjects | average/min/max + top-3 movers (in-request, not persisted) | Deleted surface (host deleted 2026-08-27) |
| Image | Single container | Weighted/derived score | NOT IMPLEMENTED (roadmap) |
| Application | Group of images | Weighted average | NOT IMPLEMENTED (roadmap) |
| Team | All team assets | Sum/average | NOT IMPLEMENTED (roadmap) |
| Environment | prod/staging/dev | Environment-weighted | NOT IMPLEMENTED (roadmap) |
| Organization | All tenants | Executive rollup | NOT IMPLEMENTED (roadmap) |
Risk Scoring Providers (kept library)
Corrected: There is no single fixed-weight 6-factor model (CVSS 25% / Exploitability 20% / Reachability 20% / Exposure 15% / Criticality 10% / Time-to-Remediate 10%). That table did not match any code. Instead,
StellaOps.RiskEngine.Coredefines a set of named providers, each with its own deterministic formula over theSignalsdictionary. The provider is chosen by theproviderfield of the request. The deleted host registered the six below inStellaOps.RiskEngine.WebService/Program.cs; no deployable registers them now:
| Provider name | Formula (from source) | Source file |
|---|---|---|
default-transforms | clamp each signal to [0,1], return the average (0 if no signals) | DefaultTransformsProvider.cs |
cvss-kev | clamp01((cvss/10) + kevBonus), kevBonus = 0.2 if KEV else 0 | CvssKevProvider.cs |
epss | EPSS probability used directly (already [0,1]); 0 if unknown | EpssProvider.cs |
cvss-kev-epss | clamp01((cvss/10) + kevBonus + epssBonus); KEV bonus 0.2; EPSS percentile bonus +0.10/+0.05/+0.02 at p99/p90/p50; falls back to raw EPSS if no CVSS/KEV | EpssProvider.cs (CvssKevEpssProvider) |
vex-gate | short-circuits to 0 when HasDenial >= 1; otherwise clamp01(max of remaining signals) | VexGateProvider.cs |
fix-exposure | clamp01(0.5·FixAvailability + 0.3·Criticality + 0.2·Exposure) | FixExposureProvider.cs |
fixchainprovider — NOT REGISTERED:FixChainRiskProvider(signalsfixchain.confidence,fixchain.status) exists inStellaOps.RiskEngine.Coreand emits a risk reduction from FixChain attestation verdicts. The deleted host did not add it to theRiskScoreProviderRegistryin itsProgram.cs, and no deployable registers any provider now.
Reachability is not a RiskEngine signal weight. Reachability normalization lives in the Signals Evidence-Weighted Score (EWS) pipeline (
ReachabilityNormalizer, dimension"RCH"), which maps aReachabilityState+ confidence into a[0,1]RCH score. See the Reachability Data note in “Data Collection” for the actual state enum.
Flow Diagram
Roadmap diagram. The sequence below depicts the aspirational Platform-aggregated dashboard (Platform fan-out to Scanner/Policy, RiskEngine per-image/per-team rollup). The deleted host’s path was simpler: a caller posted a
ScoreRequestto/risk-scores/jobs(or a batch to/risk-scores/simulations[/summary]), RiskEngine ran the named provider over the suppliedsignalsand returned a[0,1]score. No host serves that path now. There is no Platform aggregator and no cross-service fan-out in RiskEngine.
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Risk Score Dashboard Flow (roadmap — NOT IMPLEMENTED) │
└─────────────────────────────────────────────────────────────────────────────────┘
┌──────────┐ ┌─────────┐ ┌────────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐
│ Security │ │ Console │ │ Platform │ │ Risk │ │ Scanner│ │ Policy │
│ Leader │ │ │ │ Service │ │ Engine │ │ │ │ │
└────┬─────┘ └────┬────┘ └─────┬──────┘ └────┬────┘ └───┬────┘ └────┬────┘
│ │ │ │ │ │
│ View risk │ │ │ │ │
│ dashboard │ │ │ │ │
│────────────>│ │ │ │ │
│ │ │ │ │ │
│ │ GET /risk/ │ │ │ │
│ │ summary │ │ │ │
│ │────────────>│ │ │ │
│ │ │ │ │ │
│ │ │ Get findings │ │ │
│ │ │──────────────────────────> │
│ │ │ │ │ │
│ │ │ Vuln data │ │ │
│ │ │<────────────────────────── │
│ │ │ │ │ │
│ │ │ Get verdicts │ │ │
│ │ │───────────────────────────────────────>
│ │ │ │ │ │
│ │ │ Policy data │ │ │
│ │ │<───────────────────────────────────────
│ │ │ │ │ │
│ │ │ Compute │ │ │
│ │ │ scores │ │ │
│ │ │─────────────>│ │ │
│ │ │ │ │ │
│ │ │ │ Calculate │ │
│ │ │ │ per-image │ │
│ │ │ │───┐ │ │
│ │ │ │ │ │ │
│ │ │ │<──┘ │ │
│ │ │ │ │ │
│ │ │ │ Aggregate │ │
│ │ │ │ by team │ │
│ │ │ │───┐ │ │
│ │ │ │ │ │ │
│ │ │ │<──┘ │ │
│ │ │ │ │ │
│ │ │ Risk scores │ │ │
│ │ │<─────────────│ │ │
│ │ │ │ │ │
│ │ Dashboard │ │ │ │
│ │ data │ │ │ │
│ │<────────────│ │ │ │
│ │ │ │ │ │
│ Render │ │ │ │ │
│ dashboard │ │ │ │ │
│<────────────│ │ │ │ │
│ │ │ │ │ │
Deleted RiskEngine API surface (historical record)
These routes were exposed by
StellaOps.RiskEngine.WebServiceuntil the host was deleted on 2026-08-27. No host serves them now; a request returns 404. The host mounted them at the service root, with no/api/v1/risk/*prefix. The served risk-scoring surfaces are listed in the Risk Scoring Contract.
| Method & path | Auth (scope) | Purpose |
|---|---|---|
GET /risk-scores/providers | risk-engine:read | List registered provider names (sorted) |
POST /risk-scores/jobs | risk-engine:operate (audited) | Enqueue + synchronously run one scoring job; returns 202 with { jobId, result } |
GET /risk-scores/jobs/{jobId:guid} | risk-engine:read | Fetch a stored RiskScoreResult (404 if not found) |
POST /risk-scores/simulations | risk-engine:operate (audited) | Evaluate a batch of ScoreRequests without persisting; returns { results } |
POST /risk-scores/simulations/summary | risk-engine:operate (audited) | Same as simulations plus { summary: { averageScore, minScore, maxScore, topMovers } } |
GET /exploit-maturity/{cveId} | risk-engine:read | Assess exploit maturity (ExploitMaturityResult) |
GET /exploit-maturity/{cveId}/level | risk-engine:read | Just the ExploitMaturityLevel (404 if undetermined) |
GET /exploit-maturity/{cveId}/history | risk-engine:read | Maturity history (always empty — not persisted) |
POST /exploit-maturity/batch | risk-engine:operate | Batch assess { cveIds: [...] }; returns { results, errors } |
GET /healthz, GET /readyz | anonymous | Liveness / readiness probes |
When the CVSS/KEV/EPSS source bundles are not configured, source-backed providers raise NotSupportedException; the deleted exploit-maturity endpoints surfaced it as 501 Not Implemented (title: risk_sources_unavailable). Inline signals in the ScoreRequest bypass the source lookup.
Step-by-Step
1. Score Request (deleted surface)
A caller submitted a scoring job for a single subject. The provider had to be one of the registered names; signals is a deterministic string -> double map.
POST /risk-scores/jobs HTTP/1.1
Authorization: Bearer {jwt}
Content-Type: application/json
{
"provider": "cvss-kev-epss",
"subject": "CVE-2024-1234",
"signals": {
"Cvss": 9.8,
"Kev": 1,
"EpssScore": 0.94,
"EpssPercentile": 0.99
}
}
Response (202 Accepted, Location: /risk-scores/jobs/{jobId}):
{
"jobId": "3f2a...-...",
"result": {
"jobId": "3f2a...-...",
"provider": "cvss-kev-epss",
"subject": "CVE-2024-1234",
"score": 1.0,
"success": true,
"error": null,
"signals": { "Cvss": 9.8, "Kev": 1, "EpssScore": 0.94, "EpssPercentile": 0.99 },
"completedAtUtc": "2026-05-30T12:00:00Z"
}
}
Roadmap (NOT IMPLEMENTED): The original
GET /api/v1/risk/summary?scope=organization&period=30d&breakdown=team,severity,trendrequest below has no implementation. RiskEngine has noscope,period, orbreakdownquery parameters and no org/team summary endpoint.
2. Data Collection (roadmap)
NOT IMPLEMENTED as an aggregator. RiskEngine does not call Scanner/Policy/ReachGraph to assemble a tenant-wide summary. The JSON shapes below are illustrative of a future Platform aggregator and do not correspond to any current RiskEngine response. The one accurate grounding note is the reachability state enum, corrected at the end of this section.
Vulnerability Data (Scanner) — illustrative roadmap shape
{
"vulnerability_summary": {
"total_findings": 1847,
"by_severity": {
"critical": 23,
"high": 189,
"medium": 567,
"low": 1068
},
"by_status": {
"new": 145,
"existing": 1502,
"fixed": 200
},
"unique_cves": 423,
"affected_images": 89,
"affected_packages": 234
}
}
Policy Data (Policy Engine) — illustrative roadmap shape
{
"policy_summary": {
"total_evaluations": 892,
"by_verdict": {
"pass": 743,
"warn": 98,
"fail": 51
},
"compliance_rate": 0.83,
"by_policy_set": {
"production": {"pass": 234, "fail": 12},
"pci-dss": {"pass": 198, "fail": 8},
"default": {"pass": 311, "fail": 31}
}
}
}
Reachability Data (Signals / EWS)
Corrected enum. The state names
ConfirmedReachable,StaticallyReachable,StaticallyUnreachable,ConfirmedUnreachabledo not exist. The Signals EWSReachabilityStateenum (src/Signals/.../EvidenceWeightedScore/ReachabilityInput.cs) is:Unknown,NotReachable,PotentiallyReachable,StaticReachable,DynamicReachable,LiveExploitPath. (The Scanner emit lattice insrc/Scanner/.../Emit/Reachability/ReachabilityLattice.csuses a separate, coarser enum:Unknown,Conditional,Reachable,Unreachable.)
{
"reachability_summary": {
"total_analyzed": 1847,
"by_state": {
"LiveExploitPath": 12,
"DynamicReachable": 33,
"StaticReachable": 234,
"PotentiallyReachable": 180,
"Unknown": 890,
"NotReachable": 498
}
}
}
3. Risk Calculation (kept library)
Corrected. RiskEngine does not compute a fixed weighted sum of six factors. Each provider applies its own deterministic formula to the request
signals. The example below uses thecvss-kev-epssprovider, which the deleted host registered.
Per-Subject Risk Score (cvss-kev-epss)
score = clamp01( cvss/10 + kevBonus + epssBonus )
kevBonus = 0.20 if KEV else 0.00
epssBonus = 0.10 if EPSS percentile >= 0.99
= 0.05 if >= 0.90
= 0.02 if >= 0.50
else 0.00
(if CVSS<=0 and not KEV, falls back to raw EPSS score + epssBonus)
For CVE-2024-1234 with Cvss=9.8, Kev=1, EpssPercentile=0.99:
base = 9.8/10 = 0.98
kev = 0.20
epss = 0.10
raw = 1.28 -> clamped to 1.0
Subject Risk Score: 1.0
Exploit-Maturity Assessment (ExploitMaturityService; the deleted /exploit-maturity/{cveId} route)
ExploitMaturityService consolidates EPSS + KEV + in-the-wild signals.
Final level = max(level across present signals); confidence = mean confidence
of signals at that max level.
EPSS -> level mapping (EpssThresholds):
score >= 0.80 -> Weaponized
score >= 0.40 -> Active
score >= 0.10 -> ProofOfConcept
score >= 0.01 -> Theoretical
else -> Unknown
KEV present -> Weaponized (confidence 0.95)
Roadmap (NOT IMPLEMENTED): Per-image rollup (
max(finding_risks) + 0.1·avg(...)) and per-team weighted aggregation are not implemented. The only multi-subject computation the deleted host offered was the in-request/risk-scores/simulations/summaryaggregate (averageScore,minScore,maxScore, top-3topMovers).
4. Dashboard Response (roadmap — NOT IMPLEMENTED)
NOT IMPLEMENTED. No endpoint returns the
risk_summaryorg/team/grade/trend/ recommendation shape below. The deleted host’s responses were: the per-jobRiskScoreResult(Step 1), the{ results }/{ summary, results }simulation payloads, and theExploitMaturityResult. The block below is retained as a forward-looking target only.
{
"risk_summary": {
"organization": {
"score": 0.58,
"grade": "C",
"trend": "-0.05",
"trend_direction": "improving"
},
"breakdown": {
"by_team": [
{
"team": "Platform Engineering",
"score": 0.45,
"grade": "B",
"image_count": 12,
"critical_findings": 2
},
{
"team": "Product Development",
"score": 0.72,
"grade": "D",
"image_count": 34,
"critical_findings": 18
}
],
"by_severity": {
"critical": {"count": 23, "risk_contribution": 0.35},
"high": {"count": 189, "risk_contribution": 0.40},
"medium": {"count": 567, "risk_contribution": 0.20},
"low": {"count": 1068, "risk_contribution": 0.05}
},
"by_environment": {
"production": {"score": 0.62, "image_count": 45},
"staging": {"score": 0.55, "image_count": 23},
"development": {"score": 0.48, "image_count": 67}
}
},
"trends": {
"period": "30d",
"scores": [
{"date": "2024-11-29", "score": 0.63},
{"date": "2024-12-06", "score": 0.61},
{"date": "2024-12-13", "score": 0.59},
{"date": "2024-12-20", "score": 0.57},
{"date": "2024-12-29", "score": 0.58}
],
"change_30d": -0.05,
"change_7d": +0.01
},
"top_risks": [
{
"cve": "CVE-2024-1234",
"risk_score": 0.92,
"affected_images": 8,
"teams": ["Product Development"],
"remediation": "Upgrade lodash to 4.17.21"
}
],
"recommendations": [
{
"priority": 1,
"action": "Remediate CVE-2024-1234 in Product Development",
"impact": "Reduces org risk by 0.08 points"
}
]
}
}
Response shapes of the deleted surface (types kept in StellaOps.RiskEngine.Core)
RiskScoreResult (StellaOps.RiskEngine.Core/Contracts/RiskScoreResult.cs):
{
"jobId": "<guid>",
"provider": "cvss-kev",
"subject": "CVE-2024-1234",
"score": 0.84, // double in [0,1]
"success": true,
"error": null,
"signals": { "Cvss": 8.4, "Kev": 0 },
"completedAtUtc": "2026-05-30T12:00:00Z"
}
ExploitMaturityResult (StellaOps.RiskEngine.Core/Contracts/ExploitMaturityModels.cs):
{
"cveId": "CVE-2024-1234",
"level": "Weaponized", // Unknown|Theoretical|ProofOfConcept|Active|Weaponized
"confidence": 0.95,
"signals": [ { "source": "Kev", "level": "Weaponized", "confidence": 0.95, "evidence": "...", "observedAt": "..." } ],
"rationale": "Maturity level Weaponized determined by Kev (1 signal(s))",
"assessedAt": "2026-05-30T12:00:00Z"
}
5. Dashboard Rendering (roadmap — NOT IMPLEMENTED)
NOT IMPLEMENTED. There is no org risk-grade dashboard view in the Console. The Console risk client (
src/Web/.../core/api/risk-http.client.ts) renders Policy governance risk-profiles (GET /api/v1/governance/risk-profiles) plus an aggregated-status panel (/aggregated-status) and recent severity transitions (/transitions/recent). The ASCII mock-up below is an aspirational target.
┌─────────────────────────────────────────────────────────────────┐
│ Organization Risk Dashboard │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Overall Risk Score: 58/100 (Grade: C) ↓ 5% from last month │
│ ████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
│ │
│ ┌─ Risk by Team ──────────────────────────────────────────────┐│
│ │ Platform Engineering ████████░░░░░░░ 45 (B) ↓3% ││
│ │ Product Development ████████████████░░ 72 (D) ↑2% ││
│ │ Security ████░░░░░░░░░░░░ 28 (A) ↓1% ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ ┌─ 30-Day Trend ──────────────────────────────────────────────┐│
│ │ ╭───────────────────────────────────────────╮ ││
│ │ 63 │ ╲ │ ││
│ │ 61 │ ╲ │ ││
│ │ 59 │ ╲ │ ││
│ │ 57 │ ╲___________/ │ ││
│ │ ╰─────────────────────────────────────────────╯ ││
│ │ Nov 29 Dec 13 Dec 22 Dec 29 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
│ ┌─ Top Actions ───────────────────────────────────────────────┐│
│ │ 1. Remediate CVE-2024-1234 (-0.08 risk) ││
│ │ 2. Update base images in Product team (-0.05 risk) ││
│ │ 3. Enable runtime monitoring for api-service (-0.03 risk) ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
Risk Grading Scale (roadmap — NOT IMPLEMENTED)
NOT IMPLEMENTED. RiskEngine returns a
doublescore in[0,1]; it does not map scores to a 0-100 scale or to A-F letter grades. The exploit-maturity surface uses an ordinal level taxonomy (Unknown->Theoretical->ProofOfConcept->Active->Weaponized), not a grade. The table below is an aspirational presentation layer for a future dashboard.
| Score Range | Grade | Description |
|---|---|---|
| 0-20 | A | Excellent - minimal risk |
| 21-40 | B | Good - well managed |
| 41-60 | C | Fair - needs attention |
| 61-80 | D | Poor - significant risk |
| 81-100 | F | Critical - immediate action |
Data Contracts
RiskEngine request / result (kept library types)
ScoreRequest and RiskScoreResult (StellaOps.RiskEngine.Core/Contracts/):
public sealed record ScoreRequest(
string Provider,
string Subject,
IReadOnlyDictionary<string, double> Signals);
public sealed record RiskScoreResult(
Guid JobId,
string Provider,
string Subject,
double Score, // [0,1]
bool Success,
string? Error,
IReadOnlyDictionary<string, double> Signals,
DateTimeOffset CompletedAtUtc);
The deleted /risk-scores/simulations route accepted a JSON array of ScoreRequest; the /summary variant added { averageScore, minScore, maxScore, topMovers[] }.
Risk Summary Request Schema (roadmap — NOT IMPLEMENTED)
NOT IMPLEMENTED. The
RiskSummaryRequest/RiskSummaryResponseinterfaces below have no backing endpoint.scope,period,breakdown,grade,trend_direction, and themetadata.model_versionfield do not exist in the RiskEngine code. Retained as a roadmap contract sketch.
interface RiskSummaryRequest {
scope: 'image' | 'application' | 'team' | 'environment' | 'organization';
scope_id?: string; // Required for image/application/team
period?: string; // ISO-8601 duration, default 30d
breakdown?: Array<'team' | 'severity' | 'environment' | 'trend'>;
compare_to?: string; // Previous period for comparison
}
Risk Summary Response Schema
interface RiskSummaryResponse {
risk_summary: {
scope: string;
score: number; // 0-100
grade: 'A' | 'B' | 'C' | 'D' | 'F';
trend: number;
trend_direction: 'improving' | 'stable' | 'degrading';
breakdown?: {
by_team?: TeamRisk[];
by_severity?: SeverityBreakdown;
by_environment?: EnvironmentRisk[];
};
trends?: {
period: string;
scores: Array<{date: string; score: number}>;
change_30d: number;
change_7d: number;
};
top_risks?: TopRisk[];
recommendations?: Recommendation[];
};
metadata: {
calculated_at: string;
data_freshness: string;
model_version: string;
};
}
Risk Model Configuration (roadmap — NOT IMPLEMENTED)
NOT IMPLEMENTED. RiskEngine has no
risk_modelYAML config, no global factor weights, and no aggregation policy. Providers are hard-coded C# strategies. The deleted host registered them in itsProgram.csand read only the EPSS/CVSS/KEV source wiring (RiskEngine:Sources:Epss:BundlePath/DirectoryPath/SnapshotPath,Storage:Driver, and the Postgres connection string); no deployable reads those keys now. The configurableFixChainRiskOptions(e.g.FixedReduction=0.90,PartialReduction=0.50,MinConfidenceThreshold=0.60) belong to the unregisteredfixchainprovider.The reachability
mappingkeys below were also wrong (ConfirmedReachable,RuntimeObserved,StaticallyReachable,StaticallyUnreachable,ConfirmedUnreachable). The real Signals EWS states areUnknown / NotReachable / PotentiallyReachable / StaticReachable / DynamicReachable / LiveExploitPath, and their[0,1]mapping is computed byReachabilityNormalizer(base score + confidence modifier + analysis bonus − hop penalty), not a static lookup table. The YAML below is retained as an aspirational design.
risk_model:
version: "1.0.0"
factors:
cvss_score:
weight: 0.25
normalization: linear # score/10
exploitability:
weight: 0.20
components:
kev: 0.50 # In KEV = 1.0
epss: 0.30 # EPSS percentile
poc: 0.20 # Public PoC exists
reachability:
weight: 0.20
mapping:
LiveExploitPath: 1.0
DynamicReachable: 0.9
StaticReachable: 0.7
Unknown: 0.5
PotentiallyReachable: 0.35
NotReachable: 0.1
exposure:
weight: 0.15
mapping:
internet_facing: 1.0
internal_network: 0.6
isolated: 0.2
asset_criticality:
weight: 0.10
source: business_metadata
remediation_age:
weight: 0.10
decay_days: 90 # Linear decay over 90 days
aggregation:
image: max_plus_average
team: weighted_average
organization: weighted_sum
Error Handling
Deleted host behavior (historical record)
The rows below describe the host deleted on 2026-08-27. No host produces these responses now.
| Condition | Behavior (from the deleted source) |
|---|---|
| Provider not registered | RiskScoreResult returned with success=false, error="Provider not registered", score=0 (job and simulation paths) |
| Provider throws during scoring | Exception captured into RiskScoreResult.error, success=false, score=0 |
| CVSS/KEV/EPSS source unavailable in a live host | Exploit-maturity endpoints returned 501 Not Implemented (title: risk_sources_unavailable); inline signals bypass the source |
| Invalid CVE id / empty batch | 400 Bad Request (ArgumentException / “cve_ids_required”) |
| Maturity level undetermined | GET /exploit-maturity/{cveId}/level returned 404 |
inmemory storage outside Testing | Service threw on startup (postgres required in live hosts) |
Roadmap (NOT IMPLEMENTED)
| Error | Recovery |
|---|---|
| Data stale | Show warning, use cached data |
| Partial data | Calculate with available, note gaps |
| Model error | Fall back to simplified model |
| Aggregation timeout | Return partial results |
Observability
Metrics (defined in the kept library)
Corrected.
risk_score_current,risk_calculation_duration_ms,risk_grade_distribution, andrisk_trend_change_30ddo not exist. The metrics the RiskEngine code defines are below. No deployable runs that code, so no host emits them now.
| Metric | Type | Meter | Labels | Source |
|---|---|---|---|---|
stellaops_exploit_maturity_assessment_duration_ms | Histogram | StellaOps.RiskEngine | cve | ExploitMaturityService.cs |
stellaops_exploit_maturity_assessments_total | Counter | StellaOps.RiskEngine | level | ExploitMaturityService.cs |
risk_fixchain_lookups_total | Counter | StellaOps.RiskEngine.FixChain | — | FixChainRiskMetrics.cs (provider unregistered) |
risk_fixchain_hits_total / risk_fixchain_misses_total | Counter | StellaOps.RiskEngine.FixChain | verdict | FixChainRiskMetrics.cs |
risk_fixchain_cache_hits_total | Counter | StellaOps.RiskEngine.FixChain | — | FixChainRiskMetrics.cs |
risk_fixchain_lookup_duration_seconds | Histogram | StellaOps.RiskEngine.FixChain | — | FixChainRiskMetrics.cs |
risk_fixchain_adjustments_total | Counter | StellaOps.RiskEngine.FixChain | verdict, confidence_tier | FixChainRiskMetrics.cs |
risk_fixchain_reduction_percent | Histogram | StellaOps.RiskEngine.FixChain | — | FixChainRiskMetrics.cs |
risk_fixchain_errors_total | Counter | StellaOps.RiskEngine.FixChain | error_type | FixChainRiskMetrics.cs |
The Findings Ledger module separately emits EWS / scoring metrics (e.g. Ews*, Scoring* families in StellaOps.Findings.Ledger/Observability/LedgerMetrics.cs).
Audit actions (deleted surface)
The deleted /risk-scores/jobs, /risk-scores/simulations, and /risk-scores/simulations/summary endpoints were audited under module riskengine with actions create_score_job and create_simulation. Those constants (AuditActions.RiskEngine) and the AuditModules.RiskEngine module name were removed from StellaOps.Audit.Emission on 2026-10-02. The Timeline audit query still accepts riskengine as a module filter, so rows written before the deletion stay queryable.
Key Log Events
NOT IMPLEMENTED as named events.
risk.calculated,risk.trend_alert, andrisk.threshold_exceededare not emitted.ExploitMaturityServicelogs an unstructured debug line per assessment (“Assessed exploit maturity for {CveId}: {Level} …”) and an error on failure. The structured-event table below is a roadmap target.
| Event | Level | Fields | Status |
|---|---|---|---|
risk.calculated | INFO | scope, score, grade | NOT IMPLEMENTED (roadmap) |
risk.trend_alert | WARN | scope, change, direction | NOT IMPLEMENTED (roadmap) |
risk.threshold_exceeded | WARN | scope, threshold, score | NOT IMPLEMENTED (roadmap) |
Related Flows
- Dashboard Data Flow - Dashboard patterns
- Policy Evaluation Flow - Compliance data
- Advisory Drift Re-scan Flow - Risk updates
