DEV Community

Gaganpreet Singh
Gaganpreet Singh

Posted on Originally published at willeysingh.wordpress.com

Modelling Worm Outbreaks in Python to See Which Defences Actually Help

Every postmortem of a fast-moving outbreak ends with the same questions. Would segmentation have contained it? How much did the patch backlog matter? Would quarantining infected hosts at hour two have changed anything? In a real environment you cannot rerun the incident to find out. In a model, you can rerun it as many times as you like.

That is why I built the Worm Propagation Simulator. It is a small Python project that models how a self-replicating worm spreads across a network graph, and how defensive measures change the outcome. It is built for defensive analysis and teaching. It contains no exploit code, sends no network traffic and touches no real systems. Every “host” is a node in an in-memory graph, and every “infection” is a random number compared against a probability.

Repo: github.com/Willey2003/worm-propagation-simulator

How the model works

The simulation runs in ticks. The engine builds a network graph with NetworkX and gives every node a state: vulnerable, infected, patched or quarantined. Each node gets a security level of low, medium or high, which sets its chance of being compromised on a single attempt to 0.75, 0.5 or 0.25. A configurable share of nodes starts out already patched. One or more “patient zero” nodes start out infected.

On each tick, every infected node picks targets according to the chosen propagation model. For each target, the engine checks three things in order: whether the target is still susceptible, whether a firewall rule between their subnets blocks or rate-limits the attempt, and finally the probability roll. The engine records per-tick metrics as it goes: node counts by state, new infections, attempts, and attempts blocked by firewall rules.

What you can vary

Dimension Options in the code
Topology random (Erdős–Rényi), scale_free (Barabási–Albert), small_world (Watts–Strogatz), hierarchical (balanced tree), datacenter (fully meshed racks linked to each other)
Propagation model random_scan, hitlist, topological (graph neighbours only), permutation, local_preference (biased towards the node’s own subnet)
Baseline defence patch_rate: the fraction of nodes patched before the outbreak starts
Segmentation Subnet-to-subnet firewall rules with deny or rate_limit (percentage of attempts allowed)
Mid-outbreak response One scheduled intervention: patch_all, patch_subnet, quarantine_infected or rate_limit
Reproducibility A seed value fixes the random state, so runs are repeatable

Nodes are assigned round-robin to subnets of about 20 nodes each. This is what gives the segmentation rules and the local-preference model something to act on.

The configs/ folder ships six YAML scenarios: five are loosely inspired by well-known worms (Code Red, SQL Slammer, Conficker, WannaCry, Mirai), plus a defensive_analysis scenario. Treat them as stylised parameter sets for comparison, not calibrated reproductions of history. A 500-node graph says nothing precise about the 2001 internet. What they do give you is a fair way to compare propagation behaviour and defensive choices against each other.

Running it

The core engine needs only NetworkX, NumPy and PyYAML. The test suite (17 tests covering nodes, topologies, propagation models, patching and firewall blocking) also runs in GitHub Actions on every push:

git clone https://github.com/Willey2003/worm-propagation-simulator.git
cd worm-propagation-simulator
pip install networkx numpy pyyaml pytest
python -m pytest tests/ -v
Enter fullscreen mode Exit fullscreen mode

To run a scenario and print the summary:

import yaml
from src.simulator.engine import create_simulator_from_config

cfg = yaml.safe_load(open("configs/defensive_analysis.yaml"))
sim = create_simulator_from_config(cfg)
sim.run()
summary = sim.get_summary()
print({k: v for k, v in summary.items() if k != "infection_curve"})
Enter fullscreen mode Exit fullscreen mode

The summary includes the peak infected count, the tick at which it peaked, total infections and attempts, and how many attempts were blocked. infection_curve holds the full per-tick history if you want to plot it.

The lesson that surprised me: timing beats thoroughness

The defensive_analysis scenario puts 500 nodes on a scale-free graph and 20% of them start patched. It applies two firewall rules between subnets and schedules a patch_all intervention at tick 30. With seed 42, that intervention never fires. The worm reaches every vulnerable node by tick 5 and the run ends, with 396 nodes infected.

Change one line, intervention_tick: 2, and the same patch_all caps the outbreak at 7 infected nodes out of 500.

The model is simple, but this is the same lesson real incidents keep teaching. Against a fast scanner, a complete response that arrives late is worth almost nothing. A response that arrives early is worth almost everything. That is the case for pre-approved containment playbooks and for patching exposed services before an outbreak, not during one.

Other entry points

The repo also includes:

  • A Typer CLI with run, compare and scenarios commands, using Rich tables for output
  • A FastAPI service to create simulations, step them, apply interventions mid-run, fetch state over REST, and stream per-tick metrics over a WebSocket
  • A Plotly visualisation module that writes the infection curve, network graph and a combined dashboard to HTML
  • Docker, Docker Compose and Kubernetes manifests for running the API in a container

From the command line, the same runs are one command each:

python -m src.simulator.main scenarios
python -m src.simulator.main run --scenario code_red
python -m src.simulator.main compare --scenarios code_red,slammer
Enter fullscreen mode Exit fullscreen mode

The compare command prints a side-by-side table of infections, peak timing and blocked attempts for each scenario.

Ideas for extending it

Ideas I would like to explore next: per-node detection delay before quarantine, more than one intervention per run, and calibrating scan rates against published outbreak data. If you teach network security, the topology comparison on its own makes a good lab exercise. Run the same worm on a random graph and on a scale-free graph and look at what the highly connected hubs do to the curve.

Get it

The source, scenario files and tests are at github.com/Willey2003/worm-propagation-simulator. Clone it, change one parameter at a time, and see which defences change the curve. If you build a scenario that teaches something useful, open a pull request.

Top comments (0)