I used Allen Downey’s ThinkBayes2 library to practice Diachronic Bayes, the process of updating a hypothesis ($H$) based on new data (D).
- The Problem: I worked through the “Cookie Problem” and “Monty Hall Problem” to understand the mechanics of the formula: P(H|D) = (P(H)P(D|H))/(P(D)).
- Application to Risk: I treated the “Prior” (P(H)) as our initial risk assessment (e.g., “There is a 10% chance of a breach”). I then calculated the “Likelihood” (P(D|H)) based on new evidence (e.g., “A firewall log showed 5 failed login attempts”).
- Outcome: The calculation produced a “Posterior” probability, mathematically demonstrating that risk is dynamic. This highlighted a flaw in traditional “static” risk registers, which often fail to account for real-time threat intelligence.
Description: This script adapts the ‘Think Bayes’ methodology to a security context. It updates the probability of a specific threat (Hypothesis) being active after observing a specific indicator (Evidence).
# A simplified class structure based on Downey's 'Pmf' (Probability Mass Function)class RiskHypothesis: def __init__(self, priors): """ priors: Dictionary of {Hypothesis: Probability} e.g., {'High_Risk': 0.1, 'Low_Risk': 0.9} """ self.hypotheses = priors def normalize(self): """Ensures all probabilities sum to 1.0""" total = sum(self.hypotheses.values()) for hypo in self.hypotheses: self.hypotheses[hypo] /= total def update(self, evidence, likelihoods): """ Bayes Theorem Application: P(H|E) = P(H) * P(E|H) / P(E) evidence: String name of the evidence observed likelihoods: Dictionary of {Hypothesis: Probability_of_Evidence} """ for hypo in self.hypotheses: # 1. Get the Prior P(H) prior = self.hypotheses[hypo] # 2. Get the Likelihood P(E|H) # "If this hypothesis were true, how likely is this evidence?" likelihood = likelihoods[hypo] # 3. Calculate Un-normalized Posterior self.hypotheses[hypo] = prior * likelihood # 4. Normalize (dividing by P(E)) self.normalize()# --- USE CASE: INCIDENT RESPONSE ---# Scenario: We see a failed login. Is it a Brute Force Attack or just a User Mistake?# 1. ESTABLISH PRIORS (Baseline probability)# We assume Brute Force attacks are rare (10%) compared to User Mistakes (90%)priors = {'Brute_Force': 0.1, 'User_Mistake': 0.9}risk_model = RiskHypothesis(priors)print("--- PRIOR BELIEFS ---")print(risk_model.hypotheses)# 2. NEW EVIDENCE: 5 Failed Logins in 1 Minute# Likelihood P(E|H):# - If it IS a Brute Force attack, 5 fails in 1 min is very likely (90%)# - If it IS a User Mistake, 5 fails in 1 min is rare (5%)evidence_likelihoods = { 'Brute_Force': 0.90, 'User_Mistake': 0.05}# 3. UPDATE BELIEFSrisk_model.update(evidence="5_fails_1_min", likelihoods=evidence_likelihoods)print("\n--- POSTERIOR BELIEFS (After Evidence) ---")for hypo, prob in risk_model.hypotheses.items(): print(f"{hypo}: {prob:.2%}")# Result: The probability of 'Brute_Force' will jump significantly
References:
- Downey, A. (2022) Think Bayes 2. Available at: https://allendowney.github.io/ThinkBayes2/.