| name | risk-prioritiser |
| description | Risk-based prioritisation of confirmed attack paths. Combines exploit feasibility, technical CVSS severity, and asset business impact into a single ranked list driving remediation roadmaps. |
Risk Prioritiser
Consume the attack-path graph + the client-supplied business-tier map and emit a single ranked list of paths the org should remediate first. Mounted onto cloud-agent task #7.
Trigger
Cron daily after attack-path-stitcher (task #6) completes. Also event-fires when a new validated/*.json is written with nvd.score >= 9.0.
Workflow
- Load inputs.
artifacts/attack-paths.json — graph from task #6. Split into confirmed_paths (RFP-grade) and inferred_paths (topology/supply-chain).
business-tier-map.csv — client-supplied asset → tier. Search order: {OUTPUT_DIR}/business-tier-map.csv → projects/rfp-3.3/schemas/business-tier-map.csv.
- Score each
confirmed_paths entry.
feasibility = product of every edge's feasibility along the path. For confirmed paths this is always 1.0.
technical_severity = max_cvss_along_path / 10.
business_impact = tier weight of the destination (crown_jewel = 1.0 by definition).
entry_exposure = 1.0 if entry node is external: true, else 0.5.
- Final score =
feasibility × technical_severity × business_impact × entry_exposure.
- Score each single-asset validated finding that does not sit on a confirmed path. Same formula;
feasibility = 1.0, business_impact = asset's own tier weight, hop count = 1.
- Score each
inferred_paths entry, but cap their bucket placement at theoretical regardless of numeric score. Inferred paths NEVER reach immediate / short_term / medium_term — RFP §3.3 requires "confirmed", and inferred paths are evidence-deficient by construction.
- Sort descending. Stable tie-break order: max CVSS desc → hop count asc → finding age desc (newer first).
- Bucket into roadmap tiers.
immediate (0-7 days): confirmed AND score ≥ 0.6
short_term (7-30 days): confirmed AND 0.3 ≤ score < 0.6
medium_term (30-90 days): confirmed AND 0.1 ≤ score < 0.3
monitor: confirmed AND score < 0.1
theoretical (track-only): any path with path_class != "confirmed". Surfaced in board reports but excluded from the remediation SLA roadmap.
- Write outputs to
artifacts/attack-paths-ranked.json + attack-paths-ranked.md. Each row carries path_class so downstream consumers can filter.
Tier weights
| Tier | Weight |
|---|
crown_jewel | 1.00 |
revenue | 0.70 |
support | 0.40 |
dev | 0.20 |
unknown | 0.30 |
The unknown weight is deliberately above support to bias toward investigating un-mapped assets — they often turn out to be high-tier once discovered.
Output
{OUTPUT_DIR}/
artifacts/
attack-paths-ranked.json
attack-paths-ranked.md
attack-paths-ranked.json schema:
{
"generated_at": "2026-05-13T03:30:00Z",
"tier_weights_used": {"crown_jewel": 1.0, "revenue": 0.7, "support": 0.4, "dev": 0.2, "unknown": 0.3},
"ranked": [
{
"rank": 1,
"kind": "path",
"path_class": "confirmed",
"path_id": "asset05->asset42->asset99",
"hops": ["asset05", "asset42", "asset99"],
"feasibility": 1.0,
"max_cvss": 9.8,
"business_impact": 1.0,
"entry_exposure": 1.0,
"score": 0.98,
"bucket": "immediate",
"remediation_focus": "asset05"
},
{
"rank": 2,
"kind": "finding",
"path_class": "confirmed",
"finding_id": "finding-018",
"asset": "asset42",
"feasibility": 1.0,
"max_cvss": 9.1,
"business_impact": 0.7,
"entry_exposure": 1.0,
"score": 0.637,
"bucket": "immediate"
}
],
"buckets": {"immediate": 4, "short_term": 11, "medium_term": 22, "monitor": 8, "theoretical": 14}
}
Remediation focus
For each ranked path, pick a single remediation_focus asset — the one whose patch breaks the chain at the lowest cost:
- If any edge has feasibility < 1.0 → focus on that edge's source asset. The weakest pivot is the attacker's cheapest hop; hardening it eliminates the easiest entry.
- If every edge is feasibility 1.0 (fully confirmed chain) → focus on the path's entry asset (
hops[0]). Patching the externally-reachable foothold breaks every downstream hop and is the cheapest patch surface operationally.
This rule yields a single deterministic asset id per path. The previous "edge contribution formula" was deprecated — on uniformly-confirmed chains every edge has identical contribution, which gives no useful signal.
Rules
- Always derive from
attack-paths.json. Do not re-derive feasibility / CVSS — those are already validated by upstream tasks.
business-tier-map.csv is authoritative. If an asset is missing from the map, use unknown tier weight + flag unmapped_assets[] at the top of the JSON output.
- Stable rank for stable input. Re-running on identical input must produce byte-identical output.
- No new findings. Prioritisation never creates findings; it only re-orders.
- Bucket thresholds are config. The 0.6/0.3/0.1 cuts can be overridden via
--thresholds argument when running on a client with different remediation cadences. Default values match the Transilience report's "Immediate / Short-term / Medium-term" labels.
Implementation
The scoring is implemented deterministically by tools/risk-prioritise.py (not delegated to an LLM agent), to satisfy Rule 3 (stable rank for stable input). The tool reads artifacts/attack-paths.json and writes the two output files. Override the tier weights or bucket thresholds with --tier-weights / --thresholds JSON arguments — recorded in the output for audit.
References
reference/scoring-formula.md — worked example + edge-case behaviour.
projects/rfp-3.3/schemas/business-tier-map.csv — input schema and example rows.
tools/risk-prioritise.py — deterministic implementation.