September 4, 2026
The Security Review Where Threat Modeling Stopped Being a Checkbox for Me
Or: what happens when you stop hunting for bugs and start hunting for the bugs that actually matter
By Miri
7 min read
Chapter 1: "Wait, What Am I Even Looking At?"
So I got assigned a new security review. I open the docs, I open the code repo, and within about four minutes I have exactly one thought:
"β¦is this their web app?"
I was reviewing a data-integration platform built by a company let's call Donut, specifically Donut Server, plus its companion Donut Editor. For a solid chunk of my first day, I genuinely thought I was looking at some kind of web application (they had an /api directory!). I was checking if it was internet-facing. I had my whole "external, internet-accessible web app" checklist loaded up and ready to go: authn, authz, injections, the usual suspects, external-facing assumptions baked in.
It was not a web app. It was not internet-facing. I had, with great confidence, prepared to review the wrong thing entirely.
Once the fog cleared: this is an internally deployed data product. Companies run Donut Server and the Donut Editor in-house. It's not sitting on the open internet. It's sitting inside companies, being used by their own employees to move around some of the most sensitive data they have -customer records.
That single realization changed everything about how I needed to approach this.
Chapter 2: My Usual Playbook Was Suddenly Useless
Normally, my process for a web app looks like this: authn, authz, injection points, business logic abuse, repeat, write report, go get coffee. It's a good playbook. It works great for internet-facing apps. It is also, I discovered, completely unequipped to prioritize anything on a product like this.
So for the first time in a while, I did something almost embarrassingly basic. I stopped scanning code paths for the usual suspects and did actual, proper threat modeling. Not the five-minute checkbox version. The real one.
Chapter 3: Step 1 - What Are We Actually Protecting?
Before touching a single endpoint, I asked the most unglamorous question in application security: what are the critical assets?
Not "what's vulnerable" - what actually matters if it breaks.
For Donut, the answer was pretty clear once I looked at what the product does: the data. All of it. The SQL layer, the customer data flowing through it, the internal structures, the pipelines moving information between systems. This product exists to move and hold sensitive customer data at scale. That's the crown jewel. Everything else is scaffolding around it - or, fittingly, the dough around the good part.
Writing this down sounds obvious. It is obvious. But naming it explicitly, on paper, before doing anything else, completely changes what you go looking for next. It's the difference between wandering a museum and having a floor plan with "the important piece is in room 12" circled in red.
Chapter 4: Step 2 - Who Are We Actually Defending Against?
This is where things got genuinely interesting, because my default mental threat model is always "anonymous internet user, no prior access." That threat basically doesn't exist here. There's no public login page for them to find.
So who's the actual threat actor?
Given that this is deployed inside company networks and used by authorized internal users, the real risk isn't "random person on the internet." It's:
-
The unauthorized-but-technically-in-the-building employee poking around somewhere they shouldn't be.
-
The authorized user doing something they are technically allowed to click, but really, really shouldn't be able to.
-
Anyone on the internal network who gets a little too curious about that admin panel nobody explained to them.
This was the actual mindset shift. I wasn't reviewing this product to defend the vendor from the internet. I was reviewing it to defend Donut's clients from the people already inside. I stopped thinking purely in "how would someone break in from outside" terms, and started thinking like a security architect sitting on the client's side of the table, asking: "How do we ship the best data product to our customers? What security measures need to be built in so they can trust it enough to actually buy it?"
That's a genuinely different question than the one I usually ask. It reframes the whole engagement.
Chapter 5: Step 3 - Map the Actual Surface
Only now, with an asset and a threat actor defined, did I start mapping the surface for real:
-
Roles - who can do what, and who's supposed to be able to do what
-
Endpoints and parameters that actually interact with users
-
Integrations - MCP servers, systems etc.
Chapter 6: Step 4 - NOW We Go Looking (With a Purpose)
This is the part where I finally went back to my familiar review process β auth checks, business logic, the usual code-level suspects β except now every single thing I tested had a reason behind it, tied back to "does this threaten the data, given who our actual threat actor is."
Two findings from this approach stuck with me:
First finding: One key to rule them all. The encryption setup used the same hardcoded KEK (key-encryption-key) across every customer of the product. One key, shared everywhere. On paper, "hardcoded key" is a known finding category, nothing shocking. But run it through the lens of "the data is our asset, and multiple client organizations share this product" and it becomes something much scarier: a single point of failure that could ripple across every customer, not just one. That's not a bug ticket, that's a "we need to talk" finding.
The recommendation was straightforward to state, if not trivial to implement: generate the KEK randomly and uniquely, per customer/deployment, instead of shipping one master key baked into the product for everyone.
And the findings I got excited about, then quietly walked back. I also found a SQL injection point. Normally that's an instant "drop everything." Except: run it through the threat model- the user who could reach that injection already had legitimate access to that exact same data through the product's normal features. No boundary crossed, no privilege gained.
Same story with a batch of Java deserialization issues. Donut Server is written in Java, and there's no shortage of deserialization surface to poke at. Most of what I found was only reachable by a user who could already manipulate the same data directly through legitimate functionality
One deserialization finding was different, though, and it's the one that made the whole exercise feel worth it. Donut Server integrates with Kafka and in most of these internal deployments. Which means a deserialization bug reachable through that integration isn't "a user manipulating data they could already touch." It's a completely different actor , someone from IT infrastructure, exactly the kind of "authorized but shouldn't have this level of reach" insider we'd already flagged as dangerous - getting a path straight into Donut Server itself, an RCE risk. That one got flagged as a genuinely high-severity finding.
That's the real value of this exercise: not running the standard Java-vulnerability checklist and reporting everything that lights up, but filtering every finding through "does this match an actor and a path we already said we cared about." Some impressive-looking bugs get quietly deprioritized. Some quieter-looking ones get pushed straight to the top.
None of this would've been wrong to report under my old "find it, flag it" approach. But without the threat model, I wouldn't have known which finding actually mattered versus which one just looked scary on paper. Threat modeling didn't just help me find things - it told me what to prioritize, which is arguably the more valuable skill for a reviewer to have.
Chapter 7: The Actual Lesson
Here's the shift, stated plainly: I stopped reviewing code purely by category (auth, injection, logic - check, check, check), and started reviewing it as someone protecting the clients who'd be trusting this product with their data. Those are not the same exercise, even though they look similar from the outside.
Checklist-brain asks: "What's broken?" Consultant-brain asks: "What, if broken, actually affects someone - and who's most likely to be the one breaking it?"
The second question is slower to get started with. It is also the only one that reliably points you at the finding that actually matters, instead of the finding that was just easiest to trip over.
The Tips, Distilled (For People Who Skipped to the Bottom, No Judgment)
-
Confirm what you're actually reviewing before you review it. Sounds insulting to mention. Would've saved me a day.
-
Name the asset explicitly. Write it down. "The data" is a fine answer if you can say why and which data.
-
Define the realistic threat actor - not the scariest one, the realistic one.
-
Map before you dig in. Roles, endpoints, data flows, integrations. Boring. Essential.
-
Let the threat model drive prioritization, not just discovery. Two similar-severity bugs can mean very different things depending on who can reach them and what they touch.
-
Ask whose side you're really defending. Sometimes it's not the vendor in front of you - it's their clients, who never even get a seat at the table.
If there's one thing I'd tell past-me: the more complex or unfamiliar a system feels, the more you need the methodology. Threat modeling isn't overhead you add once the easy stuff is done - it's what turns "this is too much to look at" into "here's exactly where to look first."