Vibe-coded apps ship security holes by default
This is the third post in a series on what vibe-coding leaves behind. The first was about tests that actually prove the feature works. The second was about why those apps keep breaking. This one is about the bug you cannot see by clicking around: the authorization check the AI never wrote.
The check the AI didn't write
Ask an AI to build "an invoice page," and you get a route that loads /invoices/42, fetches the invoice, and renders it. It works. You click through it, the numbers show up, you ship.
What the AI optimized for was rendering invoice 42. It had no reason to ask whether the person logged in is allowed to see invoice 42. So the server hands back whatever row matches that ID. Change the URL to /invoices/43 and you are reading someone else's invoice. That is IDOR, an insecure direct object reference, and it is the most common flavor of broken access control on the web.
The AI did not write the check because nothing in the prompt demanded it. "Make the invoice page work" is satisfied the moment the page works for you. The missing line is the one that says: this invoice belongs to this account, or return 403. A human reviewer who has been burned before adds it by reflex. An AI building the happy path does not.
Why your end-to-end tests miss it
Your click-driven tests walk the app the way a user does. They log in, they land on the dashboard, they open their own invoice. The link in the UI points at their own ID, so the test follows it and passes. Everything is green.
The hole lives at a URL the frontend never renders. There is no button that links to invoice 43 when you are logged in as the owner of invoice 42. Your test harness only knows about the URLs the app shows it. It will never type a stranger's ID into the address bar, because no user flow ever does. The bug is invisible to a test that only knows how to click.
Finding IDOR means doing something the UI refuses to: take a request the app already made, change one ID, and send it again as a different user. That is a server-trust question, and it lives below the buttons.
Replay the real calls with mutated IDs
Hover is a free, open-source MCP server you add to the coding agent you already run, and access-control testing is built for exactly this. It runs a local HTTPS man-in-the-middle proxy in front of your dev browser, so the agent sees the real API calls your app makes, headers and bodies and all.
From there the agent does what your test suite cannot. It takes a captured request, mutates the object ID, and replays it. If the server returns invoice 43 to the account that owns invoice 42, that is a confirmed access-control finding, not a guess. The agent saw the real call, changed one value, and watched the server leak.
A confirmed finding does not stay a chat message. Hover crystallizes it into a .api-test.spec.ts regression gate, a plain spec that asserts the mutated request gets rejected. That gate runs in CI with no agent in the loop. Once you patch the missing check, the gate stays green. If someone reintroduces the hole later, the gate goes red on the pull request. The agent found it once, and the spec guards it from then on.
One agent, no separate tool
Access-control testing runs through the same agent you already use to author tests. There is no separate tool to install, no bundler plugin, no second account. You go from authoring a feature test to probing access control against the same dev browser — and every confirmed finding lands as an .api-test.spec.ts gate that keeps running in CI, long after the session that found it.
Where this leaves you
Vibe-coding moved the cost. Writing the feature is cheap now. Knowing it is safe is still work, and the AI that built the happy path is the last thing you should trust to find the hole it skipped. The fix is to let an agent do the part the UI hides: replay the real traffic, change the ID, and turn what it finds into a gate that outlives the session.
Try Hover on your own app.
Add Hover’s MCP to the coding agent you already run. It explores your app and crystallizes plain Playwright specs you own.
npm i -g @hover-dev/mcp && claude mcp add hover -- hover-mcpRead the quick start →