The gap a solo developer ships is the one nobody mapped
Threat modeling for a solo developer is not a workshop or a vendor engagement. It is a 30-minute exercise you run on your own app before the URL spreads, where you write down where trusted code stops and untrusted input begins, list every route a stranger can reach, and check that the data you care about is actually guarded at each of those points. The concrete launch risk is narrow and common: a route or data flow that you assumed was private is reachable by anyone, because nobody ever drew the line between inside and outside.
Two of those facts are observable today. Which routes respond to an anonymous request, and what a public response contains, you can check from a clean browser in minutes. Whether the route should be private, and whether the auth check behind it actually enforces what you intended, is a judgment call only you can make against your own data model. Keep those separate. A route returning data is observable. A route returning data that it should not is a conclusion you reach by comparing the observation against the boundary you drew.
The point of the exercise is to make that boundary explicit while it is still a sentence in a note, not a row in someone else's scraper.
Why AI-built apps skip the boundary entirely
An AI builder generates code that works on the first try, and working is the trap. A prompt like "add a dashboard for the logged-in user" produces a page, a route, and a query that all function when you test them while logged in. The thing that does not get generated is the explicit decision about what happens when an anonymous caller hits that same route directly, or when a logged-in user requests another user's id.
The model optimizes for the happy path you described, not the hostile path you did not. So the generated app tends to enforce auth at the page layer, where you can see it, and leave the API route or the database query open underneath, where you cannot. The trust boundary exists in your head as "only my users see this" but was never written into the code as a server-side check.
- The app runs end to end in your own session, so the missing boundary is invisible from the inside.
- AI-generated middleware often guards the page render but not the underlying data route or the BaaS endpoint behind it.
- Fast deploy means the public URL is live before anyone has listed which routes a stranger can reach. A simple threat model for an indie hacker is mostly the act of writing that list down once.
Run the 30-minute pass on your own app
This is the whole exercise. Four steps, a text file, and your own deployed URL. You do not need STRIDE or any formal method for a small project; the value is in the enumeration, not the framework.
First, enumerate trust boundaries. Write one line for each place where untrusted input crosses into trusted code: the browser to your API, your API to the database, your app to any third-party webhook, anyone to your file storage. Each line is a boundary where a check has to exist.
Second, list every public route. Open your router config or your pages and API directories and write down every path. Mark each one as intended-public (landing page, login) or intended-private (dashboard, admin, any /api route that returns user data). This list is the single most useful artifact of the whole exercise.
Third, identify the three highest-impact data flows. Not all data is equal. Find the three flows that would hurt most if exposed: user records, payment or billing data, anything that lets one account act as another. For each, trace where it travels and where the access check sits.
Fourth, note where auth is actually enforced. For each intended-private route and each high-impact flow, answer one question: is the check on the server, or only in the UI. Then verify the private routes from outside.
- Safer result: a request to an intended-private route from a clean session (no cookie, no token) returns 401, 403, or a redirect to login, and the body carries no data.
- Suspicious result: the same anonymous request returns 200 with real data, or a logged-in user can change an id in the URL and read a record that is not theirs. Either means the boundary you drew is not enforced.
- Check the data routes directly, not just the pages. A protected dashboard page in front of an open GET /api/users route is the exact gap AI builders leave.
Fix what the model surfaced, and know its edges
The exercise produces a short list of routes and flows where the boundary is missing. Fix those by moving the check to the server: enforce auth in the API route handler or middleware that returns the data, not only in the component that renders it. For object access, confirm the server checks that the requested record belongs to the caller, rather than trusting an id from the URL. For your BaaS, that means real row-level rules on the data, not a client-side filter.
Be honest about what this 30-minute pass does not cover. It is enumeration and a few spot checks, not proof. It will not find a subtle authorization bug that only triggers on a specific role combination, it does not test business logic (a discount that can go negative, a quota that can be bypassed), and it does not exercise every route under every condition. It surfaces the boundaries you forgot to draw; it does not certify the ones you did.
- The pass distinguishes authentication (is this caller who they claim) from authorization (is this caller allowed this specific record). AI-built apps usually get the first roughly right and the second wrong.
- A clean spot check on three flows says nothing about a fourth you did not trace. The list is only as good as your enumeration.
- Some gaps only a code review or a deeper engagement will catch. For where that line sits, see public scan vs code review before launch, and for the higher threshold, when an indie app needs a pentest.
Where VibeCodeGuard fits, and what it does not prove
The threat model is the manual step: you draw the boundaries and list the routes. VibeCodeGuard's Launch Check is the automated verification of the public-surface half of that list. Once you have written down which routes a stranger can reach, a scan against the public URL confirms the findings an outside visitor can actually observe: missing security headers, source maps and exposed build or deploy artifacts, secret-shaped strings in the client bundle, open or wildcard CORS, publicly readable buckets and endpoints. It checks the public surface so your enumeration is not the only thing standing behind it. For the full solo-founder split of what to scan versus what to review by hand, see the solo-founder SaaS pre-launch security checklist.
What it does not do is the modeling. Deciding which routes should be private, which data flow matters most, and whether a given auth check enforces your intent is the judgment you bring to the exercise. The scan confirms part of the result; it does not replace the thinking that produced the list.
Next step: open a blank note and spend the 30 minutes now, ending with a list of intended-private routes you have verified return no data to an anonymous caller. Then run a focused Launch Check against the public URL before sharing it, so an exposed artifact or header gap shows up in your scan and not in someone else's.
A clean Launch Check does not prove your threat model was complete. The scan reads what is reachable from outside; it does not know which routes you meant to be private, and it does not read the authorization logic behind a route that requires a login. Treat a flagged item as a launch blocker, and treat a clean result as confirmation of the public surface only, not of every boundary you drew.
> launch check
Scan the public surface before launch.
Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.