Skip to content

Instantly share code, notes, and snippets.

@alexsavio
Last active April 13, 2026 16:11
Show Gist options
  • Select an option

  • Save alexsavio/3cdf6f284614fd4ea4d7a2b865ec5249 to your computer and use it in GitHub Desktop.

Select an option

Save alexsavio/3cdf6f284614fd4ea4d7a2b865ec5249 to your computer and use it in GitHub Desktop.
Dependency cooldown cost model — simulation and analytical check. Companion to https://alexsavio.github.io/dependency-cooldown-considered-harmful.html
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["numpy>=2.0"]
# ///
"""Monte Carlo + analytical validation of the dependency cooldown cost model.
Backs the claims in the blog post
`content/dependency-cooldown-considered-harmful.md`.
Model
-----
Each year, a dep tree sees:
- lambda_cve CVE disclosures (rate, /year). Each costs vuln_days_per_cve
days of exposure, where:
no cooldown: vuln_days_per_cve = audit_latency_days
cooldown C: vuln_days_per_cve = max(C, audit_latency_days)
- r_attack malicious releases (rate, /year) in packages you would
plausibly adopt. For each, detection delay D is drawn from a
3-component exponential mixture (typosquats, account comp, long-dwell).
Your natural adoption delay N_0 ~ Uniform(0, natural_adopt_max).
With cooldown C: effective adoption N = max(C, N_0).
You are compromised iff N < D.
Annual expected cost per policy:
cost = lambda_cve * vuln_days_per_cve * I_cve
+ r_attack * P(compromise) * I_attack
Break-even ratio I_attack / I_cve is solved from equating the two policies.
Run with:
uv run scripts/simulate_dependency_cooldown.py
"""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Final
import numpy as np
from numpy.typing import NDArray
SEED: Final[int] = 42
# --- Defaults matching the blog post --------------------------------------
LAMBDA_CVE: Final[float] = 25.0 # CVEs/year in your dep tree
R_ATTACK: Final[float] = 0.1 # malicious releases/year you could adopt
COOLDOWN_DAYS: Final[float] = 8.0
AUDIT_LATENCY_DAYS: Final[float] = 1.0 # no-cooldown case: daily audit
NATURAL_ADOPT_MAX: Final[float] = 7.0 # N_0 ~ U(0, 7) weekly upgrade cadence
# Detection-delay mixture: (weight, mean_days) for exponential components
MixtureComponent = tuple[float, float]
DETECTION_MIXTURE: Final[tuple[MixtureComponent, ...]] = (
(0.50, 0.5), # typosquats: hours
(0.30, 3.0), # compromised maintainer account: a few days
(0.20, 60.0), # long-dwell (xz, SolarWinds family)
)
assert math.isclose(sum(w for w, _ in DETECTION_MIXTURE), 1.0, abs_tol=1e-9), (
"DETECTION_MIXTURE weights must sum to 1"
)
_MIXTURE_WEIGHTS: Final[NDArray[np.float64]] = np.array(
[w for w, _ in DETECTION_MIXTURE], dtype=np.float64
)
_MIXTURE_MEANS: Final[NDArray[np.float64]] = np.array(
[m for _, m in DETECTION_MIXTURE], dtype=np.float64
)
N_ATTACK_SAMPLES: Final[int] = 2_000_000
MC_ANALYTIC_TOLERANCE: Final[float] = 0.002
SWEEP_COOLDOWNS: Final[tuple[float, ...]] = (0.0, 1.0, 2.0, 3.0, 5.0, 7.0, 8.0, 14.0, 30.0, 60.0, 90.0)
SHAPE_COOLDOWNS: Final[tuple[float, ...]] = (0.0, 1.0, 2.0, 5.0, 7.0, 14.0, 30.0)
REPORT_RATIOS: Final[tuple[float, ...]] = (1e2, 1e3, 1e4, 1e5, 1e6)
@dataclass(frozen=True)
class PolicyResult:
cooldown_days: float
vuln_days_per_year: float # contribution from CVE exposure
p_compromise: float # per malicious release
compromises_per_year: float # = R_ATTACK * p_compromise
# --- Policy builders -------------------------------------------------------
def _policy(cooldown_days: float, p_compromise: float) -> PolicyResult:
vuln_days_per_cve = max(cooldown_days, AUDIT_LATENCY_DAYS)
return PolicyResult(
cooldown_days=cooldown_days,
vuln_days_per_year=LAMBDA_CVE * vuln_days_per_cve,
p_compromise=p_compromise,
compromises_per_year=R_ATTACK * p_compromise,
)
# --- Vectorized sampling --------------------------------------------------
def sample_detection_delays(n: int, rng: np.random.Generator) -> NDArray[np.float64]:
"""Draw n samples from the exponential mixture (weights x means)."""
component = rng.choice(len(_MIXTURE_MEANS), size=n, p=_MIXTURE_WEIGHTS)
return rng.exponential(scale=_MIXTURE_MEANS[component])
def sample_natural_adoption(n: int, rng: np.random.Generator) -> NDArray[np.float64]:
return rng.uniform(0.0, NATURAL_ADOPT_MAX, size=n)
# --- Analytical closed forms ----------------------------------------------
def _survival_at(t: float) -> float:
"""S(t) = Pr(D > t) for the detection-delay mixture."""
return float(np.dot(_MIXTURE_WEIGHTS, np.exp(-t / _MIXTURE_MEANS)))
def analytical_p_compromise(cooldown: float) -> float:
"""P(max(C, N_0) < D) in closed form.
Split the N_0 integral at C:
P = (1/T) [C * S(C) + integral_{C}^{T} S(n) dn]
with S(t) = Pr(D > t) = sum_k w_k exp(-t/mean_k).
For C >= T the flat region covers [0, T] so P = S(C).
"""
t = NATURAL_ADOPT_MAX
if cooldown >= t:
return _survival_at(cooldown)
flat = cooldown * _survival_at(cooldown)
# integral_{C}^{T} w_k * exp(-n/mean_k) dn = w_k * mean_k * (exp(-C/mean_k) - exp(-T/mean_k))
tail = float(np.dot(
_MIXTURE_WEIGHTS * _MIXTURE_MEANS,
np.exp(-cooldown / _MIXTURE_MEANS) - np.exp(-t / _MIXTURE_MEANS),
))
return (flat + tail) / t
def analytical_policy(cooldown_days: float) -> PolicyResult:
return _policy(cooldown_days, analytical_p_compromise(cooldown_days))
# --- Monte Carlo -----------------------------------------------------------
def monte_carlo_policy(
cooldown_days: float,
rng: np.random.Generator,
n_samples: int = N_ATTACK_SAMPLES,
) -> PolicyResult:
d_mal = sample_detection_delays(n_samples, rng)
n0 = sample_natural_adoption(n_samples, rng)
n = np.maximum(cooldown_days, n0)
p_compromise = float(np.mean(n < d_mal))
return _policy(cooldown_days, p_compromise)
def run_paired_monte_carlo() -> tuple[PolicyResult, PolicyResult]:
"""Run both policies with common random numbers (seed reset per run)."""
no_cooldown = monte_carlo_policy(0.0, np.random.default_rng(SEED))
cooldown = monte_carlo_policy(COOLDOWN_DAYS, np.random.default_rng(SEED))
return no_cooldown, cooldown
# --- Cost functions --------------------------------------------------------
def total_cost(result: PolicyResult, i_attack_over_i_cve: float) -> float:
return result.vuln_days_per_year + result.compromises_per_year * i_attack_over_i_cve
def break_even_ratio(
cooldown: PolicyResult, no_cooldown: PolicyResult
) -> float:
"""Return I_attack/I_cve at which the two policies tie.
(vuln_days_co - vuln_days_nc) * I_cve = (compromises_nc - compromises_co) * I_attack
"""
extra_vuln_days = cooldown.vuln_days_per_year - no_cooldown.vuln_days_per_year
prevented = no_cooldown.compromises_per_year - cooldown.compromises_per_year
if prevented <= 0:
return math.inf
return extra_vuln_days / prevented
def optimal_cooldown(
policies: list[PolicyResult], i_attack_over_i_cve: float
) -> PolicyResult:
return min(policies, key=lambda r: total_cost(r, i_attack_over_i_cve))
# --- Self-check ------------------------------------------------------------
def assert_mc_matches_analytical(
mc: PolicyResult, analytical: float, label: str
) -> None:
diff = abs(mc.p_compromise - analytical)
if diff > MC_ANALYTIC_TOLERANCE:
raise AssertionError(
f"{label}: Monte Carlo {mc.p_compromise:.4f} vs analytical "
f"{analytical:.4f} differ by {diff:.4f} "
f"(tolerance {MC_ANALYTIC_TOLERANCE})"
)
# --- Report sections -------------------------------------------------------
def _print_header(title: str) -> None:
print()
print(title)
print("-" * len(title))
def _fmt_policy(r: PolicyResult) -> str:
return (
f"vuln-days/yr={r.vuln_days_per_year:6.1f} "
f"P(compromise)={r.p_compromise:.4f} "
f"compromises/yr={r.compromises_per_year:.5f}"
)
def print_config() -> None:
print("=" * 72)
print("Dependency Cooldown Cost Model - Simulation and Analytical Check")
print("=" * 72)
print(f"lambda_cve = {LAMBDA_CVE} CVEs/year")
print(f"r_attack = {R_ATTACK} malicious releases/year")
print(f"cooldown = {COOLDOWN_DAYS} days")
print(f"audit_latency = {AUDIT_LATENCY_DAYS} days")
print(f"natural_adopt_max = {NATURAL_ADOPT_MAX} days (uniform)")
print(f"detection_mixture = {DETECTION_MIXTURE}")
print(f"monte_carlo_samples = {N_ATTACK_SAMPLES:,}")
def print_analytical(p_nc: float, p_co: float) -> None:
_print_header("Analytical P(compromise per attack)")
print(f" no cooldown: {p_nc:.4f}")
print(f" cooldown C={COOLDOWN_DAYS:.0f}: {p_co:.4f}")
print(f" delta prevented: {p_nc - p_co:+.4f}")
def print_monte_carlo(no_cooldown: PolicyResult, cooldown: PolicyResult) -> None:
_print_header("Monte Carlo")
print(f" no cooldown: {_fmt_policy(no_cooldown)}")
print(f" cooldown={COOLDOWN_DAYS}: {_fmt_policy(cooldown)}")
def print_mc_vs_analytical(
no_cooldown: PolicyResult,
cooldown: PolicyResult,
p_nc: float,
p_co: float,
) -> None:
_print_header(f"Analytical vs Monte Carlo (tolerance {MC_ANALYTIC_TOLERANCE})")
print(f" P_nc: analytical={p_nc:.4f} MC={no_cooldown.p_compromise:.4f} "
f"diff={no_cooldown.p_compromise - p_nc:+.4f} [ok]")
print(f" P_co: analytical={p_co:.4f} MC={cooldown.p_compromise:.4f} "
f"diff={cooldown.p_compromise - p_co:+.4f} [ok]")
def print_cost_differential(
cooldown: PolicyResult, no_cooldown: PolicyResult
) -> None:
_print_header("Cost differential")
extra_vd = cooldown.vuln_days_per_year - no_cooldown.vuln_days_per_year
prevented = no_cooldown.compromises_per_year - cooldown.compromises_per_year
years_between_prevented = 1.0 / max(prevented, 1e-12)
print(f" guaranteed extra CVE exposure: {extra_vd:7.1f} vuln-days/year")
print(f" attacks prevented by cooldown: {prevented:.5f}/year")
print(f" = 1 compromise prevented every ~{years_between_prevented:,.0f} years")
def print_break_even(cooldown: PolicyResult, no_cooldown: PolicyResult) -> None:
_print_header("Break-even I_attack / I_cve")
print(f" {break_even_ratio(cooldown, no_cooldown):,.0f}")
print(" (a single compromise must be at least this many vuln-days of CVE")
print(" exposure to make an 8-day cooldown pay off for the default params)")
def print_shape_table(baseline: PolicyResult) -> None:
"""Side-by-side CVE cost and attack prevention for common cooldown windows.
This is the table reproduced in the blog post's section 4.
"""
_print_header("Shape of the tradeoff - common cooldown windows vs C=0 baseline")
print(f" {'C':>4} "
f"{'vuln-days/yr':>13} "
f"{'P(compr)':>10} "
f"{'prevented/yr':>14} "
f"{'1 per N yrs':>14} "
f"{'break-even':>12}")
for c in SHAPE_COOLDOWNS:
policy = analytical_policy(c)
prevented = baseline.compromises_per_year - policy.compromises_per_year
years_between = 1.0 / prevented if prevented > 1e-12 else math.inf
be = break_even_ratio(policy, baseline) if c > 0 else math.nan
print(
f" {c:>4.0f} "
f"{policy.vuln_days_per_year:>13.1f} "
f"{policy.p_compromise:>10.4f} "
f"{prevented:>14.5f} "
f"{years_between:>14,.0f} "
f"{be:>12,.0f}"
)
def print_sweep_table(policies: list[PolicyResult]) -> None:
_print_header("Cooldown sweep (analytical) - cost at several risk ratios")
print(f" {'C':>4} "
f"{'vuln-days/yr':>13} "
f"{'P(compr)':>10} "
f"{'cost@1e3':>12} "
f"{'cost@1e4':>12} "
f"{'cost@1e5':>12}")
for p in policies:
print(
f" {p.cooldown_days:>4.0f} "
f"{p.vuln_days_per_year:>13.1f} "
f"{p.p_compromise:>10.4f} "
f"{total_cost(p, 1e3):>12.2f} "
f"{total_cost(p, 1e4):>12.2f} "
f"{total_cost(p, 1e5):>12.2f}"
)
def print_sweep_optima(policies: list[PolicyResult]) -> None:
"""For each risk ratio, pick the cooldown C that minimizes total cost.
Total cost = CVE-exposure cost + expected attack cost, in vuln-day units
(so a risk ratio of 10,000 means one compromise == 10k vuln-days of
CVE exposure). Optimal C is the argmin over the sweep grid above.
"""
_print_header("Optimal cooldown per risk ratio (argmin of the sweep above)")
print(" Reads: 'if you value one compromise at N vuln-days of CVE exposure,")
print(" the cost-minimizing cooldown window is C days'.")
print()
print(f" {'I_attack/I_cve':>16} {'optimal C':>10} {'total cost':>14}")
for ratio in REPORT_RATIOS:
best = optimal_cooldown(policies, ratio)
print(
f" {ratio:>16,.0f} {best.cooldown_days:>8.0f}d "
f"{total_cost(best, ratio):>14.2f}"
)
def print_verdict(no_cooldown: PolicyResult, cooldown: PolicyResult) -> None:
_print_header("Verdict")
ratio = 1e4
zero_cost = total_cost(no_cooldown, ratio)
eight_cost = total_cost(cooldown, ratio)
print(f" At I_attack/I_cve = {ratio:,.0f} (one compromise == 10k vuln-days):")
print(f" no cooldown total cost = {zero_cost:.2f}")
print(f" cooldown=8 total cost = {eight_cost:.2f}")
winner = "no cooldown dominates" if eight_cost > zero_cost else "cooldown wins"
print(f" -> {winner} at this realistic risk ratio")
# --- Entry point -----------------------------------------------------------
def main() -> None:
print_config()
p_nc = analytical_p_compromise(0.0)
p_co = analytical_p_compromise(COOLDOWN_DAYS)
print_analytical(p_nc, p_co)
no_cooldown, cooldown = run_paired_monte_carlo()
print_monte_carlo(no_cooldown, cooldown)
assert_mc_matches_analytical(no_cooldown, p_nc, "no-cooldown")
assert_mc_matches_analytical(cooldown, p_co, f"cooldown={COOLDOWN_DAYS}")
print_mc_vs_analytical(no_cooldown, cooldown, p_nc, p_co)
print_cost_differential(cooldown, no_cooldown)
print_break_even(cooldown, no_cooldown)
print_shape_table(analytical_policy(0.0))
sweep = [analytical_policy(c) for c in SWEEP_COOLDOWNS]
print_sweep_table(sweep)
print_sweep_optima(sweep)
print_verdict(no_cooldown, cooldown)
if __name__ == "__main__":
main()
========================================================================
Dependency Cooldown Cost Model - Simulation and Analytical Check
========================================================================
lambda_cve = 25.0 CVEs/year
r_attack = 0.1 malicious releases/year
cooldown = 8.0 days
audit_latency = 1.0 days
natural_adopt_max = 7.0 days (uniform)
detection_mixture = ((0.5, 0.5), (0.3, 3.0), (0.2, 60.0))
monte_carlo_samples = 2,000,000
Analytical P(compromise per attack)
-----------------------------------
no cooldown: 0.3406
cooldown C=8: 0.1959
delta prevented: +0.1447
Monte Carlo
-----------
no cooldown: vuln-days/yr= 25.0 P(compromise)=0.3402 compromises/yr=0.03402
cooldown=8.0: vuln-days/yr= 200.0 P(compromise)=0.1955 compromises/yr=0.01955
Analytical vs Monte Carlo (tolerance 0.002)
-------------------------------------------
P_nc: analytical=0.3406 MC=0.3402 diff=-0.0004 [ok]
P_co: analytical=0.1959 MC=0.1955 diff=-0.0003 [ok]
Cost differential
-----------------
guaranteed extra CVE exposure: 175.0 vuln-days/year
attacks prevented by cooldown: 0.01447/year
= 1 compromise prevented every ~69 years
Break-even I_attack / I_cve
---------------------------
12,097
(a single compromise must be at least this many vuln-days of CVE
exposure to make an 8-day cooldown pay off for the default params)
Shape of the tradeoff - common cooldown windows vs C=0 baseline
---------------------------------------------------------------
C vuln-days/yr P(compr) prevented/yr 1 per N yrs break-even
0 25.0 0.3406 0.00000 inf nan
1 25.0 0.3134 0.00272 368 0
2 50.0 0.2887 0.00519 193 4,814
5 125.0 0.2354 0.01051 95 9,511
7 175.0 0.2071 0.01335 75 11,234
14 350.0 0.1612 0.01794 56 18,117
30 750.0 0.1213 0.02193 46 33,064
Cooldown sweep (analytical) - cost at several risk ratios
---------------------------------------------------------
C vuln-days/yr P(compr) cost@1e3 cost@1e4 cost@1e5
0 25.0 0.3406 59.06 365.59 3430.92
1 25.0 0.3134 56.34 338.40 3159.05
2 50.0 0.2887 78.87 338.66 2936.63
3 75.0 0.2695 101.95 344.45 2769.51
5 125.0 0.2354 148.54 360.45 2479.50
7 175.0 0.2071 195.71 382.07 2245.68
8 200.0 0.1959 219.59 395.88 2158.80
14 350.0 0.1612 366.12 511.20 1961.99
30 750.0 0.1213 762.13 871.32 1963.20
60 1500.0 0.0736 1507.36 1573.58 2235.76
90 2250.0 0.0446 2254.46 2294.63 2696.26
Optimal cooldown per risk ratio (argmin of the sweep above)
-----------------------------------------------------------
Reads: 'if you value one compromise at N vuln-days of CVE exposure,
the cost-minimizing cooldown window is C days'.
I_attack/I_cve optimal C total cost
100 1d 28.13
1,000 1d 56.34
10,000 1d 338.40
100,000 14d 1961.99
1,000,000 90d 6712.60
Verdict
-------
At I_attack/I_cve = 10,000 (one compromise == 10k vuln-days):
no cooldown total cost = 365.21
cooldown=8 total cost = 395.54
-> no cooldown dominates at this realistic risk ratio
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment