Skip to content

Tool: score_risk

Score a batch of software-vulnerability findings into an action: FIX_TODAY, SCHEDULE, or DEFER. score_risk composes the KEV and EPSS catalogs with the reachability and asset context you supply, applies the k9 risk-scoring rubric, and returns a per-finding verdict plus a NIST SP 800-30 risk score.

Ask your agent:

Score this project's dependency alerts for reachable risk.

Read the rubric once per session before scoring, so your agent supplies the right reachability basis and asset context. It is published on two MCP surfaces: the resource k9://rubric/risk-scoring and the prompt risk_scoring_rubric.

For why the verdicts look the way they do, including why a DEFER can still carry a Moderate NIST score, see How to Prioritize CVEs by Risk, Not Severity.

Request

score_risk(findings, scored_by_model?)
  • findings: a list of finding objects, max 50 per call.
  • scored_by_model: optional string naming the model that drove the scoring.

Each finding:

Field Required Type Notes
kind yes "software_vuln" "software_vuln" is the only kind of finding supported currently.
finding_key yes string Your unique key for this finding. Returned as the response key. Keep it stable across re-scans so usage dedupes.
vuln_id yes string CVE-…, GHSA-…, GO-…, or ALAS-…. KEV/EPSS are CVE-keyed; a non-CVE id still scores, with threat unknown.
reachable yes object {value, basis}. See below.
asset_context no object Environment context. Omit and the rubric assumes production / tier_2 / internal. See below.
project_id no string Groups findings by project. Does not affect a verdict.

Note: severity is not an input for risk scoring (why).

reachable

Whether the vulnerable code is reachable, and the basis for that call. value is true, false, or null; basis names why and must match the value. Your agent can determine reachability using the rubric's guidance or supply it from a specialized tool.

basis Pairs with Meaning
tool_confirmed true or false A reachability tool (e.g. call-graph analysis) produced the verdict.
manual_call_graph true Your own analysis confirmed the vulnerable symbol is reachable from an entry point with attacker-controlled input.
code_not_loaded false The vulnerable package or module is not imported anywhere.
unreachable_from_entrypoint false The code is loaded, but no path from any entry point reaches it.
no_attacker_controlled_input false The code is reachable, but only with first-party / trusted inputs.
undetermined null Neither a tool nor manual analysis could conclude. The floor of last resort.

asset_context

All fields optional. Unknown fields are treated as unknown, not guessed. Unrecognized keys are rejected, so a typo surfaces as an error rather than a silent mis-score.

Field Values
asset_criticality tier_1, tier_2, tier_3
data_classification public, internal, confidential, regulated
network_exposure public_unauth, public_auth, internal, isolated
lifecycle production, staging, dev
regulatory_scope list of strings, e.g. ["PCI", "SOC2"]

Response

The response is an object with the risk-scoring rubric_version, batch statistics (batch_stats), and a dictionary of verdicts keyed by finding_key. Each verdict contains:

Field Description
bucket FIX_TODAY, SCHEDULE, or DEFER. The action recommendation. Drive your work from this.
rationale One-line explanation of the verdict.
factors threat, exposure, impact, each present / absent / unknown with a confidence and rationale.
activated_floors Safety floors that changed the bucket. Empty means the factor assessment alone produced the verdict.
evidence The KEV / EPSS / reachability inputs echoed back, plus default_context_applied.
risk_assessment NIST SP 800-30 rendering: a qualitative band and a 0 to 100 composite, as inherent, residual, and retired (inherent minus residual). Report residual; retired is the risk the analysis accounted for.

Batch statistics

batch_stats summarizes the whole call so you can sanity-check a run before trusting it. The statistics primarily measure three quality dimensions: reachability discipline, how much scoring ran on defaulted context, and the batch size behind any noise-reduction claim.

Field Type Description
findings_total int Findings scored in this call. The denominator for a noise-reduction figure; reconcile it against the alert count you fetched.
buckets object Count per bucket, e.g. {"FIX_TODAY": 1, "SCHEDULE": 3, "DEFER": 38}.
reachability_basis_counts object Per-basis histogram of the reachable.basis values you supplied.
undetermined_fraction float Share of findings scored undetermined. Above ~0.10 usually means reachability analysis was skipped, not that it was genuinely inconclusive.
default_context_fraction float Share scored without caller-supplied asset_context.
quality_flags list of strings Threshold-derived flags that name a remedy, e.g. undetermined_above_10_percent, default_context_above_25_percent. Resolve these before reporting results.

Example:

"batch_stats": {
  "findings_total": 42,
  "buckets": { "FIX_TODAY": 1, "SCHEDULE": 3, "DEFER": 38 },
  "reachability_basis_counts": {
    "code_not_loaded": 25,
    "no_attacker_controlled_input": 8,
    "tool_confirmed": 5,
    "undetermined": 4
  },
  "undetermined_fraction": 0.095,
  "default_context_fraction": 0.0,
  "quality_flags": []
}

An empty quality_flags means the batch met the quality thresholds. A non-empty list is a signal to fix the inputs and re-score before acting on the verdicts.

Policy floors

Floors raise the recommendation when a condition is serious enough, and appear in activated_floors only when they changed the outcome.

  • kev_emergency: a KEV-listed vulnerability is never left at DEFER.
  • ransomware_floor: known ransomware-campaign use forces FIX_TODAY.
  • tier_1_asset: a real threat against a tier-1 asset is never left at DEFER.
  • public_asset_floor: a determined-unreachable finding on an internet-facing asset with real impact is raised to SCHEDULE, in case the reachability call is wrong.

Example

An illustrative unreachable finding on a public, critical asset. The values are representative of that case, not a captured run.

Request (one finding, unreachable but on a public, tier-1 service):

{
  "findings": [
    {
      "kind": "software_vuln",
      "finding_key": "payments-api/CVE-2025-12345",
      "vuln_id": "CVE-2025-12345",
      "reachable": { "value": false, "basis": "code_not_loaded" },
      "asset_context": {
        "asset_criticality": "tier_1",
        "network_exposure": "public_auth",
        "lifecycle": "production"
      }
    }
  ]
}

Response (illustrative, abridged):

{
  "rubric_version": "2026.07.18-v15",
  "verdicts": {
    "payments-api/CVE-2025-12345": {
      "bucket": "SCHEDULE",
      "activated_floors": ["public_asset_floor"],
      "factors": {
        "threat": "absent",
        "exposure": "absent",
        "impact": "present"
      },
      "risk_assessment": {
        "nist_800_30_qualitative": {
          "inherent": { "band": "very_high", "label": "Very High" },
          "residual": { "band": "moderate", "label": "Moderate" }
        },
        "nist_800_30_semi_quantitative": {
          "inherent": { "composite": 100 },
          "residual": { "composite": 50 },
          "retired": 50
        }
      }
    }
  },
  "batch_stats": { ... }
}

The code is not reachable, so exposure is honestly absent, but the public-asset floor keeps a finding on an internet-facing, material asset in the SCHEDULE queue. The residual score reflects the tail risk without inflating the exposure factor.


Last update: July 20, 2026