Security teams are drowning in spreadsheet-based risk registers that don’t scale, don’t automate, and can’t talk to other tools. If you’re a Python developer tasked with fixing that, this guide gives you a working risk assessment model you can run today.Scalable enterprise cyber risk management requires automation, but you can start by building asset-threat mappings, scoring risks programmatically, and outputting a prioritized report, all in plain Python.
- Python dataclasses let you model assets and threats with typed fields that are easy to extend and test.
- The NIST SP 800-30 risk formula, Risk = Likelihood x Impact, translates directly into a Python function.
- Sorting and filtering risk scores with Python’s built-in
sorted()gives you instant prioritization logic. - Wrapping your scoring logic in a
RiskAssessorclass makes the model reusable across projects. - Python risk scripts beat spreadsheets on repeatability and auditability, but human judgment still drives final decisions.
What Cyber Risk Assessment Means for Python Developers
Cyber risk assessment is the process of identifying assets, mapping threats to those assets, estimating the likelihood and impact of each threat, and producing a prioritized list of risks to address. Frameworks like NIST SP 800-30 and ISO 27005 describe this process in detail. Neither one ships a Python module.
Python fits this workflow naturally. You can represent assets and threats as structured data, apply scoring logic with functions, and output results to CSV, JSON, or a dashboard. The goal here is a working model, not a framework diagram.
Modeling Assets and Threats as Python Data Structures
Start with Python dataclasses. They give you typed fields, clean initialization, and easy serialization without the overhead of a full ORM.
from dataclasses import dataclass, field
from typing import List
@dataclass
class Asset:
name: str
asset_type: str # e.g., "server", "database", "endpoint"
criticality: int # 1 (low) to 5 (critical)
@dataclass
class Threat:
name: str
category: str # e.g., "network", "application", "physical"
likelihood: int # 1–5 scale
impact: int # 1–5 scale
@dataclass
class AssetThreatPair:
asset: Asset
threat: Threat
A realistic asset inventory might include a web server with criticality 4, a customer database at criticality 5, and an internal VPN endpoint at criticality 3. Map each to relevant threats, SQL injection, ransomware, credential stuffing, and you have the raw material for scoring.
web_server = Asset("web-server-01", "server", criticality=4)
sql_injection = Threat("SQL Injection", "application", likelihood=4, impact=5)
pair = AssetThreatPair(asset=web_server, threat=sql_injection)
This structure keeps your data clean and your logic separate. You can load it from a CSV or JSON file later without changing the scoring code.
Building a Risk Scoring Function in Python
The standard formula from NIST SP 800-30 is straightforward: Risk Score = Likelihood x Impact. You can weight this by asset criticality to get a composite score that reflects business context.
def calculate_risk_score(pair: AssetThreatPair) -> dict:
base_score = pair.threat.likelihood * pair.threat.impact
composite_score = base_score * pair.asset.criticality
if composite_score >= 75:
severity = "Critical"
elif composite_score >= 40:
severity = "High"
elif composite_score >= 15:
severity = "Medium"
else:
severity = "Low"
return {
"asset": pair.asset.name,
"threat": pair.threat.name,
"category": pair.threat.category,
"score": composite_score,
"severity": severity
}
What this code does: it multiplies likelihood by impact to get a base score, then multiplies by asset criticality to weight risks against business value. The severity labels map directly to the risk matrix quadrants you’d see in any ISO 27005 assessment.
Want to customize the weights? Adjust the criticality multiplier or add a separate vulnerability score field to your dataclass. The formula stays the same; the inputs change to match your organization’s threat model.
Prioritizing Risks with Python Sorting and Filtering
Once you have a list of scored risks, sorting them takes one line.
pairs = [
AssetThreatPair(web_server, sql_injection),
AssetThreatPair(Asset("db-01", "database", 5), Threat("Ransomware", "network", 3, 5)),
AssetThreatPair(Asset("vpn-01", "endpoint", 3), Threat("Credential Stuffing", "application", 4, 3)),
]
results = [calculate_risk_score(p) for p in pairs]
prioritized = sorted(results, key=lambda r: r["score"], reverse=True)
Filter to surface only Critical and High items for immediate action:
action_items = [r for r in prioritized if r["severity"] in ("Critical", "High")]
You can also group by threat category to focus remediation efforts. A network team needs a different view than an application security team.
from collections import defaultdict
by_category = defaultdict(list)
for r in prioritized:
by_category[r["category"]].append(r)
Generating a Risk Report from Your Python Model
A console table gets you started fast. For stakeholder sharing, export to CSV. For downstream tool integration, use JSON.
import csv, json
# Console output
print(f"{'Asset':<20} {'Threat':<25} {'Score':>6} {'Severity':<10}")
print("-" * 65)
for r in prioritized:
print(f"{r['asset']:<20} {r['threat']:<25} {r['score']:>6} {r['severity']:<10}")
# CSV export
with open("risk_report.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["asset","threat","category","score","severity"])
writer.writeheader()
writer.writerows(prioritized)
# JSON export
with open("risk_report.json", "w") as f:
json.dump(prioritized, f, indent=2)
The CSV lands directly in Excel or Google Sheets for security managers who don’t run Python. The JSON plugs into a SIEM, a ticketing system, or a dashboard API. You get both with ten lines of code.
Wrapping the Model into a Reusable RiskAssessor Class
Refactoring into a class makes the model testable, importable, and easy to extend.
class RiskAssessor:
def __init__(self):
self.pairs: List[AssetThreatPair] = []
self.results: List[dict] = []
def add_pair(self, asset: Asset, threat: Threat):
self.pairs.append(AssetThreatPair(asset, threat))
def run_assessment(self):
self.results = sorted(
[calculate_risk_score(p) for p in self.pairs],
key=lambda r: r["score"],
reverse=True
)
return self.results
def get_critical(self):
return [r for r in self.results if r["severity"] == "Critical"]
Using it looks like this:
assessor = RiskAssessor()
assessor.add_pair(web_server, sql_injection)
assessor.run_assessment()
print(assessor.get_critical())
Each method has one job. That makes unit testing straightforward with pytest and makes the class easy to subclass if you need custom scoring logic for a specific business unit.
Python Risk Assessment vs. Manual Spreadsheet Registers
Spreadsheets work fine for teams managing fewer than 50 assets with infrequent updates. They’re fast to set up and everyone knows Excel. The Python approach wins on repeatability, version control, and auditability. You can run the same assessment against updated threat data in seconds, commit the script to Git, and diff the results across quarters.
For teams managing hundreds of assets, integrating with CI/CD pipelines, or feeding risk data into other tools, the Python model isn’t optional. It’s the only approach that scales. Don’t build a Python risk assessor to replace a working spreadsheet. Build it when the spreadsheet is already failing you.
Extending Your Python Risk Model for Enterprise Scale
The model you’ve built here is a solid foundation. Here’s where to take it next:
- Use
pandasto ingest large asset inventories from CSV or Excel files withpd.read_csv(), then convert rows toAssetdataclasses in a loop. - Add
matplotliborplotlyto generate risk heat maps that plot likelihood against impact, with asset criticality encoded as dot size. - Connect likelihood scores to a CVE database or threat intelligence feed so your scoring reflects current vulnerability data, not static estimates.
- Add a
residual_riskfield to your dataclass to track risk after controls are applied, which is a requirement in ISO 27005 assessments.
Can a Python script replace a mature enterprise risk management platform? No. What it can do is automate the repeatable parts of risk assessment, produce consistent output, and give your security team a programmable starting point they actually control. That’s where the real value is.
The next logical step is loading your asset inventory from a real CSV file and running the full RiskAssessor pipeline against it. From there, explore the pyquery.org Python security scripting tag for guides on vulnerability scanning integration and automated report generation.
Frequently Asked Questions
What Python libraries are used for cybersecurity risk assessment?
For a custom risk assessment script, you need no external libraries beyond the Python standard library. For scaling up, pandas handles large asset inventories, matplotlib or plotly handles visualization, and requests connects to external threat intelligence APIs.
What does the risk scoring function return?
The calculate_risk_score() function returns a dictionary with the asset name, threat name, category, composite score, and severity label. This format works directly with Python’s csv.DictWriter and json.dump() for export.
How do I customize the risk weights for my organization?
Adjust the criticality multiplier in calculate_risk_score(), or add a vulnerability_score field to your Threat dataclass and include it in the formula. The NIST SP 800-30 framework supports both qualitative and quantitative weighting approaches.
Can I load asset data from a CSV file instead of hardcoding it?
Yes. Use pandas.read_csv() to load your asset inventory, then iterate over rows and create Asset dataclass instances. The same pattern works for threat data stored in a separate CSV.
What is the difference between qualitative and quantitative risk analysis in Python?
Qualitative analysis uses ordinal scales like 1 to 5 for likelihood and impact, which is what this guide implements. Quantitative analysis uses monetary values and probability percentages to calculate annualized loss expectancy. Both translate to Python functions; qualitative is faster to implement with limited data.

Ryan French is the driving force behind PyQuery.org, a leading platform dedicated to the PyQuery ecosystem. As the founder and chief editor, Ryan combines his extensive experience in the developer arena with a passion for sharing knowledge about PyQuery, a third-party Python package designed for parsing and extracting data from XML and HTML pages. Inspired by the jQuery JavaScript library, PyQuery boasts a similar syntax, enabling developers to manipulate document trees with ease and efficiency.
