Security Requirements & Abuse Case Modeling

Threat models describe systems; backlogs contain features. The gap between the two is where most threat modelling effort quietly evaporates: a document lists forty threats, the team ships a sprint of stories, and nothing in the story text ever mentions any of them. The fix is not more analysis — it is expressing the analysis in the unit of work the team already uses.

This guide covers that translation: abuse cases written beside user stories, acceptance criteria a reviewer can actually check, control baselines for recurring feature shapes so common cases need no fresh thinking, and a definition of done that treats security criteria exactly like functional ones. It is part of Threat Modeling Fundamentals & Methodology, and it is where the output of STRIDE framework implementation becomes work an engineer can pick up.


Threat Anatomy

The failure mode here is organisational rather than technical, and it is remarkably consistent. A threat model is produced at design time, usually by whoever cares most about security. It is thorough, and it is written in the vocabulary of threats and controls. Implementation then proceeds from tickets written in the vocabulary of features and users. Nobody maps one to the other, so the model describes a system nobody is building and the tickets describe work nobody is threat modelling.

Three symptoms tell you this is happening. Threat models are updated on a schedule rather than when the architecture changes. Security findings arrive from penetration tests describing things the model already predicted. And engineers describe security as “the review at the end” rather than as part of the work — which is an accurate description of the process they have been given.

The Gap Abuse Cases Are Meant to Close The upper track runs from architecture through threat identification to a document of controls, in the vocabulary of threats. The lower track runs from a user story through implementation to a shipped feature, in the vocabulary of users. Without a bridge the tracks never meet. The abuse case is the artefact that exists in both vocabularies and therefore travels between them. Track 1 — the threat model, in the vocabulary of threats architecture decomposed threats identified and scored a document of controls The abuse case — written in both vocabularies at once "as an actor with X, I do Y to gain Z" · sits in the ticket · produces acceptance criteria · gets estimated Track 2 — the backlog, in the vocabulary of users a user story is refined implementation and review a shipped feature

Prerequisites & Scope

  • A backlog with a definition of done the team actually applies, rather than one on a wiki page.
  • A threat model or at least a documented set of trust boundaries, so abuse cases have something to draw on — see defining trust boundaries.
  • A test suite that runs on every change, because untested criteria decay within a quarter.
  • Someone who can write baselines, usually whoever reviews security work today.

Out of scope: compliance evidence generation, covered by threat model documentation patterns, and the scoring that decides which threats matter most, covered by threat prioritization and risk scoring.


Mitigation Architecture

Artefact Written by Lives in Fails when
Abuse case The engineer refining the story The ticket It is written after implementation, as documentation
Acceptance criterion The same engineer, reviewed The ticket’s done list It states a principle rather than a checkable outcome
Control baseline The security reviewer, once per feature type The repository, version controlled It is a wiki page nobody opens during refinement
Definition of done The team The board Security criteria are marked optional
Regression test Whoever implements the story The test suite The criterion was never expressible as a test

The single structural decision that makes this work is putting the baselines in the repository next to the code rather than in a knowledge base. A baseline that is one directory away from the file being edited gets read; one that requires opening a browser does not.


Step-by-Step Implementation

Step 1 — Write the abuse case beside the story (ASVS V1.1.3)

## Story
As a workspace member, I can invite a colleague by email so they can join the workspace.

## Abuse cases
1. As an outsider who guesses an invite URL, I join a workspace I was never invited to.
   → Gain: read access to everything in that workspace.
2. As a departing member, I invite a personal address before losing access, to keep a way back in.
   → Gain: persistent access after offboarding.
3. As a spammer with one free account, I send thousands of invitations through your mail domain.
   → Gain: delivery from a reputable sender; you gain a damaged sending reputation.
4. As a member, I enumerate which addresses already have accounts from the invite response.
   → Gain: a list of your customers' employees.

Four cases, none of which requires deep security expertise to write — they require thinking about the feature adversarially for ten minutes during refinement. That is the skill worth building, and it is teachable in a way that formal threat modelling notation is not.

Step 2 — Convert each into a testable criterion (ASVS V1.1.4)

## Acceptance criteria (security)
- [ ] Invite tokens are 128-bit random, single-use, and expire after 7 days.
- [ ] Accepting an invite requires that the authenticated address matches the invited address.
- [ ] Invites are revoked automatically when the inviting member loses workspace access.
- [ ] Invitations are rate limited to 20 per member per day and 200 per workspace per day.
- [ ] The invite response is identical whether or not the address already has an account.

Every line names something a reviewer can look for and a test can assert. Compare with “invitations should be secure”, which passes review because nobody can say it does not.

Step 3 — Give recurring feature shapes a baseline (ASVS V1.1.2)

# security/baselines/user-content.yml — applied to any feature accepting content from users
applies_to: "features that accept content from a user and display it to another user"
required_controls:
  - id: BL-UC-1
    control: "Content is sanitised at the render boundary for the exact context it lands in"
    verify: "Payload suite renders known attack strings and asserts nothing executes"
  - id: BL-UC-2
    control: "Uploads are typed from content, stored under a generated name, served from the content origin"
    verify: "Upload defence suite passes for this feature's endpoint"
  - id: BL-UC-3
    control: "Author identity is recorded and visible to moderation"
    verify: "Moderation view shows author and timestamp for every item"
  - id: BL-UC-4
    control: "Rate limit per author, and a report path for other users"
    verify: "Limit test passes; report endpoint exists and is monitored"

A baseline turns the common case into a checklist and reserves fresh analysis for the genuinely novel. It also makes review faster: “this is a user-content feature, baseline applied, plus two feature-specific cases” is a far better conversation than starting from a blank page every time.

Step 4 — Make the criteria block completion

## Definition of done
- [ ] Functional acceptance criteria pass
- [ ] Security acceptance criteria pass, each with a linked test
- [ ] Applicable control baseline referenced by identifier in the pull request
- [ ] Any accepted risk recorded with an owner and a review date

The fourth line matters as much as the second. Teams will sometimes ship with a known gap, and that is a legitimate business decision — but it should leave a dated record with a name on it, not an unspoken assumption that someone will get to it.

Where Each Artefact Attaches to the Work During refinement the engineer writes abuse cases and converts them into acceptance criteria, referencing the baseline for the feature type. During implementation each criterion gains a test. At done, the criteria are checked like functional ones and any accepted risk is recorded with an owner and a review date. Refinement write 2 to 4 abuse cases reference the baseline by id turn each into a criterion Ten minutes, by the engineer who will build it. Implementation each criterion gains a test the test fails first, then passes review checks both, together A criterion with no test is a principle wearing a checkbox. Done security criteria block done exactly as functional ones do accepted risks get an owner and a review date, so the decision is visible later.

Edge Cases & Bypass Patterns

The abuse case written after the fact. Retrospective abuse cases describe what was built rather than what should have been considered, and they always conclude that the implementation is fine. Require them at refinement, before estimation.

Four Ways the Practice Turns Into Paperwork Abuse cases written after implementation always conclude the implementation is fine. Criteria that restate coding standards add nothing a linter does not already check. Baselines that grow past a handful of controls get skipped wholesale. And applying the process to trivial stories teaches the team that it is ceremony. Written afterwards describes what was built, and concludes that what was built is fine Restates the coding standard "uses parameterised queries" is a lint rule, not an acceptance criterion Baseline grown to thirty items long lists are skipped wholesale; automate what can be automated and prune the rest Applied to a copy change teaches the team it is ceremony, which is how it stops happening on the stories that matter

Criteria that restate the control library. “Uses parameterised queries” is a coding standard, not an acceptance criterion for this story. The criterion should describe an outcome for this feature: “the report filter accepts only the declared column names, and a probe with an injected clause returns 400”.

Baselines that grow without pruning. A baseline of thirty controls is skipped wholesale. Keep each to the handful that actually apply, and move the rest into automated checks where they belong.

Stories too small to threaten anything. Applying the process to a copy change trains the team that it is ceremony. Publish the trigger list — authentication, authorization, money, personal data, file handling, outbound requests — and let everything else through with the baseline alone.


Automated Testing & CI Validation

# tests/test_invite_abuse_cases.py — one test per acceptance criterion, named after it.
def test_invite_token_is_single_use(client, invite):
    assert client.post("/invites/accept", json={"token": invite.token}).status_code == 200
    assert client.post("/invites/accept", json={"token": invite.token}).status_code == 400

def test_invite_bound_to_the_invited_address(client, invite, other_user):
    r = client.post("/invites/accept", json={"token": invite.token}, as_user=other_user)
    assert r.status_code == 403

def test_invite_revoked_when_inviter_loses_access(client, workspace, inviter, invite):
    workspace.remove_member(inviter)
    assert client.post("/invites/accept", json={"token": invite.token}).status_code == 400

def test_invite_response_does_not_reveal_existing_accounts(client, workspace):
    existing = client.post("/invites", json={"email": "[email protected]"})
    unknown  = client.post("/invites", json={"email": "[email protected]"})
    assert existing.status_code == unknown.status_code
    assert existing.json() == unknown.json()
- name: Every security criterion has a test
  run: python3 tools/check_criteria_have_tests.py --tickets .tickets/ --tests tests/

The last check is the one that keeps the practice honest over time: it fails when a ticket declares a security criterion that no test name references, which is precisely how criteria quietly become decoration.


When to Escalate Beyond the Baseline

Most stories are served by an abuse case and a baseline. Three situations warrant more: a design with no precedent in the codebase, a feature that moves a trust boundary, and anything where the worst realistic outcome would be reportable. In those cases, run a full modelling session with the technique the situation calls for — usually a goal-directed one — and treat the abuse cases as the output rather than the input. Record which route was taken and why, so the escalation decision is itself reviewable later.

Compliance Mapping

Framework Control Satisfied By
SOC 2 CC8.1 — change management Security acceptance criteria in the definition of done, evidenced per pull request
OWASP ASVS V1.1.2 — secure development lifecycle Control baselines per feature type, referenced by identifier
OWASP ASVS V1.1.3–V1.1.4 — threat and requirement documentation Abuse cases and testable criteria stored with the work item
NIST SSDF PW.1 — design software to meet security requirements Requirements derived from abuse cases at refinement time
ISO 27001 A.8.25 — secure development lifecycle Documented process with tests as the evidence of application

Common Pitfalls Checklist


Frequently Asked Questions

How is an abuse case different from a threat in a threat model?

A threat is a property of the system; an abuse case is a story about a feature. The threat says “the session identifier is not rotated at privilege change”. The abuse case says “as someone who can plant a cookie, I get the victim to sign in on an identifier I already hold, so I inherit their session”. The second sits beside a user story, gets estimated alongside the work, and yields acceptance criteria — which is why it reaches implementation far more reliably than a line in a document.

Does every story need an abuse case?

No, and insisting otherwise is how the practice dies. Most stories are covered by the baseline for their feature type, and inventing analysis for a copy change spends attention you will want elsewhere. Require abuse cases where the story touches authentication, authorization, money, personal data, file handling or outbound requests, and publish that trigger list so the rule is applied consistently rather than by mood.

Who writes them — engineers or the security team?

Engineers, with the security team providing baselines, the trigger list, and review on the hard ones. Requirements written by a security team arrive as an external checklist and are treated as one. Abuse cases written by the engineer building the feature are informed by how it actually works, and they routinely name the specific bypass that a reviewer looking from outside would never have guessed at.

How do you keep this from becoming ceremony?

Tie every abuse case to a test. If a criterion cannot be expressed as something a test or a reviewer can check, it is a principle rather than a requirement, and principles do not survive a busy sprint. The measure of success is not how many abuse cases were written; it is how many failing tests they produced before the feature shipped.