Route handlers that answer whoever asks
API Route Missing Authentication
Why this workflow produces it
A v0 project is a Next.js App Router app, so there is a real server in the repository from the first prompt — route handlers under app/api/, and server actions. That is a genuine advantage over a scaffold with nowhere to put a secret. It also means the project has an attack surface that a static single-page app does not have, which is why this is the finding that leads the page.
The two rules in the row above fired on the same number of projects, which is what you would expect: one is the general check for a handler with no authentication, the other is the Next-specific version of it. Read them as one finding seen from two angles rather than two separate problems.
An exported GET or POST in a route file is a live, publicly reachable HTTP endpoint the moment it deploys. Nothing in the framework requires a session before the body of the handler runs, and there is no default-deny step to forget — the default is answer.
Middleware does not close this by itself. It is opt-in and matched by path pattern, so a matcher written early does not know about a route generated twenty prompts later. Nothing breaks, nothing warns, and the app works perfectly for you because your own session is always present while you are testing. You never see the response the endpoint gives someone who is not signed in.
The change that prevents it
Put the rule in your project instructions rather than fixing handlers one at a time, and require the check inside the handler rather than only in a matcher. A matcher is a good second layer and a bad only layer.
Paste this into your project instructions, or into the prompt itself:
Every route handler under app/api/ resolves the caller's session as its first statement and returns 401 before doing any work if there is not one. Do this inside the handler, not only in middleware — a middleware matcher does not cover routes added later. If an endpoint is deliberately public, put a comment saying why on the line above the export, and keep it to GET with no user-specific data in the response.
The scan that verifies it
Scan and confirm the count drops. Then do the part a scanner cannot do for you: pick each remaining handler and request it with no cookie and no authorization header. What comes back is the answer.
npx xploitscan scan . -f json \ | jq '[.findings[] | select(.rule == "VC003" or .rule == "VC065")] | length'
VC065 is outside the free rule set. The free scan runs 30 of the 212 rules, so a free plan never evaluates it. This command also counts VC003, which a free scan does run — so the number it returns on a free plan is the VC003 count alone, and it says nothing either way about VC065.
Honest caveat. Both checks read one file at a time, and that cuts in both directions. A route genuinely gated by a middleware matcher still reads as unprotected here, which is our false positive rather than your bug. A route whose gate is imported from a helper we cannot follow reads as unprotected too. Treat each hit as a handler worth opening, not as a confirmed hole.