Threat Modeling in Agile Teams: Epics, Stories and the Sprint

Most advice on threat modeling assumes a design phase: get the architects in a room, draw the system, find the threats, write it up, then build. Agile teams don't have that phase. The design changes every sprint, and a threat model written in week one describes a system that stops existing by week six. So the model either becomes a document nobody opens, or the team skips threat modeling and finds the design flaws in a penetration test after release.

Neither is necessary. Agile teams don't need less threat modeling. They need it in smaller pieces, sized to each level of the backlog, all writing to one model that stays current. This post covers which epics need a session and which don't, a 15-minute check for stories, what to add to the definitions of ready and done, and a worked epic carried through two sprints.

The short version: keep one living threat model per system (a data flow diagram, attack trees for the goals that matter and a scored risk register), and change it at the size of each backlog item. An epic that adds a trust boundary, a data store, an integration, a role or a new way in gets a 60-minute session before its stories are written. Every story that touches the diagram gets a 15-minute check in refinement: if it matches an existing leaf, copy that leaf's mitigation into its acceptance criteria; if it adds something new, take it back to the epic model. At each sprint review, spend 10 minutes on what changed in the register. Findings go into the feature's own acceptance criteria or into enabler stories, never into a separate security backlog.

Why the big upfront model fails in agile

A threat model done once, up front, has three problems in an agile team. It goes out of date quickly, because each sprint changes the system a little and nobody updates a document they didn't write. It arrives too late or too early: before the epic is refined, there's nothing concrete to model, and after the stories are written, the design has already been decided. And it competes with delivery, because a two-day workshop has to be scheduled around sprints instead of happening inside them.

Agile itself isn't the problem. The Threat Modeling Manifesto values "continuous refinement over a single delivery" and "a journey of understanding over a security or privacy snapshot", which describes how agile teams already work. What changes is the unit of work. Instead of one model per project, you get one model per system and many small changes to it, each sized to the backlog item that caused it. Our post on shifting security left makes the case for doing this at design time. This one is about fitting it into the backlog.

Three sizes of threat modeling, one model

Diagram of threat modeling sized to each backlog level. Product, one per system: a system model taking half a day once, then kept current, which writes a DFD with trust boundaries, attack trees for the top goals and a scored risk register. Epic, only if it passes the epic test: an epic session of 60 minutes before stories are written, highlighted in red, which writes diagram changes, new leaves, security acceptance criteria and enabler stories. Story, in backlog refinement: a story check of 15 minutes for stories that touch the diagram; if it matches a leaf, add its criteria; if something is new, go back to the epic; if nothing is new, done. Sprint, at the sprint review: a register review of 10 minutes covering rows added, mitigated, accepted or re-scored since the last sprint. All four feed one living threat model per system: every level starts from the current diagram, tree and register, and writes its changes back in the same sprint.
Each backlog level gets a threat modeling activity of its own size. The epic session is where most design flaws are found. All four read from and write to the same model.
LevelWhenWhoTimeOutput
System modelOnce per system, then kept currentThe team, with a facilitator for the first oneAbout half a dayDFD with trust boundaries, attack trees for the two or three goals that matter most, scored register
Epic sessionAfter the epic is understood, before its stories are writtenThe developers and tester who'll build it, and the product owner60 minutesDiagram changes, new and re-scored leaves, security acceptance criteria, enabler stories, accepted risks with an owner
Story checkBacklog refinementWhoever refines the story15 minutesAcceptance criteria copied from matching leaves, or a flag that the epic model needs another look
Register reviewSprint reviewThe team and the product owner10 minutesWhat was added, mitigated, accepted or re-scored this sprint

If you don't have a system model yet, build it before anything else. Our guide to running your first threat modeling workshop covers that session. You only build it once. Everything after that is an edit.

The epic test: which epics need a session

Modeling every epic burns time on work that doesn't change the system's security, and teams soon stop doing it. Modeling none means the design flaws ship. The middle ground is a short test, asked when the epic is first refined. If the answer to any question is yes, the epic gets a session.

  1. Does it add or move a trust boundary? A new service, a new network zone, a public endpoint for something that was internal.
  2. Does it add a data store, or a new kind of data to an existing one? Especially personal, payment or health data, or secrets.
  3. Does it add an external integration, in either direction? A webhook, a third-party API, an import, an export to a partner.
  4. Does it add a role, or change who can do what? New user types, sharing, delegation, admin tools, support access.
  5. Does it add a new way in? An endpoint, a file upload, a link sent by email, a public form.

These five questions are deliberately about the system's structure, not about how risky the feature sounds. A "simple" CSV import adds a new way in and a new data path, while a big redesign of the billing page may change nothing the attacker can reach. Our list of re-threat-modeling triggers covers the same idea for changes outside the backlog, such as incidents, new dependencies and changes to infrastructure.

The 60-minute epic session

Hold it once the epic is understood well enough to draw, and before anyone writes its stories. That's the point where a finding can still change the design cheaply: "invites are bound to an email address" is a design decision, not a bug fix, if it's made before the invite flow exists.

  1. Update the diagram (10 minutes). Open the system's DFD and add what the epic adds: new processes, stores, flows and boundaries. Never start from a blank whiteboard. Starting from the existing diagram is what keeps one model per system.
  2. Extend the attack tree (20 minutes). Pick the goal the epic makes easier, such as "read another customer's invoices", and add the new paths as leaves. Check the existing leaves too: a new endpoint is often a new place for an old threat.
  3. Score the leaves (10 minutes). Likelihood and impact on the same risk matrix the rest of the register uses, so the new rows can be ranked against the old ones.
  4. Turn leaves into backlog items (15 minutes). Each leaf you'll fix becomes acceptance criteria on the story that builds that part, or an enabler story if several stories depend on it. Each leaf you won't fix becomes an accepted risk with a named owner and a review date.
  5. Write down the assumptions (5 minutes). "Only workspace owners can invite." Assumptions are what the next story check tests against.

The output is a change to the model, not a report. Nobody writes a summary. The diagram, the tree and the register are the record.

The 15-minute story check

Stories are too small for a session, but some stories quietly change what the epic session assumed. The story check is four questions asked during refinement, about stories that touch an element on the diagram:

  1. Which elements on the diagram does this story touch? If none, stop: there's nothing to check.
  2. Does it match a leaf already in the tree? If so, copy that leaf's mitigation into the story's acceptance criteria, with a test.
  3. Does it add anything the model doesn't have? A new flow, a new kind of data, a bigger blast radius for an existing leaf. If so, add or re-score the leaf now.
  4. Is it bigger than 15 minutes? If the story changes the structure (it would pass the epic test on its own), take it back to the epic model rather than squeezing it into refinement.

This complements abuse cases rather than replacing them. An abuse case describes how one story could be misused, in the story's own words. The story check connects the story to the system model, so a threat found once is reused every time the same element changes, and a story that makes an old leaf worse gets it re-scored.

A worked example: an epic through two sprints

Invoicely is the small invoicing SaaS from our threat model example: a web API, a PDF worker, file storage, a Stripe webhook and a support console. Its system model already exists, with ten scored threats. The top one is T2, an IDOR where a customer reads another customer's invoices by changing the ID (4 × 5 = 20).

The next epic is Team workspaces: a business owner can invite their accountant and staff into a shared workspace, with roles.

The epic test

Two yeses. It adds roles and changes who can do what (question 4), and it adds a new way in, an invite link sent by email (question 5). So the epic gets a session.

The epic session

The team adds a Workspace membership store and an invite email flow to the DFD. Under the goal "someone outside the company reads or changes its invoices or payout details", they add five leaves. One of them isn't new at all: workspace IDs in the API are a new place for T2.

#LeafL × IDecision
T2A member swaps the workspace ID in an API call and reads another workspace's invoices4 × 5 = 20Mitigate with an enabler story: one membership check that every workspace endpoint goes through
W1An invite link is forwarded or reused, and someone else joins the workspace3 × 4 = 12Mitigate: single-use links, expiry after 7 days, bound to the invited email address
W3A removed member keeps access through an open session or an API token3 × 4 = 12Mitigate: removal revokes sessions and tokens at once
W2The Accountant role can change payout bank details2 × 5 = 10Mitigate: Accountant is read and export only, following least privilege
W4Invites are used to send unwanted email from Invoicely's domain2 × 2 = 4Accept for now, owner: head of product, review in six months

Assumption recorded: only workspace owners can invite or change roles.

From leaves to stories

No "security story" is written. The leaves become acceptance criteria on the stories that build each part, plus one enabler story that has to land first because three stories depend on it.

StorySecurity acceptance criteriaCloses
Enabler: every workspace endpoint goes through one membership checkA test calls every workspace endpoint with another workspace's ID and expects 404T2
As an owner, I can invite my accountant by emailThe link works once, expires after 7 days and only accepts a sign-in with the invited addressW1
As an owner, I can choose a member's roleA test calls each payout and invite endpoint as an Accountant and expects 403W2
As an owner, I can remove a memberThe removed member's next request with an existing session or token gets 401W3

Sprint 1 takes the enabler, the invite story and the roles story. Sprint 2 takes the removal story and whatever refinement adds.

A story check in sprint 2

During sprint 2 refinement, a new story arrives: "As an accountant, I can export all the workspace's invoices as a CSV file." It doesn't pass the epic test on its own, so it gets the 15-minute check.

  1. Elements touched: the API, the invoice store and a new export endpoint. No new boundary.
  2. Matching leaf: T2. The criterion "the export goes through the membership check, tested with another workspace's ID" is copied in.
  3. Anything new: two things. Invoice notes are customer-written text, so a note starting with = becomes a formula when the CSV is opened in a spreadsheet (CSV injection). That's a new leaf, W5, scored 2 × 3 = 6, with the criterion "cells starting with =, +, - or @ are escaped". And a one-click export of everything makes W1 worse: a stranger who gets into the workspace can now take all its invoices at once, so W1's impact goes from 4 to 5, and its score from 12 to 15.
  4. Bigger than 15 minutes? No. Done.

The re-score is the part a separate threat-model document would miss. Part of W1's fix had already slipped into sprint 2, and at 15 it now sits level with T5, the support console's shared database role, near the top of the register. That makes it much harder to let it slip again.

The sprint reviews

SprintRegister changes
Sprint 1Added W1 to W4. T2 and W2 mitigated, with their tests in the pipeline. W4 accepted, with an owner and review date. W1 still open: the invite story is done but binding to the email address moved to sprint 2.
Sprint 2Added W5. W1 re-scored from 12 to 15, then mitigated. W3 and W5 mitigated. The DFD now matches what shipped, including the export endpoint.

That's the whole security conversation at each review: two minutes of reading rows, plus a decision on anything that slipped. The product owner can see that W1 was carried over and decide whether the epic is done without it.

Definitions of ready and done

The practice only lasts if it's attached to checkpoints the team already respects. Add these:

  • Definition of ready, for epics: the epic test has been answered. If any answer is yes, the epic session has happened and its stories carry their security acceptance criteria.
  • Definition of ready, for stories: stories that touch the diagram have had the story check.
  • Definition of done: every security acceptance criterion has a passing test, including at least one negative test, such as calling the endpoint as another customer.
  • Definition of done: the data flow diagram matches what was built.
  • Definition of done: the register shows the new status of every leaf the story touched. Accepted risks have an owner and a review date.

The diagram item is the one teams drop first and the one that matters most. If the diagram drifts from the code, the next epic session starts from a system that doesn't exist.

Keeping the model current across sprints

  • Update the model in the sprint that changes the system, not in a later "documentation sprint". An update is a few minutes when the change is fresh and an archaeology project six months later.
  • Treat open register rows as backlog items. A mitigation that isn't in the backlog doesn't get built. Our post on why risk registers are backlogs covers the argument, and prioritising a backlog of threats covers the ranking.
  • Take a snapshot at each release. When an auditor or a customer asks what you knew about a risk when version 3.2 shipped, the answer is the model as it was then, not as it is now.
  • Re-check the assumptions now and then. Once a quarter, read the recorded assumptions and ask whether they're still true. "Only owners can invite" stops being true the day someone ships delegated admin.
  • Measure it lightly. Count epics that passed the test and had a session, and findings that changed a design before code was written. If the second number is zero for a quarter, the sessions are happening too late. Our threat modeling maturity page has more on what to track.

Where agile threat modeling goes wrong

  • The "As an attacker" story. "As an attacker, I want to read other customers' invoices, so that…" sounds clever and is never prioritised, because no product owner wants it delivered. Put the mitigation on the feature story as acceptance criteria instead.
  • A separate security backlog. Security tickets in their own queue get triaged separately and finished never. Enabler stories live in the same backlog as features, ranked by the same product owner.
  • Modeling every story. If every story gets a session, the sessions become ceremony and stop finding things. The epic test and the 15-minute check exist so most stories cost nothing.
  • The sprint-zero model. A model built in the first sprint and never touched again is the waterfall model with extra steps. If the diagram isn't in the definition of done, this is what you'll get.
  • Waiting for the security team. If an epic session needs a security engineer to happen, it happens when one is free, which is after the design is fixed. A trained developer or tester can facilitate. Our post on security culture without a dedicated team covers building that habit.

What this doesn't do: it doesn't replace testing. The epic session finds design flaws, not implementation bugs, so you still need code review, scanning and penetration testing for those. Our comparison of threat modeling and penetration testing covers where each fits.

Checklist: threat modeling in an agile team

  1. Build one system model per system: a DFD with trust boundaries, attack trees for the top goals and a scored register.
  2. Ask the five-question epic test when each epic is first refined.
  3. For each epic that passes, hold a 60-minute session before its stories are written, starting from the existing diagram.
  4. Turn leaves into acceptance criteria on feature stories, or into enabler stories. Never into a separate security backlog.
  5. Record accepted risks with an owner and a review date, and record the assumptions.
  6. Run the 15-minute story check in refinement for stories that touch the diagram, and re-score leaves a story makes worse.
  7. Add the security criteria, the diagram and the register to the definitions of ready and done.
  8. Spend 10 minutes on the register at each sprint review.
  9. Take a snapshot of the model at each release.
  10. Re-check the assumptions once a quarter.

Frequently asked questions

Can you do threat modeling in agile?

Yes, as long as you stop treating it as one big document written before development starts. Keep one living threat model per system, and change it in small steps at each level of the backlog: a 60-minute session for epics that change the shape of the system, a 15-minute check on stories that touch the diagram, and a short look at the risk register at each sprint review. Most sprints then add a few leaves and close a few rows rather than starting over.

How much time does threat modeling take in a sprint?

Less than most teams expect once the system model exists. A typical two-week sprint needs about 15 minutes per story that touches the data flow diagram, done during refinement, and 10 minutes at the sprint review. An epic that adds a trust boundary, a data store, an integration, a role or a new way in gets one 60-minute session before its stories are written. Building the first system model takes about half a day, once.

Should threat modeling be a separate user story?

Usually not. A story called "threat model the invite feature" produces a document, not a safer feature, and it competes with features for priority. Put the work where the change is: the epic session's time comes out of refinement, its findings become acceptance criteria on the feature stories, and a fix that several stories depend on, such as one shared authorisation check, becomes an enabler story with the attack tree leaf it closes named in it.

Who should run threat modeling in a Scrum team?

The team that builds the feature: developers, a tester and the product owner, who decides what risk is acceptable. A security champion or the security team can facilitate, especially for the first few epic sessions, but the session shouldn't wait for them. If threat modeling only happens when a security engineer is free, it happens after the design is fixed.

How does threat modeling fit the definition of done?

Add three checks to the definition of done: every security acceptance criterion has a passing test, including at least one negative test such as calling the endpoint as another customer; the data flow diagram matches what was actually built; and the risk register shows the new status of every leaf the story touched, with accepted risks named, owned and dated. Add one to the definition of ready: an epic that passes the epic test has had its threat model session before its stories are pulled into a sprint.

Keep one threat model per system, and change it every sprint

Draw the system as a data flow diagram, add attack tree leaves as each epic changes it, and score every leaf in a risk register you can export as a dated PDF, free. Pro adds collaboration, so the whole team edits the same model, and Backup & Restore, so the forest owner can take a labelled snapshot at each release. On Enterprise, ticketing sync opens a Jira, Linear, Azure DevOps or ServiceNow ticket when a risk is scored High or Critical, and marks the risk Mitigated when the ticket is closed. Or open the Invoicely example from this post first and see how it fits together.

Or start a blank model →

Not ready to sign up? Get new threat-modeling guides by email instead.