An AI-assisted pipeline that takes raw security findings — the messy, inconsistent output of scanners, audits, and pentests — and produces a financially ranked remediation backlog. A large language model normalizes each finding into structured risk inputs, a Return on Security Investment (ROSI) calculation converts those inputs into dollar-denominated priorities, and a reporter emits a ranked CSV plus an executive-ready markdown summary.
Most security backlogs are sorted by a High / Medium / Low label that came out of whichever tool found the issue. The result is predictable:
- Everything is Critical. When fifteen scanners each apply their own severity scheme, "Critical" stops being a signal — it becomes the default for anything that doesn't look harmless.
- No financial logic. A "Critical" SQL injection on a customer-data endpoint and a "Critical" missing TLS 1.2 header on a marketing brochure site look identical in the queue, even though one carries six figures of expected annual loss and the other carries hundreds of dollars.
- No defensible "what to fix first." When a CISO asks why this issue is sitting above that one, the honest answer is usually "the scanner said so" — which is not an answer you can take to a finance committee.
This project replaces the label with a number. Every finding is reduced to an estimated Annualized Loss Expectancy (ALE), a remediation cost, and the ROSI multiple between them. The backlog is sorted by that multiple.
- LLM normalization. Each raw finding (free-text scanner output) is passed to a calibrated system prompt that returns a strict JSON object:
risk_domain,severity,threat_frequency,vulnerability_probability,asset_value_usd,control_effectiveness,estimated_remediation_cost_usd,risk_reduction_pct, and a one-line rationale. The prompt encodes CVSS → probability bands, CISA KEV overrides, and pentest-evidence overrides so two findings with similar text receive similar numbers. - ROSI scoring. The structured inputs feed a deterministic FAIR-style calculation:
ALE = asset_value × threat_frequency × vulnerability_probability × (1 − control_effectiveness)Residual ALE = ALE × (1 − risk_reduction_pct)ROSI = (ALE − Residual ALE − Remediation Cost) / Remediation CostFindings are then bucketed into five priority tiers (Immediate → Deprioritize) by ROSI multiple.
- Ranked backlog. The full set is sorted by ROSI, printed as a terminal table, written to
output/ranked_remediation_backlog.csv, and summarized inoutput/executive_summary.mdwith a portfolio overview, a top-5 table, and a recommended sprint plan with target dates derived from each finding's tier.
.
├── data/
│ └── sample_issues.csv # 15 raw findings across 8 source types
├── src/
│ ├── normalizer.py # LLM call + JSON parsing + calibration prompt
│ ├── scorer.py # ALE / residual ALE / ROSI / priority tier
│ └── reporter.py # Terminal table, CSV writer, markdown writer
├── output/ # Generated; created on first run
│ ├── ranked_remediation_backlog.csv
│ └── executive_summary.md
├── prioritize.ipynb # Notebook that wires the three modules together
├── requirements.txt
└── README.md
src/normalizer.py— exposesnormalize_finding(raw_description, source_tool) -> dict. Calls a local LLM via theollamaPython package usingformat='json'for structured output. Strips markdown fences defensively and returns a dict ofNones + anerrorfield if anything fails to parse.src/scorer.py— exposes the four math primitives (calculate_ale,calculate_residual_ale,calculate_rosi,assign_priority_tier) plusscore_issue(normalized) -> dictwhich runs the whole chain and returns a combined record with ALE, residual ALE, financial benefit, ROSI, priority tier, and recommended action.src/reporter.py— exposesgenerate_report(scored_issues) -> None. Sorts by ROSI desc, prints atabulatetable to stdout, prints a portfolio summary block, writes the full backlog (including the LLM's rationale field) to CSV, and writes a stakeholder-facing markdown report with a top-5 sprint plan.prioritize.ipynb— seven cells: imports → load CSV → normalize with progress bar → score → report → three matplotlib/seaborn charts → export paths.
# 1. Install Python dependencies
pip install -r requirements.txt
# 2. Install and start the local LLM backend
# (uses Ollama; swap-point is src/normalizer.py if you prefer a hosted API)
ollama pull llama3.1:8b
ollama serve
# 3. (Optional) override defaults via .env
echo "OLLAMA_MODEL=llama3.1:8b" > .env
echo "OLLAMA_HOST=http://localhost:11434" >> .env
# 4. Launch the notebook and run all cells
jupyter notebook prioritize.ipynbNote on the LLM backend. Normalization runs against a local Ollama model (
llama3.1:8bby default), so the pipeline works offline with no paid API key. The entire LLM integration is isolated tosrc/normalizer.py— the calibration prompt, JSON parsing, and downstream ROSI scoring are provider-agnostic, so swapping in a different model or a hosted provider is a one-function change.
The scoring model is a simplified, single-issue variant of the FAIR (Factor Analysis of Information Risk) framework — see Measuring and Managing Information Risk: A FAIR Approach (Freund & Jones, 2014) and the Open FAIR standards (Open Group, O-RA / O-RT). FAIR decomposes risk into Loss Event Frequency × Loss Magnitude; this project's threat_frequency × vulnerability_probability is the LEF component and asset_value × (1 − control_effectiveness) is the LM component, scaled annually.
Probability calibration draws from the Verizon Data Breach Investigations Report (DBIR) — annualized attack-attempt rates against internet-facing services, the long-known dominance of credential and web-application vectors, and the empirical distribution of CVSS scores observed in breaches. CISA KEV-listed vulnerabilities are floored at a higher threat_frequency because the catalog is, by definition, evidence of in-the-wild exploitation.
Control-effectiveness ranges are anchored to NIST SP 800-53 Rev. 5 control families — when the LLM reads "MFA enforced," "WAF in front," "EDR present," or "network segmented," it should bias control_effectiveness upward in proportion to which 800-53 families those controls cover (AC, SC, SI, IA). Remediation cost estimates draw on industry-published patch-management labor estimates and typical engineering-day rates.
The framework does not claim to predict any individual loss event. It claims to produce a consistent, defensible ordering — given two findings, the one with the higher ROSI should be fixed first, and the reason can be shown in dollars.
After running the notebook against the 15 sample findings in data/sample_issues.csv, the pipeline produces three artifacts:
1. Terminal ranked table — top of the backlog sorted by ROSI multiple:
+------+--------------------------------------------+------------+----------+--------+-----------+
| Rank | Finding | Risk Domain| ALE ($) | ROSI | Tier |
+------+--------------------------------------------+------------+----------+--------+-----------+
| 1 | Unpatched RCE — internet-facing web app | Infra | 4,500,000| 161x | Immediate |
| 2 | SQL injection — customer portal | App | 4,500,000| 58x | Immediate |
| 3 | Vendor excessive data access | Third-party| 975,000 | 38x | Immediate |
| 4 | TLS 1.0 on API gateway | Network | 975,000 | 28x | Immediate |
| 5 | IDOR — HR application | App | 975,000 | 25x | Immediate |
+------+--------------------------------------------+------------+----------+--------+-----------+
Portfolio: 15 findings · Total ALE: $14.2M · Recommended sprint ALE reduction: $9.1M
2. output/ranked_remediation_backlog.csv — the full backlog with the LLM's rationale field preserved per row. Suitable for direct paste into a GRC tool or JIRA.
3. output/executive_summary.md — a stakeholder-facing markdown report with portfolio overview, top-5 sprint plan, and recommended target dates per priority tier. Drop into a Risk Committee read-out as-is.
- The ranked backlog this pipeline produces is schema-compatible with the Issue Register tab of the
security-risk-registerExcel workbook — paste the CSV, and the monthly board dashboard updates automatically. - For the regulatory finding (not security finding) equivalent, see
compliance-copilot— same Ollama backend, similar remediation JSON schema, applied to GDPR / DORA / NIS2 / SOC 2 / ISO 27001 audit findings instead of vulnerability-scanner output. - For the portfolio-level financial exposure that the ALE numbers in this tool sum into, see
cyber-risk-quantificationand theCRQ-Dashboard. - Methodology source: CRQ-F Framework Phase 1 (Input Calibration) + Phase 2 (Control ROI), addressing gap G4 (GRC-to-Quant Bridge).
Built by Rafat Yazdani — CPA · CISSP · CISA · CCSK · AWS Certified Security Strategy & Cyber Risk Quantification — Accenture Security
Eleven-plus years in GRC, cyber risk quantification, and security strategy across Accenture, Schellman & Company, EY, and BDO USA. Specialty: translating technical risk into board-ready financial decisions for digital-native and regulated-industry clients.
Methodology backbone: CRQ-F Framework (Cyber Risk Quantification — Financial). Portfolio overview: github.com/rafatyazdani — Methodology · Quantification math · Consulting products · Governance & maturity · Operations · Board output.