With all the discussion about AI escaping controlled environments, I started thinking about a simpler question: what does it look like when something begins in one location, spreads to other locations, and continues until someone detects and stops it?
That question turned into a Bash project.
I built a terminal-based simulation that shows how an infection can move across folders and files, how quickly it can spread, and how quarantine and cleanup affect the result.
This is not a real worm. The program does not copy or execute itself, scan a network, exploit another system, or access another computer. Everything happens inside a controlled laboratory directory with no network access.
The infection is represented only by a harmless line of text.
Creating the Laboratory
When the program starts or is reset, it creates its own working environment:
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LAB_DIR="$PROJECT_DIR/worm-lab"
WORK_DIR="$LAB_DIR/working-directory"
Everything generated by the experiment stays inside worm-lab.
The working directory represents a small group of fictional computers or endpoints. Instead of using actual devices, the program uses folders.
It creates 30 folders during each run.
How the Random Folder Names Are Created
The script uses a helper function to create a random suffix.
A simplified version looks like this:
random_suffix() {
printf '%04x%04x' "$RANDOM" "$RANDOM"
}
Bash provides the built-in $RANDOM variable, which returns a different number each time it is used.
The function converts two random numbers into hexadecimal text. That produces strings similar to:
08f49a12
54a8e130
a19d643b
The program adds node- to the beginning of each suffix:
name="node-$(random_suffix)"
The resulting folder names look like:
node-08f49a12
node-54a8e130
node-a19d643b
The program checks whether the name already exists before creating the folder:
while ((created < HOST_COUNT)); do
name="node-$(random_suffix)"
[[ -e "$WORK_DIR/$name" ]] && continue
mkdir -p "$WORK_DIR/$name"
((created++))
done
If the random function happens to produce a duplicate name, the script skips it and generates another one.
This continues until all 30 unique folders have been created.
I used random folder names because I did not want the program to depend on fixed names such as:
pc-01
pc-02
pc-03
Instead, it must discover which folders exist during the current run. This makes each simulation slightly different and gives the project a more realistic discovery process.
Creating a Random File in Each Folder
After creating a folder, the program creates one randomly named text file inside it.
The filename uses the same random-suffix function:
filename="data-$(random_suffix).txt"
That produces filenames such as:
data-470ac1f2.txt
data-a82d3140.txt
data-193bf804.txt
The program then generates random text for the file.
Each file contains between 1 and 255 characters:
random_text() {
local length=$((RANDOM % 255 + 1))
local chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,;:!?_-/'
local output=""
local i index
for ((i = 0; i < length; i++)); do
index=$((RANDOM % ${#chars}))
output+="${chars:index:1}"
done
printf '%s' "$output"
}
This line chooses the file length:
length=$((RANDOM % 255 + 1))
The result can be any number from 1 through 255.
The function then repeatedly chooses a random position from the allowed character set:
index=$((RANDOM % ${#chars}))
It takes the character at that position and adds it to the output:
output+="${chars:index:1}"
After the required number of characters has been generated, the program writes the text into the random file:
content="$(random_text)"
printf '%s' "$content" > "$WORK_DIR/$name/$filename"
A generated folder might therefore look like this:
node-54a8e130/
└── data-470ac1f2.txt
The contents might look like:
Df72aKq!9_Pm41zT8x
Another folder may contain only a few characters, while another may contain close to 255.
Using random file lengths and contents means the cleaner cannot rely on every file having the same structure. It must search for the exact infection marker and preserve whatever original text surrounds it.
Recording the Original File Length
Before any infection occurs, the program records the original length of each file.
Conceptually, it stores information similar to:
ORIGINAL_LENGTH["$name"]=${#content}
This gives the program a baseline.
For example:
Original file length: 143
When the infection marker is added, the file becomes longer. After cleaning, the program can verify whether the file has returned to its original size.
This provides a basic demonstration of file-integrity checking.
How the Infection Starts
Once the 30 folders and files have been created, the program discovers the folders in the working directory.
It does not assume which names were generated.
A simplified discovery step looks like this:
mapfile -t HOSTS < <(
find "$WORK_DIR" \
-mindepth 1 \
-maxdepth 1 \
-type d \
-printf '%f\n' |
sort
)
This command:
- Searches the working directory.
- Finds only directories.
- Retrieves their names.
- Sorts the names.
- Stores them in the
HOSTSarray.
Although the folder names are random, sorting them gives the simulator a consistent order for the current run.
The program then selects the first discovered folder:
seed_host="${HOSTS[0]}"
That folder becomes the initial infected host.
The program does not copy the Bash script into it. It does not execute anything inside the folder.
Instead, it opens the folder’s text file and inserts a harmless marker.
CONTENT_MARKER="WORMLAB_SIM_INFECTION_MARKER_7F3A9C"
The initial seed operation is similar to:
infect_folder "$seed_host" "INITIAL-SEED"
The second value identifies the source of the infection. Since no other folder caused the first infection, its source is recorded as the initial seed.
The event log might show:
Seeded node-08f49a12 as the initial infected folder.
At this point, the simulation begins with:
1 infected folder
29 clean folders
How the Marker Is Inserted
The program finds the data file inside the selected folder and reads its contents:
file="$(host_data_path "$target")"
content="$(<"$file")"
It calculates the middle position of the text:
midpoint=$((${#content} / 2))
It then divides the original content into two parts and places the marker between them:
printf '%s%s%s' \
"${content:0:midpoint}" \
"$CONTENT_MARKER" \
"${content:midpoint}" > "$temporary"
Suppose the original file contains:
Xf9pQ2mT7zL4
The file contains 12 characters, so its midpoint is position 6.
The infected version becomes:
Xf9pQ2WORMLAB_SIM_INFECTION_MARKER_7F3A9CmT7zL4
The original text remains present. It has only been divided so the marker can be inserted in the middle.
The temporary file is then moved over the original:
mv -- "$temporary" "$file"
Writing to a temporary file first helps avoid leaving a partially written file if something goes wrong during the operation.
The Controlled Propagation Algorithm
After the first folder is seeded, the simulator uses a cycle-based propagation algorithm.
At the beginning of every cycle, it creates a snapshot of folders that are:
- Infected
- Not quarantined
for name in "${HOSTS[@]}"; do
if [[ "${STATE[$name]}" == "infected" &&
"${QUARANTINED[$name]}" == "0" ]]; then
sources_snapshot+=("$name")
fi
done
Each active infected folder is allowed to affect one clean, non-quarantined folder:
for source in "${sources_snapshot[@]}"; do
target="$(next_clean_folder 2>/dev/null || true)"
[[ -z "$target" ]] && break
infect_folder "$target" "$source"
done
Because every infected folder can add one more infection during the next cycle, the number of affected folders grows quickly:
Initial seed: 1 infected
Cycle 1: 2 infected
Cycle 2: 4 infected
Cycle 3: 8 infected
Cycle 4: 16 infected
Cycle 5: 30 infected
The growth is approximately exponential.
Newly infected folders do not begin spreading during the same cycle in which they were infected. They must wait until the next cycle.
This happens because the source list is captured before propagation begins.
That makes the simulation easier to observe and prevents one newly infected folder from immediately affecting several others during the same cycle.
Tracking Where the Infection Came From
Every time a clean folder becomes infected, the program records:
- The target folder
- The source folder
- The cycle number
INFECTED_BY["$target"]="$source"
INFECTED_CYCLE["$target"]="$CYCLE"
REPLICATION_HISTORY+=("C$CYCLE $source → $target")
The dashboard can then display an infection history such as:
C1 node-08f49a12 → node-18bf733c
C2 node-08f49a12 → node-492d70ae
C2 node-18bf733c → node-54a8e130
This creates a simple infection lineage.
It shows not only which folders are affected, but also how the simulated infection reached them.
Viewing the Infection
The program includes an IDE-style dashboard with four main areas:
- Overview
- Host Inspector
- Analytics
- File Viewer
The Overview displays the number of infected, clean, and quarantined folders.
The Host Inspector displays information about the selected folder:
Folder: node-54a8e130
File: data-470ac1f2.txt
State: INFECTED
Infected by: node-18bf733c
Infection cycle: 2
Marker present: Yes
The File Viewer opens the selected file and shows its contents.
The program finds the position of the marker:
before="${content%%"$CONTENT_MARKER"*}"
marker_start=${#before}
marker_end=$((marker_start + ${#CONTENT_MARKER}))
While printing the file, the exact marker is displayed in bold yellow:
if ((i >= marker_start && i < marker_end)); then
printf '%s%s%s%s' "$BOLD" "$YELLOW" "$char" "$RESET"
else
printf '%s' "$char"
fi
This makes the simulated infection easy to identify while leaving the surrounding random text in the terminal’s normal color.
Pressing Esc, B, or V returns from the File Viewer to the previous dashboard screen.
Quarantine Versus Cleanup
The simulator treats quarantine and cleanup as separate actions.
Quarantine prevents a folder from participating in propagation.
An infected quarantined folder cannot affect another folder. A clean quarantined folder cannot be selected as a target.
The marker remains inside the file until the cleaner removes it.
This helped demonstrate an important lesson: containment and remediation are not the same thing.
Sometimes the first priority is stopping additional spread. Cleanup can happen after the affected locations have been isolated.
How the Cleaner Finds the Marker
The cleaner examines the text file inside each folder and searches for the exact marker:
if grep -Fq -- "$CONTENT_MARKER" "$file"; then
printf '%s' "$file"
fi
The -F option tells grep to treat the marker as ordinary fixed text.
The -q option keeps the search quiet because the program only needs a yes-or-no result.
Once the marker is found, the cleaner removes only that exact sequence:
content="$(<"$file")"
cleaned="${content//$CONTENT_MARKER/}"
It then writes the cleaned text to a temporary file and replaces the infected version:
printf '%s' "$cleaned" > "$temporary"
mv -- "$temporary" "$file"
For example:
Before:
abcWORMLAB_SIM_INFECTION_MARKER_7F3A9Cxyz
After:
abcxyz
The original text before and after the marker is preserved.
The program can clean one selected folder or scan all 30 folders.
After cleaning, it can compare the current file length with the recorded original length.
Problems and Improvements
One of the first problems was terminal flickering.
The early version constantly cleared and rebuilt the entire dashboard, even when nothing had changed:
clear_screen
draw_ui
The updated version uses event-driven redraws. It refreshes only after an action, a propagation cycle, a cleanup operation, a selection change, or a terminal resize.
Another challenge was removing the marker without damaging the original data.
The solution was to use one exact marker and remove only that sequence. The program also writes changes to a temporary file first instead of editing the original file directly.
Navigation was another small issue. The File Viewer originally felt like a separate screen with no obvious exit.
The newer version remembers which dashboard tab was open before the file was examined and returns to that tab when Esc, B, or V is pressed.
What the Experiment Taught Me
The clearest lesson was how quickly a small problem can grow.
One affected folder does not look serious. However, when every affected folder can participate in another round of propagation, the number rises quickly.
The experiment also helped reinforce several defensive ideas:
- Early detection limits the number of affected locations.
- Quarantine can slow or stop propagation.
- File changes can serve as indicators.
- Cleanup should preserve legitimate data.
- Logs help reconstruct what happened.
- Infection lineage helps identify the original source.
- Verification is necessary after cleanup.
The project also gave me more practice with Bash:
- Functions
- Arrays
- Random number generation
- Folder and file discovery
- String manipulation
- Temporary files
- Pattern searching
- Terminal colors
- Keyboard controls
- Event logging
- State tracking
- Interactive interface design