Risk Score Dashboard Flow
Audience: developers integrating with the RiskEngine scoring API, and security leaders evaluating StellaOps risk posture. Read the reconciliation banner first — the per-subject scoring API is implemented; the org-wide risk dashboard is roadmap.
Reconciliation status (2026-05-30): This flow was reconciled against
src/Findings(RiskEngine),src/Signals(Evidence-Weighted Score), and the Console risk client insrc/Web. The implemented RiskEngine is a per-subject scoring service exposed under/risk-scores/*and/exploit-maturity/*(consolidated into theFindingsmodule — code undersrc/Findings/StellaOps.RiskEngine.*). 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 — there is no such route inStellaOps.RiskEngine.WebService. 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 implemented Risk Score flow describes how the StellaOps RiskEngine computes 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.
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 | Service | Computes per-subject risk scores and exploit-maturity levels (part of the Findings module) | Implemented |
| 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)
- A registered RiskEngine provider for the desired scoring strategy (see Providers below)
- Caller token carrying
risk-engine:read(query) and/orrisk-engine:operate(submit jobs/simulations/batch) - Roadmap-only: historical data for trends, cross-tenant aggregation permissions
Risk Scoring Subject (implemented)
RiskEngine scores a single opaque subject per request. The aggregation dimensions below are roadmap; today the only implemented “aggregation” is the optional POST /risk-scores/simulations/summary endpoint, which returns 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] | Implemented |
| Simulation batch | Caller-supplied set of subjects | average/min/max + top-3 movers (in-request, not persisted) | Implemented |
| 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 (implemented)
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, RiskEngine registers a set of named providers, each with its own deterministic formula over the
Signalsdictionary. The provider is chosen by theproviderfield of the request. Registered providers (StellaOps.RiskEngine.WebService/Program.cs):
| 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, but it is not added to theRiskScoreProviderRegistryinProgram.cs, so it cannot be invoked over HTTP today.
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 implemented path is simpler: a caller posts a
ScoreRequestto/risk-scores/jobs(or a batch to/risk-scores/simulations[/summary]), RiskEngine runs the named provider over the suppliedsignalsand returns a[0,1]score. 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 │ │ │ │ │
│<────────────│ │ │ │ │
│ │ │ │ │ │
Implemented RiskEngine API surface
These are the real routes exposed by
StellaOps.RiskEngine.WebService. The service mounts them at the service root (the public path depends on the gateway route prefix for theriskengineservice); there is no/api/v1/risk/*prefix in the code.
| 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 today — 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 in a live host, source-backed providers raise NotSupportedException, which exploit-maturity endpoints surface as 501 Not Implemented (title: risk_sources_unavailable). Inline signals in the ScoreRequest bypass the source lookup.
Step-by-Step
1. Score Request (implemented)
A caller submits a scoring job for a single subject. The provider must 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 (implemented)
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 the registeredcvss-kev-epssprovider.
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 (/exploit-maturity/{cveId})
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 that exists is 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 implemented responses are: 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"
}
]
}
}
Implemented response shapes (for reference)
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 (implemented)
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);
/risk-scores/simulations accepts a JSON array of ScoreRequest; the /summary variant adds { 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 registered inProgram.cs; the only runtime config is the EPSS/CVSS/KEV source wiring (RiskEngine:Sources:Epss:BundlePath/DirectoryPath/SnapshotPath,Storage:Driver, and the Postgres connection string). 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
Implemented
| Condition | Behavior (from 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 return 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 returns 404 |
inmemory storage outside Testing | Service throws 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 (implemented)
Corrected.
risk_score_current,risk_calculation_duration_ms,risk_grade_distribution, andrisk_trend_change_30ddo not exist. The actual metrics emitted by the RiskEngine code are below.
| 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 (implemented)
The /risk-scores/jobs, /risk-scores/simulations, and /risk-scores/simulations/summary endpoints are audited under module riskengine with actions create_score_job and create_simulation (StellaOps.Audit.Emission/AuditActions.cs -> RiskEngine).
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
