Threat Models That Can Street Fight: Vibe Code to Prod

Napkin-math security for companies shipping vibe-coded software. A 15-minute method, five questions, and a copy-pasteable prompt.

Napkin-math security for companies shipping vibe-coded software.

The New Gap

AI made it trivially easy to build software. It did not make it trivially easy to build safe software.

For decades, engineers carried tribal knowledge that kept systems alive: never trust client input, don’t commit secrets, validate on the server, assume the network is hostile. Scar tissue from incidents, passed down in code review and post-mortems. That knowledge doesn’t magically transfer.

Vibe coders are at companies now. Shipping production code, not side projects. The code works, looks clean, passes the tests. But the person who wrote it may not know their API key is in the client bundle, or that changing a user ID in the URL returns someone else’s data. This isn’t a criticism (somewhat of a vibe coder myself). It’s what happens when the barrier to building drops faster than the barrier to understanding what you built.

The security industry has an answer. It’s called threat modeling: systematically thinking through how your system could be attacked before you ship it. In its formal incarnation, it involves frameworks like STRIDE (six categories of threats), PASTA (a seven-stage risk analysis process), full data flow diagrams, and multi-day workshops. Roughly as accessible to a shipping startup as a particle accelerator is to a home cook.

Here’s the thing, though. There’s a spectrum. On one end: spending zero seconds thinking about threats before shipping. That’s negligent. On the other end: running a three-week formal analysis on every feature at a 40-person company. That’s insane. Nobody would argue for either extreme.

The interesting question is: where’s the elbow in the ROI curve? How much threat thinking, applied how, gives you the most protection per minute invested?

If you just want the tool: skip to “Try It Right Now” for a copy-pasteable prompt that walks you through the whole threat model.

The name of this approach borrows from Sanjoy Mahajan’s Street-Fighting Mathematics: the idea that an approximate answer obtained quickly is more valuable than an exact answer obtained slowly, or never obtained at all. That’s the whole philosophy. Formal threat modeling is precise but slow, so teams do nothing, and nothing is the worst possible answer.

The Street-Fighting Math Parallel (why this works)

Mahajan, a professor at MIT, teaches heuristics for getting approximate solutions to hard problems without the machinery of formal proofs. Six of his techniques map onto security thinking with surprising precision:

Easy cases test your model at the extremes. In security: ask two questions. First, “what’s the dumbest attack that would work?” This reveals the lowest-effort, highest-probability threats. Second, “what if the attacker has root on every box?” This reveals your maximum blast radius. If your system collapses completely when one employee’s laptop is compromised, you’ve failed the easy-case test. (CircleCI learned this the hard way in 2023. One compromised session token gave access to every customer’s stored secrets.) These two bookend questions constrain everything in between.

Order-of-magnitude estimation asks: is the answer closer to 1, 100, or 1,000,000? In security: is this a lose-the-company risk or a minor embarrassment? A leaked analytics key with read-only access to anonymized data? That’s a $1K problem. A leaked database credential with access to every customer’s PII? That’s a $10M problem. You don’t need a Monte Carlo simulation to tell the difference.

The other four: dimensional analysis (check that the attacker capability matches the asset value and access required), lumping (group threats into buckets instead of modeling every individual CVE), successive approximation (ship the rough model, refine next sprint), and pictorial proofs (draw the architecture and the threats appear at the boundaries).

The meta-principle across all of these: educated guessing beats willful ignorance. Always.


The Evidence: What Actually Goes Wrong

If the same threats keep causing breaches decade after decade, then identifying those threats shouldn’t require a PhD. Let’s look at what actually happens.

The AI Amplification

This is where the landscape shifts. Two things are happening simultaneously:

First, exploitation is getting easier. Attacks that required technical skill now require a prompt. The floor on attacker capability just dropped.

Second, the code being produced has more blind spots. AI code generation is optimized for functionality, not security posture. Consider a concrete example: you ask an AI to build a user profile page. It generates a Next.js API route that takes a user_id from the query params and returns that user’s data. The code works perfectly. It’s clean, typed, handles errors. But it never checks whether the authenticated user is the same as the requested user. Anyone who changes the ID in the URL gets anyone else’s profile. That’s the IDOR pattern from earlier, and the AI didn’t flag it because you asked for a feature, not a security review.

In multiple studies, LLMs chose insecure implementations 28-72% of the time, and developers using AI assistants wrote less secure code while feeling more confident about it. Not because the AI is malicious. Because you didn’t ask it not to.

The result: the gap between “I can ship this” and “I understand what could go wrong” has never been wider.

Here’s the good news: the same tool that introduced the vulnerability can help you find it. AI is excellent at asking “what could go wrong here?” when you tell it to. The problem isn’t AI. The problem is that nobody’s asking.


The Breaches

The root causes are always the same: leaked credentials, missing access controls, excessive permissions, unpatched known vulnerabilities. Drizly, Uber, CircleCI, and dozens more. None involved a novel zero-day. All would have surfaced in a 15-minute “what could go wrong?” session.


Five Questions Before You Ship

STRIDE is the industry-standard threat taxonomy: Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege. It’s precise, comprehensive, and built for security professionals working through formal data flow diagrams.

The five questions below are inspired by the same thinking, reframed for anyone, including the person who built the feature with an AI assistant and has never heard of STRIDE. They cover the threats that show up most often in the breach data, in language that doesn’t require a security background. They’re the input to the real deliverable: the attack mind map.

Secrets

“Can anyone see my keys?”

Keys in code, secrets in git, env vars leaking into client bundles. AI generators are particularly bad here: they’ll inline an API key if you give them one in the prompt.

What to check:

  • Are there any secrets in the codebase? (Run a secret scanner. GitGuardian found 10 million hardcoded secrets in public GitHub repos in a single year.)
  • Are environment variables properly scoped? (Server-only vs. client-exposed)
  • Are secrets rotatable? (If a key leaks, can you change it without downtime?)

Boundaries

“What runs on the client vs. the server? What can the user see and modify?”

Everything in the browser is user-controlled territory. The vibe coder may not realize this.

What to check:

  • Where’s the trust boundary? What crosses it?
  • Is validation happening on the server, or only on the client? (Client-side validation is a UX feature, not a security control.)
  • Can the user see things in the network tab they shouldn’t? (API responses that return more data than the UI displays)
  • Are there any admin or internal endpoints accessible from the client?

Access Control

“Who can do what? Am I actually checking?”

Broken Access Control has been the #1 item on the OWASP Top 10 since 2021. It’s the hardest category for automated tools to catch because it’s a logic flaw, not a syntax error. The code is technically correct. It just doesn’t check whether the person making the request is allowed to.

The classic test: change a user ID in a URL or API request. Do you get someone else’s data? That’s the IDOR we talked about earlier, and it’s one of the most exploited vulnerabilities on the internet.

What to check:

  • Can user A access user B’s data by modifying a request? (Change the ID. Try it.)
  • Are there role checks on every endpoint, or just on the frontend routes?
  • Can a regular user hit admin endpoints?
  • What happens with an expired or invalid token?
  • If you’re using Supabase, is Row Level Security actually enabled on the tables this feature touches? It’s the difference between “the database checks permissions” and “the database trusts everyone.” Many AI-generated tutorials disable RLS to keep things simple.

Blast Radius

“If this breaks, how bad is it?”

Not a precise risk score. Just: is this a bad-day problem or a lose-the-company problem? If you can’t tell the difference, that uncertainty is the most important finding.

What to check:

  • If this component is fully compromised, what does the attacker get?
  • Is there segmentation? (Can a breach in one service spread to others?)
  • What data is at risk? (PII, payment info, credentials, or nothing sensitive?)
  • Would you need to notify customers? Regulators?
  • Is your API returning more data than the frontend displays? This is one of the most common vibe-coded patterns: select('*') or returning the full user object when the UI only renders three fields. Anyone with browser dev tools open can see everything your API sends back.

Least Power

“Does this need all this access?”

Minimum permissions for the job. AI-generated code defaults to the path of most privilege because it’s the path of least resistance.

What to check:

  • Does this API key have write access when it only needs read?
  • Does this database user have access to all tables, or just the ones it needs?
  • Does this service account have admin privileges?
  • If this credential is compromised, what’s the blast radius? (This connects back to the previous question.)

The 15-Minute Method

The point of this method is one artifact: the attack mind map. Everything else is input to it. The five questions tell you what to look for. The steps give you a sequence. But the mind map is what you ship. It lives in the PR, the doc, or the Slack thread. It’s the evidence that someone thought about what could go wrong before the code went live.

When to run it: Before shipping any vibe-coded feature or any significant new feature that touches data, auth, or external services. Not every one-line CSS fix. But if the PR introduces a new endpoint, handles user data, or connects to a third-party service, run the fifteen minutes.

Step 1: Describe What You’re Shipping (2 minutes)

Answer three questions in plain language:

  1. What does it do? One sentence.
  2. What data does it touch? (User PII, credentials, payments, files, nothing sensitive)
  3. What systems does it talk to? (Database, third-party APIs, file storage, auth service)

Here’s our example. You vibe-coded a user settings page in Next.js with Supabase:

“User settings page. Next.js API route that reads/writes the profiles table in Supabase (display name, email, avatar URL). Uploads avatars to Supabase Storage. Uses Supabase Auth for sessions.”

Step 2: Draw the Pieces (3 minutes)

Sketch the components and what talks to what. This doesn’t need to be a formal diagram. Boxes and arrows in Excalidraw, on a whiteboard, or even a markdown list. You can also prompt your AI to generate this: “Draw me an architecture diagram of how this feature works, showing what talks to what.”

The critical thing to identify: where are the trust boundaries? Where does user-controlled input enter the system? Where does data cross from less-trusted to more-trusted zones?

[Browser (React)] --fetch--> [Next.js API Route] --supabase-js--> [Supabase DB]
                                    |
                              [Supabase Storage]
                                    |
                              [Supabase Auth]

--- trust boundary: everything above this line is server ---
--- everything in the browser is user-controlled territory ---

Step 3: Ask the Five Questions (5 minutes)

For each question, write down anything that triggers a “hmm.”

QuestionThis Feature
Secrets: Can anyone see my keys?Supabase anon key is in the client bundle (by design). Is the service_role key server-only?
Boundaries: What’s client vs. server?File upload size/type validation. Is it client-side only or enforced in the API route too?
Access Control: Who can do what? Am I checking?The API route takes a user_id param. Can user A update user B’s profile by changing it?
Blast Radius: How bad if this breaks?Profile data includes email + display name. PII exposure. Would need to notify users.
Least Power: Does this need all this access?Is the API route using the service_role key (full DB access) when it could use the user’s own JWT?

You don’t need to solve anything yet. You’re identifying threats, not mitigating them.

Step 4: Build the Attack Mind Map (4 minutes)

Take the threats from Step 3 and draw them as a mind map. The attacker’s goal at the center. Branches are attack paths. Score each path:

  • 🔴 Red: no current mitigation
  • 🟡 Yellow: partial mitigation
  • 🟢 Green: adequately mitigated

You can sketch this in Excalidraw, generate it with a prompt (“Turn these threats into a mind map diagram”), or use Mermaid syntax that renders in GitHub, Notion, and most markdown tools:

graph TD
    A["Steal User Data"] -->|via| B["IDOR on Settings API"]
    A -->|via| C["Malicious File Upload"]
    A -->|via| D["service_role Key Leak"]
    A -->|via| E["Over-fetched API Response"]

    B --> B1["Change user_id in request"]
    B1 ---|"🔴 No ownership check in API route"| B2["Read/write any user's profile"]

    C --> C1["Upload .html/.exe as avatar"]
    C1 ---|"🟡 Client checks file type, server doesn't"| C2["Stored XSS or RCE"]

    D --> D1["service_role key in client bundle or git"]
    D1 ---|"🟢 Key is server-only, .env.local not committed"| D2["Full database access"]

    E --> E1["API returns full profile row"]
    E1 ---|"🟡 Frontend only shows 3 fields, API returns 12"| E2["Leak email, metadata, internal IDs"]

The canonical format is plain markdown with emoji scoring. It works everywhere, needs no tooling, and is easy to scan in a PR review:

## Attack Mind Map: User Settings Page

- **Goal: Steal User Data**
  - IDOR on Settings API 🔴
    - Change user_id param → read/write any user's profile
    - No server-side ownership check in the API route
  - Malicious File Upload 🟡
    - Upload .html as avatar → stored XSS
    - Client validates file type, server does not
  - service_role Key Leak 🟢
    - Key is in .env.local, server-only, not in git
  - Over-fetched API Response 🟡
    - API returns 12 fields, frontend displays 3
    - Leaks email, internal IDs, metadata to anyone with dev tools open

Step 5: Fix Before Ship (2 minutes)

Look at your red and yellow paths. Pick the top 1-2 that are highest blast radius and lowest effort to exploit.

For our example:

  1. IDOR on Settings API (RED): Add ownership check in the API route. Compare user_id param against the authenticated session. Don’t ship without it.
  2. Over-fetched API Response (YELLOW): Select only the fields the frontend needs instead of SELECT *. Quick fix, do it now.

A red path is a ship-blocker. If the team decides to ship anyway, the mind map is the evidence that the risk was known and accepted. That’s not security theater. That’s how you make risk decisions explicit instead of invisible.

That’s it. Fifteen minutes. You’ve gone from “I think this is fine” to “I know what the top risks are and which ones to fix first.”

Step 6: Close the Loop (after shipping)

One line, added to the mind map after the feature has been live for a week or two:

“Shipped 2026-03-15. No issues.” or “Missed rate limiting on avatar upload. Added to Boundaries lens for next review.”

This is the step everyone skips and the one that makes the whole practice improve over time. It takes 30 seconds. It turns a one-off exercise into a feedback loop.


What to Add Next

Once this becomes habit, bolt on more: the OWASP Proactive Controls (ten action-oriented security controls), Adam Shostack’s Elevation of Privilege card game for team sessions, or his comprehensive threat modeling resource page covering 16+ methodologies. For the canonical deep dive, Shostack’s Threat Modeling: Designing for Security is the book.


Try It Right Now

Pick a feature you’re shipping this week. Copy the prompt below into your LLM of choice. It will interview you through the entire threat model and produce the attack mind map at the end.

You are a security-minded engineer helping me threat model a feature
before I ship it. Interview me through the following steps. Ask one
question at a time. Wait for my answer before moving on.

Step 1 — Describe: Ask me what the feature does, what data it touches,
and what systems it talks to.

Step 2 — Draw: Based on my answers, sketch an ASCII architecture diagram
showing the components and data flows. Ask me to confirm or correct it.
Identify the trust boundaries.

Step 3 — Five Questions: Walk me through each of these, one at a time.
For each, ask me the question and flag anything that sounds risky:
  - Secrets: "Can anyone see your keys?"
  - Boundaries: "What runs on the client vs. the server?"
  - Access Control: "Who can do what? Are you actually checking?"
  - Blast Radius: "If this breaks, how bad is it?"
  - Least Power: "Does this need all this access?"

Step 4 — Attack Mind Map: Based on everything we discussed, generate an
attack mind map in markdown format. Use 🔴 (no mitigation), 🟡 (partial),
🟢 (mitigated) to score each path.

Step 5 — Fix Before Ship: Recommend the top 1-2 red/yellow items to fix
before shipping. Be specific about what to do.

Start by asking me Step 1.

Paste the mind map into your PR description.

TL;DR: The implicit safety net of tribal knowledge is gone. Try the prompt on your next feature. Fifteen minutes. If it improved your security stance, it was worth it. Let me know how it goes.