T3SF: Automating Security Tabletop Exercises with MSEL-Driven Orchestration
Hook
Most tabletop exercises fail because facilitators spend more time watching the clock and copying scenario injects into chat windows than observing how participants respond to the crisis unfolding before them.
Context
Security tabletop exercises—simulated incidents where teams practice incident response without actual systems at risk—have been the backbone of preparedness training for decades. The traditional model involves a facilitator reading from a script of "injects" (timed scenario events like "CEO's laptop shows ransomware warning" or "Database server stops responding"), watching participants discuss responses, and manually introducing complications as time progresses. This manual orchestration creates several problems: timing drift as facilitators get absorbed in discussion, inconsistent delivery across multiple exercise runs, facilitator fatigue during multi-hour scenarios, and difficulty coordinating exercises across distributed teams using modern chat platforms.
T3SF (Technical Tabletop Exercises Simulation Framework) addresses this orchestration gap by treating tabletop exercises as programmable systems. Instead of facilitators manually typing scenario events into Slack or Discord channels, T3SF reads a Master Scenario Events List (MSEL)—a JSON timeline of all planned injects—and dispatches them automatically through messaging platform APIs. The framework emerged from Base4Security's work conducting repeated training exercises where the same scenarios needed to run consistently across different organizations, different platforms, and different facilitator skill levels. By separating scenario content (the MSEL) from delivery mechanism (platform adapters), T3SF makes tabletop exercises reproducible, version-controlled, and platform-agnostic.
Technical Insight
T3SF's architecture centers on three components: the MSEL specification, the orchestration engine, and platform adapters. The MSEL is a JSON structure defining exercise metadata, participants, and time-sequenced injects. Each inject contains message content, target channels/users, timing offset from exercise start, and optional rule conditions for branching logic. Here's a simplified MSEL structure:
{
"metadata": {
"name": "Ransomware Incident Response",
"duration": 120,
"organizations": ["security-team", "it-ops"]
},
"injects": [
{
"id": "inject_001",
"time_offset": 0,
"target": "security-team",
"content": "Multiple workstations reporting ransomware warnings. File shares becoming inaccessible.",
"type": "incident_notification"
},
{
"id": "inject_002",
"time_offset": 300,
"target": "it-ops",
"content": "Domain controller CPU at 98%. Unusual authentication attempts logged.",
"type": "technical_indicator"
}
]
}
The orchestration engine uses Python's asyncio to manage concurrent timing loops and message dispatch. When an exercise starts, T3SF spawns coroutines for each inject, calculating absolute timestamps from the start time and relative offsets. This async pattern is crucial because a single exercise might have dozens of injects targeting different channels simultaneously, and blocking I/O to one messaging platform shouldn't delay delivery to others. The core loop looks conceptually like this:
import asyncio
from datetime import datetime, timedelta
async def dispatch_inject(inject, platform_adapter, start_time):
target_time = start_time + timedelta(seconds=inject['time_offset'])
delay = (target_time - datetime.now()).total_seconds()
if delay > 0:
await asyncio.sleep(delay)
await platform_adapter.send_message(
channel=inject['target'],
content=inject['content'],
metadata={'inject_id': inject['id']}
)
async def run_exercise(msel, platform_adapter):
start_time = datetime.now()
tasks = [
dispatch_inject(inject, platform_adapter, start_time)
for inject in msel['injects']
]
await asyncio.gather(*tasks)
Platform adapters abstract the differences between Discord's bot API, Slack's Web API, Telegram's Bot API, and WhatsApp Business API. Each adapter implements a common interface with methods like send_message(), create_channel(), and list_participants(). The adapter pattern means switching from Discord to Slack requires only changing the adapter initialization—the MSEL and orchestration logic remain identical. This is powerful for organizations that run the same training scenario across different teams using different platforms.
The multi-organization and multi-area configuration deserves attention because it reflects real enterprise structures. A complex exercise might simulate a multinational company with regional IT teams and a central security operations center. T3SF lets you define organizational boundaries in the MSEL and map them to actual platform channels. Injects can target specific organizations, specific areas within organizations, or broadcast globally. The framework tracks which participants belong to which organizational units, enabling realistic information flow constraints—the European IT team doesn't automatically see injects meant for the SOC unless the MSEL explicitly routes them.
T3SF also includes optional rule sets for conditional inject delivery. Rules can check participant actions (did anyone run a specific command?), time conditions (only send this inject if more than 15 minutes have passed), or manual facilitator triggers. This adds limited branching to otherwise linear scenarios, though it's not a full decision-tree system. The Docker packaging includes environment variable configuration for platform tokens, webhook URLs, and exercise parameters, making it suitable for CI/CD pipelines that spin up exercises on demand or scheduled training calendars.
Gotcha
T3SF's MSEL format is purely time-based, which means it struggles with exercises that need genuine branching narratives where participant decisions significantly alter the scenario trajectory. If your team isolates the infected systems quickly, the framework will still deliver the "ransomware spreads to domain controller" inject at the scheduled time unless you've pre-programmed conditional rules. This works for training muscle memory and process execution but limits realism for advanced teams expecting consequences. You'd need to manually pause the exercise or heavily customize the rule engine for sophisticated branching.
Platform adapter coverage is uneven. While Discord and Slack have mature APIs that T3SF leverages well, WhatsApp Business API access requires Meta business verification and webhook infrastructure that many organizations won't have. The Telegram adapter is straightforward but lacks group management features for ephemeral exercise channels. You'll also need to manage bot creation, token security, and permission scopes separately for each platform—T3SF doesn't abstract away the initial platform setup complexity. The 48 GitHub stars suggest a small community, so expect to read the source code when troubleshooting rather than finding Stack Overflow answers. There's no built-in post-exercise analytics; you'll need to instrument your own logging to capture response times, decision quality, or participant engagement metrics.
Verdict
Use if: You run recurring security training scenarios with predictable event timelines, need consistent exercise delivery across multiple teams or platforms, want version-controlled scenario content separate from facilitator execution, or operate in organizations where distributed teams use Discord/Slack/Telegram for daily communication. It's particularly valuable when you have a library of proven scenarios you need to deploy repeatedly without facilitator variance. Skip if: You need sophisticated branching scenarios that adapt to participant decisions in real-time, require comprehensive exercise analytics and performance dashboards, lack technical resources to set up and manage messaging platform bots, or need a turnkey commercial solution with vendor support. Also skip if your exercises involve live-fire components (actual vulnerable systems) rather than pure discussion—T3SF is purely a chat orchestration tool, not a cyber range.