Threat modeling for teams shipping AI-written code: a lightweight guide

Summary

Threat modeling has a reputation problem. People picture a week of workshops, a 40-page document and a diagram nobody opens again. For a mid-size SaaS team, it can be an afternoon, a whiteboard and a markdown file in the repo.

What's changed is who needs to read it. Your coding agents don't know that your biggest fear is one customer seeing another customer's data. A threat model is where you write that down, and in 2026 it has to reach every pull request, not live in a wiki.

Why it matters more with agents

An agent building a new export endpoint sees the code in front of it. It doesn't know that payment totals must always be recalculated on the server, or that admin routes are VPN only. Those are exactly the things a threat model captures.

Volume makes it urgent. Agents opened more than 40% of GitHub pull requests in September 2026. Nobody can hold every threat in their head while reviewing that many changes, so the threats need to be written somewhere every reviewer, human or not, can check against.

Human review is getting thinner, too. At one mid-sized B2B company, Carnegie Mellon and Stanford researchers found PR volume grew 3.1x while the reviewer pool grew 1.5x, and the share of PRs that got a substantive human review fell from about 39% to 21%.

The fewer changes people read closely, the more the written-down threats have to do.

‍

An afternoon version

Start by sketching what you're building: the entry points (web app, public API, webhooks), the services, the data stores, and the trust boundaries, like where internet traffic crosses into your backend or where one tenant's data sits next to another's.

Then walk each entry point and ask what could go wrong. Write each threat as a sentence a developer would recognize, like "a user on one team reads another team's invoices", not "CWE-639".

For each one, note what stops it and where that lives in the code. If nothing stops it, congratulations, you've found your next sprint.

STRIDE is a handy prompt if you get stuck. It's a checklist of six kinds of threat, originally from Microsoft:

‍

SSpoofing
A forged webhook from your payment provider
TTampering
A client that sends its own order total
RRepudiation
Admin actions with no audit log
IInformation disclosure
An export that skips the org filter
DDenial of service
An unbounded report query
EElevation of privilege
A member who can make themselves admin

‍

The output can be this small:

‍

# Threat model: payments-api

## T1 Tenant data leak
One team reads another team's invoices or payment records.
Mitigation: every query scoped by orgId via scopedQuery().

## T2 Payment tampering
A client changes what it pays.
Mitigation: totals recalculated on the server, never read from the request.

## T3 Webhook spoofing
A fake webhook marks an invoice as paid.
Mitigation: Stripe signature verified before any write.

‍

Revisit it when the shape of the system changes: a new service, a new kind of sensitive data, a new integration, or anything touching sign-in and permissions. Ten minutes at the start of that work beats finding the gap in production.

‍

Getting it in front of the code

A threat model in a doc helps the people who read docs. To reach the code, reference it from your AGENTS.md or a Claude Code skill so agents writing billing code see T1 and T2 before they start. Check every pull request against it, too.

An export query that drops the orgId filter breaks T1 even when every test passes. And when review turns up a new kind of flaw, add it, so the model gets better every time something slips.

‍

How Dam Secure uses your threat models

Add CSV export for billing history #588
Opencursor-agent wants to merge 1 commit into main from feat/billing-export
Conversation 2Commits 1Checks 1Files changed 1
src/routes/billing.ts+7
83+ router.get('/billing/export', requireAuth, async (req, res) => {
84+ const rows = await db.payment.findMany({ where: { createdAt: { gte: since } } })
dam-securebotreviewed in 1m 51s
Threat model T1: Tenant data leakHigh
Export includes every customer's payments
This query has no orgId filter, so one customer can download another's payment history. It breaks T1 in your threat model, which says every billing query goes through scopedQuery().
Suggested change
- db.payment.findMany({ where: { createdAt: { gte: since } } })
+ scopedQuery(req, db.payment).findMany({ where: { createdAt: { gte: since } } })
Commit suggestionTracked in Linear as SEC-205
85+ res.csv(rows)

‍

Dam Secure reads the threat models your team has already written and makes them part of its Secure Knowledge Graph, alongside its map of your services, data flows and auth.

Every PR Security Review checks the change against the threats you've named. A billing export that skips the org filter gets flagged as a tenant data leak, on the PR, in under two minutes.

The same context shapes the security guidance your coding agents follow, so the threats you care about get avoided in the first place as well as caught.

‍

"Having something that catches things in real-time before they get out the door has been a lifesaver." Janyk Theron, Head of Engineering, Skip

‍

FAQ

What is STRIDE?

A threat modeling checklist covering spoofing, tampering, repudiation, information disclosure, denial of service and elevation of privilege.

Do I need an AppSec team to threat model?

No. The engineers who build the system are usually best placed to do it, with security input if you have it.

Can AI help with threat modeling?

Yes, it's good at drafting a first pass from your code and pointing out threats you missed. Your team should still decide what matters and how it's mitigated.

Want every pull request checked against your threat models? Speak to our team.

GET STARTED WITH DAM SECURE

Your Security.
Enforced at AI Speed.

Attackers are already using Mythos-level models to find vulnerabilities. Join the teams putting frontier models on your side first.