Combining Threat Modeling with Tabletop Exercises for Maximum Results
Usually, threat modeling and tabletop exercises are performed in silos. However, I believe one of the most powerful use cases of them is to combine them together. In this blog post, I will describe how I have been using the best of both worlds to get maximum results. Before we start, it is important to understand the difference between a threat model and a tabletop exercise and how they are usually used in an organization. Then we will move onto the recommended flow.
Threat Modeling vs Tabletop Exercises
Threat modeling is a process to identify threats and mitigate them during the design phase of the application. It helps us to document risks and controls, but it mostly does not help organizations to verify that anyone would notice when a threat happens or that the right people would respond in the right order.
Tabletop exercises are scenario-based walkthroughs: "Assume X happened. What do you do, in what order, and who is involved?" They test detection, response, and communication. Organizations usually run tabletops with generic scenarios (e.g. "a breach occurred"). So usually the discussion stays abstract and may not reflect our actual architecture. It is still a good practice to nail down some unique scenarios, but as I said, it doesn't always relate to the expected incidents coming from our feature.
What Does It Mean to Combine Them?
We run a threat model for a feature, then use the resulting threats as the exact scenarios for tabletop exercises. This helps us to validate both the design and response in one pass. It also improves visibility and collaboration between different verticals.
Let's talk about an example to understand this better:
Example Feature: Export User List to CSV
Step 1: Perform a Threat Model
1.1 Draw a simple data flow
Include:
- Actors: Who or what interacts with the feature (end user, support agent, cron job, external partner).
- Processes: Your app or service that handles the request (e.g. "Export API handler").
- Data stores: Where data is read from or written to (DB, cache, object storage, logs).
- Data flows: Arrows showing direction of data (e.g. "User → Export API → DB read → CSV generated → User").
1.2 Mark trust boundaries
A trust boundary is a line between zones that you treat differently (e.g. "untrusted" vs "internal," "user space" vs "admin only"). Draw a dotted line or box around each zone and label it. Examples:
- Untrusted client (browser, mobile app, partner API) → your edge/API.
- Authenticated user → export service (only their own data).
- Internal admin → support tool that can impersonate.
1.3 List some concrete threats
You can use STRIDE (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege) as a checklist, or simply ask: "What could an attacker or misconfiguration do within this flow?"
| # | Threat | STRIDE (if used) |
|---|---|---|
| 1 | Unauthorised user triggers export for another user's data (IDOR / missing authz). | E (Elevation) |
| 2 | Export response includes more fields than the UI shows (e.g. internal IDs, PII). | I (Info Disclosure) |
| 3 | Attacker abuses endpoint to enumerate users or export at scale (no rate limit / abuse control). | D (DoS / Abuse) |
| 4 | Export file or request/response logged in plain text (sensitive data in logs). | I (Info Disclosure) |
| 5 | Export is cached or stored in a location accessible to other users (path traversal or misconfig). | I, T (Tampering) |
1.4 Note mitigations
For each threat, write what's already in place (or "none"). This keeps the threat model honest and highlights gaps.
- Threat 1 (Export IDOR): We check
request.user_id === resource.owner_idin the handler. - Threat 2 (Data leak in export): Export uses a dedicated data transfer object that only includes allowed columns.
- Threat 3 (Abuse): Rate limit: 10 exports per user per hour.
- Threat 4 (Logging PII): No; we currently log request IDs and status. Gap: response size or row count could leak info.
1.5 Pick tabletop scenarios
Not every threat is a good tabletop scenario. Prefer threats where detection and response are non-obvious or untested. Example:
- We discover the export was called with another user's ID and data was returned. How do we find out? Who do we notify? How do we scope impact and notify users?
Step 2: Turn Threats into Tabletop Scenarios
Take this threat and turn it into an incident-style scenario. The scenario should be concrete to feel like a real incident.
Scenario template – Templates generated using manual and AI-suggested checks
- Setup (1–2 sentences): "It's Tuesday 2 pm. [Feature X] has been in production for two weeks. Assume [threat Y] has already happened."
- Trigger: "How do you find out? (Alert? User report? Audit? Partner complaint?)" Let the room suggest; then pick one and say "Okay, we'll say it was [e.g. a user report]. What happens next?"
- Questions for the room (use as needed, don't read like a script):
- Who is notified first, and how (Slack, PagerDuty, email)?
- What do you do in the first 15 minutes? (Preserve evidence? Check logs? Disable the feature?)
- How do you confirm scope? (Who was affected? What data was exposed? Since when?)
- Who owns the decision to disable the feature or roll back? What's the threshold?
- When and how do you communicate: internal only, affected users, or regulator? Who drafts and approves the message?
- Do we have audit logs or metrics that would have detected this earlier? If not, what would we add?
- What's the follow-up? (Post-incident review, playbook update, design change.)
Keep the scenario short; the value is in the discussion, not the narrative.
Now let's phrase the tabletop scenario:
It's Tuesday 2 pm. Support gets a ticket: a user received a CSV that contained other users' email addresses. Engineering confirms the export API was called with a user ID that didn't match the authenticated user, and the call succeeded. Walk through: how would we have detected this if the user hadn't reported it? Who do you notify in the first 15 minutes? How do you determine how many users or exports were affected? What do you tell the affected users, and who approves that message? Do we disable the export feature while we fix it?
Step 3: Run a Short Tabletop
3.1 Audience
At least one developer who built or maintains the feature (knows where logs and code live), and one person who would realistically respond to an incident (on-call, tech lead, or security). We might bring in more people if it's relevant for the scenario.
3.2 Roles
- Facilitator: Reads the scenario, asks the questions, and keeps time. Doesn't solve the incident; keeps the room on "what would you do?" and "who would do it?"
- Scribe (can be facilitator): Captures gaps and action items in real time (e.g. "No alert for IDOR on export," "Comms owner unclear").
- Participants: Answer as themselves or as the role they'd play in a real incident. No laptops; discussion only so everyone stays in the room.
Suggested agenda (total ~25–30 min)
| Phase | Time | What happens |
|---|---|---|
| Intro | 2 min | Facilitator states the goal: "We're walking through a scenario from our threat model. No blame; we're testing our response and finding gaps." |
| Scenario 1 | 10–12 min | Read setup and trigger; ask detection, first 15 min, scope, comms, disable/rollback. Scribe notes "we don't know" and "we'd need X." |
| Scenario 2 (if you have two) | 10–12 min | Same format. |
| Wrap-up | 5 min | Review captured gaps. Turn each into a concrete action item with owner (or "TBD"). |
Expected Output
A short list of action items with at least:
- Detection: What's missing (alerts, audit logs, metrics) so we'd notice this threat?
- Roles and playbooks: Who is responsible for what (identification, code fix, customer communication, other action items)? What should be in the runbook for this feature?
- Design or config: Any change to the feature or environment (e.g. extra validation, rate limit, or "disable export until we add logging")?
Example action items from an "Export IDOR" tabletop:
- Add server-side audit log for export API:
user_id,requested_resource_id, result (success/denied). Owner: Backend. ETA: Next sprint. - Define incident owner for data exposure on export feature. Owner: Tech lead + Security. ETA: 1 week.
- Add playbook section: "Export API: suspected IDOR" (how to query new audit log, how to scope affected users).
End-to-End Flow
[Feature spec] → [30–45 min threat model] → [List of threats + mitigations]
↓
[Choose 1–2 threats where response matters]
↓
[20–30 min tabletop with scenario derived from those threats]
↓
[Action items: detection, playbook, or design] → [Assign owners and follow up]
Run the threat model and tabletop in the same week (or back-to-back in one 1–2 hour block) so the scenarios stay tied to the design.
What You Get
- Threat model: Documented risks and controls for the feature; reusable for security reviews, onboarding, and future tabletops.
- Tabletop: Confidence (or explicit gaps) that the team would detect and respond correctly; concrete follow-ups with owners.
- Combined: Scenarios grounded in your actual design, so the exercise is relevant and the resulting actions map straight back to the feature (e.g. "add audit logging to the export endpoint," "playbook for export IDOR").
Doing this for every small feature would be heavy; use it for higher-risk or higher-visibility changes (new data flows, PII, privilege changes, or external integrations). Even one or two features per quarter will improve both threat coverage and response readiness. Over time, the same format (threat model → pick threats → tabletop → action items) becomes a repeatable ritual that teams can run with minimal prep.
Please subscribe if you would like to receive more content like this :)