
Blind Trust in Vibe Coding: How Stack Ignorance Leads to Broken Authorization
Vibe coding is fine for scaffolding a UI. It gets risky when the UI starts acting like the authorization layer. That shortcut holds up right until a low-privilege account figures out how to send the request the button was hiding.
What vibe coding gets wrong about authorization
The core mistake is simple: the person building the feature thinks in terms of screens, not trust boundaries. They wire up a button that only appears for admins, or disable an action when the current account looks limited, and call it finished.
That is not access control. That is presentation.
Authorization only matters where the action is accepted. If the server accepts the request, the client was never the final authority. When you build by vibes, it is easy to miss that distinction because the app behaves correctly in the browser.
The hidden stack behind a simple UI
A modern feature usually crosses several layers:
- a React component decides what to render
- a state store decides what the user can see
- an API call carries an ID or role flag
- a backend handler applies business rules
- a database enforces data ownership only if you asked it to
That stack matters because each layer can lie in a different way. A UI can hide a button while still sending the request path. An API can accept a field it should ignore. A database row can be updated if the backend forgets to scope the query.
Frontend checks are not access control
A frontend check is useful for UX. It is not security.
If the button disappears for non-admins, the user still knows the action exists. They can inspect the request, replay it, or call the endpoint directly. I usually assume anything in the browser can be changed, removed, or forged.
The backend still decides who can do what
The backend has the session, the identity, and the real policy decision point. That is where ownership and role checks belong. If the server does not enforce them, every client-side guard is just decoration.
A small example that looks safe until you test it
Here is the kind of code that passes a quick review because the UI looks careful.
async function updateProject(project) {
if (!currentUser.isAdmin && project.ownerId !== currentUser.id) {
alert("Not allowed");
return;
}
await fetch("/api/projects/update", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
projectId: project.id,
ownerId: project.ownerId,
name: project.name
})
});
}At a glance, that feels reasonable. The UI blocks obvious misuse. The request includes the right IDs. The issue is that the client is sending a field it should not control.
The client sends an ID it should not control
If ownerId comes from the browser, an attacker can change it before the request leaves the page. Even if the interface never shows that field, the network payload does.
That is where stack ignorance hurts: the developer remembers the component, but forgets the transport. The request body is part of the attack surface.
The server trusts the wrong field
A broken handler might do something like this:
app.post("/api/projects/update", requireAuth, async (req, res) => {
const { projectId, ownerId, name } = req.body;
const project = await db.projects.findFirst({
where: { id: projectId, ownerId }
});
if (!project) {
return res.status(404).json({ error: "Not found" });
}
await db.projects.update({
where: { id: projectId },
data: { name }
});
res.json({ ok: true });
});This looks like a check, but it is checking the attacker-supplied ownerId, not the session user. A different user can sometimes pass a matching value and gain write access if the rest of the logic is sloppy.
How stack ignorance turns into a real bug
This class of issue usually shows up in one of three ways.
Missing ownership checks
The handler validates that a record exists, but not that the caller owns it. That is common in “edit by ID” flows. If the route accepts projectId, orderId, or invoiceId and never ties it back to the session, you have a problem.
Role checks in the wrong layer
The UI hides an admin action, but the API route only checks whether the request “looks right.” That works until someone bypasses the UI. Security has to survive direct HTTP requests, not just button clicks.
Assumptions that break during refactors
A feature starts with one tenant model, then grows. A field that used to be safe becomes dangerous after a refactor, but the old client-side guard survives. These bugs are common because the code still “works” for the original happy path.
How to test authorization like a skeptic
Do not trust the browser. Trust the request.
Reproduce with a low-privilege account
Log in as the least privileged role that can still reach the feature. If the app has free, member, and admin accounts, test with the free or member account first. You want to see what the API accepts when the UI is already trying to be helpful.
Mutate requests instead of clicking buttons
Use DevTools, a proxy, or a simple script to change:
- object IDs
- role fields
- tenant IDs
- owner IDs
- action names
If the server still performs the action, the server is not enforcing the rule.
Compare UI behavior with API behavior
A useful test table looks like this:
| Test | UI result | API result | Finding |
|---|---|---|---|
| Hide button for non-admin | Button missing | Request still succeeds | Client-side only control |
| Edit own record | Allowed | Allowed | Expected |
| Edit another user's record | Blocked in UI | Allowed with mutated request | Missing ownership check |
| Send forged role field | Field hidden | Server trusts value | Wrong trust boundary |
Defensive patterns that hold up
Enforce authorization on the server
Every sensitive route should verify the caller's rights before any write happens. That includes reads when the data is private. If the policy is “only the owner can edit,” the query should include the session identity, not a client-supplied guess.
Derive access from session context, not client input
Use the authenticated user from the session or token, then apply policy rules from that identity. The browser should never tell the server who it is allowed to be.
Add tests for forbidden paths
You need tests that prove the bad path fails:
- another user cannot update the record
- a member cannot invoke an admin action
- a forged ID does not bypass ownership
- a stale UI state does not widen access
These tests are boring in the best way. They keep refactors from quietly reopening the hole.
What to tell the team when you find it
Keep it plain: the UI is hiding the action, but the backend is still accepting it. Show the request, show the modified field, and show the response. That is usually enough to make the issue unarguable.
I try to frame it as a trust problem, not a blame problem. The code is not “bad” because someone used a shortcut. It is broken because the trust boundary was drawn in the wrong place.
Conclusion
Blind trust in vibe coding leads to a very specific kind of authorization bug: the app looks controlled until you stop using the UI and inspect the request. If the server does not verify identity, ownership, and role at the point of execution, the frontend is only a suggestion.
The fix is not more hidden buttons. The fix is server-side authorization, session-derived identity, and tests that attack the API directly.


