
Auditing Vibe-Coded Apps: Missing Authentication, CORS, and Input Validation
Why vibe-coded apps fail in the same predictable places
I keep running into the same pattern when auditing vibe-coded apps: the UI looks polished, the demo works, and the first real test goes straight past the frontend into the backend. The breakage is usually not clever. It is one of three things:
- missing authentication on a route that should require it
- CORS set up as if every browser request were trustworthy
- validation only happening in the client
Those bugs are boring in the useful sense. They are easy to reproduce, easy to explain, and easy to miss when the app was stitched together quickly from prompts and snippets.
Start with the trust boundary, not the UI
When I audit one of these apps, I ignore the frontend story first. The question that matters is simple: which requests does the server trust, and why?
What to map before you test
Before sending any tampered request, I map four things:
- the user roles that exist
- the routes or actions that change state
- the tokens, cookies, or headers used for auth
- which fields come from the browser versus the server
If the app uses server actions, Next.js API routes, or a small Express backend, I also note where the boundary shifts. A route can look “internal” from the UI and still be public over the network.
How I split frontend assumptions from backend reality
The frontend may hide buttons, block empty fields, or disable controls. That is not security. It is just UX.
A useful test is to ask: if I skip the UI and send the request myself, does the server still enforce the rule?
Treat the browser as an untrusted client. If a rule matters, confirm it on the server.
Missing authentication checks
This is the most common failure mode in vibe-coded apps. A route was built to “just work,” then nobody went back and added enforcement.
Reproducing access with a logged-out or low-privilege account
I usually test in this order:
- send the request with no cookies
- send it with a free or low-privilege account
- send it with a different account's identifiers in the payload
If the route still returns 200 or mutates data, the bug is real.
A common example is a profile update endpoint that accepts userId, email, or planId from the client and never checks whether the caller owns that record.
Common failure modes in API routes and server actions
The same mistakes show up over and over:
if (session) { ... }without checking role or ownership- server actions that trust hidden form fields
- API routes that validate shape but not authorization
- object IDs accepted directly from the request body
The bad version looks secure because it touches a session object somewhere. The real question is whether the session is compared against the target resource.
CORS misconfigurations that look harmless
CORS bugs are easy to misunderstand. A permissive origin by itself is not always exploitable. The damage shows up when it combines with credentialed requests and weak auth checks.
Why permissive origins are not the main issue by themselves
A response like Access-Control-Allow-Origin: * is noisy, but by itself it does not let a random site read authenticated data with cookies.
The test that matters is whether the server:
- reflects arbitrary origins
- allows credentials
- returns sensitive data to a browser request that should not be cross-site readable
Testing credentialed requests and reflected origins
I check three cases:
Origin: https://evil.exampleOrigin: null- no
Originheader at all
If the server reflects the origin and also sends Access-Control-Allow-Credentials: true, I try a safe authenticated request from a test origin I control. If the response becomes readable, that is a real browser-side exposure.
Do not stop at “CORS is open.” Confirm whether the response is readable with credentials and whether the route was supposed to be private.
Input validation gaps that turn into real impact
Client-side validation fails fast, but it does not protect the backend. Vibe-coded apps often validate in React form handlers and assume that is enough.
Checking type, length, and allowlist rules
I look for three basic controls at the API edge:
- type checks: string, number, boolean, array
- length limits: short enough to avoid abuse
- allowlists: only accepted values, not “anything that is not empty”
If a field is supposed to be a status value, I send an unexpected string. If it is supposed to be numeric, I try strings, null, and very large values. If it is a URL or filename, I check whether the backend normalizes or rejects unsafe forms.
Where client-side validation gives a false sense of safety
The frontend often blocks bad input, but the backend still accepts it. That leads to inconsistent state, broken authorization logic, or stored data that downstream code never expected.
This gets risky fast when validation protects:
- role changes
- pricing or plan fields
- file names and paths
- IDs used in follow-up requests
A small JavaScript testing workflow I use
I keep this simple. No heavy tooling is required to prove the bug.
Fetch replay, request tampering, and auth boundary checks
I copy a real request from DevTools, strip it down, and replay it with small changes:
const res = await fetch("https://target.example/api/update", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <test-token>"
},
body: JSON.stringify({
userId: "someone-else",
role: "admin",
name: "Test"
})
});
console.log(res.status, await res.text());
Then I change one thing at a time:
- remove the auth header
- switch to another account token
- alter a single identifier
- send malformed JSON
- replay from a different origin
That tells you exactly which layer is failing.
What to record in a report
A useful report should include:
- the endpoint or action
- the account level used for testing
- the exact request changes
- the response status and effect
- why the server should have rejected it
- the business impact in plain language
Fixes that actually hold up
Enforce auth on the server
Do not rely on hidden buttons, client state, or form restrictions. Check identity and ownership on every sensitive route and action. If the request changes data, the server should verify:
- who is calling
- what they are allowed to touch
- whether the target object belongs to them
Lock down CORS and validate every input at the API edge
Set CORS deliberately for known origins only. Do not reflect arbitrary origins unless you have a very specific reason, and do not combine that with credentialed requests unless you have audited the entire flow.
For input validation, reject bad data before it reaches business logic. That means validating at the API edge, not after the record is already half-processed.
Conclusion
Vibe-coded apps usually fail in ordinary places because speed beats review. The UI looks finished, but the server still trusts the browser too much. If you start with the trust boundary, replay requests outside the UI, and test auth, CORS, and validation separately, the real issues show up fast.


