Is your vibe coded app quietly wide open?
When I was speccing the chatbot for this site, I spent longer on one question than on anything else in the build: at what exact moment does a visitor become a record? Not what the record holds. When it gets written. I landed on first interaction, never on page load, and I wrote that into the spec before a line of the thing existed.
That decision is why I sleep fine about it now, and it's the whole answer I'd give any vibe coder worried their app is quietly leaking. The security holes in AI-built apps are almost never exotic. They're default-open. Backends, buckets, and database rows ship permissive until a policy closes them, and the generator will implement what your spec asked for and default everything it didn't. An unstated security property is not a neutral gap. It's an open door with a polite sign on it.
The numbers that made the rounds this week are the same shape. A security firm scanned thousands of live vibe coded apps and turned up more than two thousand high-impact vulnerabilities, several hundred exposed secrets, and well over a hundred instances of exposed personal data. One app that went viral had row-level security never switched on, which meant the public API key in its own frontend was effectively an admin key: millions of auth tokens and tens of thousands of email addresses, sitting there for anyone who thought to ask. Nobody got clever. The door was open from day one and nobody had specified that it shouldn't be.
why do vibe coded apps leak?
Because the fastest path to a working app is the permissive one, and the model is optimizing for a working app.
Think about what the agent is actually doing. You asked for a signup flow. It writes the table, wires the client, and hands you something that works on the first try. Somewhere in that stack there's a default that says "authenticated users can read this table" and nobody, human or model, ever said which rows. The feature works. The tests pass, because the tests check that a logged-in user can read their data, not that a logged-in stranger can't read yours. The green surface is measuring the thing you asked about. It is silent on the thing you didn't. Vibe coding compresses the distance between an idea and a working app, and what gets compressed out is the conversation about what the app must never do.
This is the part I want vibe coders to internalize, because it reframes the whole problem. Your app isn't insecure because the AI wrote bad code. It's insecure because security is a property that has to be stated, and the generator only implements what's stated. Prompting harder does not fix that. Reviewing the diff line by line barely fixes it, because you're looking for something that isn't there. The gap is upstream of the code entirely, which is exactly why spec-first work is not ceremony. The spec is where the security properties get decided, and anything you don't decide there gets decided for you, by a default, in favor of whatever ships fastest.
what do i actually check before it goes live?
Three moves, in this order. They're not a full audit. They're the three that catch most of what I've seen go wrong.
One: data you never store cannot leak. This is the cheapest control in existence and almost nobody reaches for it, because the default instinct in every analytics-shaped feature is to log first and decide later. Go back to the chatbot on this site. The visitor record is written on first interaction, never on page load. That single line in the spec means a person who lands, reads, and leaves is never a row in anything. There is no record to breach, no retention question to answer, no disclosure to make. I did not harden that data. I declined to collect it. Ask of every write in your app: does this need to exist? A surprising number don't, and the ones that don't are free security.
Two: authentication is not authorization. These get conflated constantly, and the conflation is the single most common hole I find in AI-built apps. Authentication is "who are you." Authorization is "what may you read." An endpoint that checks the first and skips the second will happily hand a logged-in stranger every row in the table, and it will do it while passing every test you wrote, because you're logged in when you test it. Row-level policies exist for exactly this. If your app has a users table, a rows table, or anything that belongs to one person and not another, and you cannot point at the policy that enforces that, assume it isn't enforced.
Three: a control that passes its tests can still be silently wrong. This is the one that bites experienced people, so I'll show it rather than assert it. On a build I worked on, e-signature events were logged with the signer's IP for the audit trail. It passed every test. Every entry had an IP. What nobody caught for months was that the app sat behind a reverse proxy that wasn't forwarding the original headers, so every single entry logged the proxy's own IP. The audit trail was full and it was uniformly wrong, and the rate limiter reading the same value was throttling everyone as one visitor. I wrote that one up in full because the shape of it generalizes: the control reported on itself, and it reported success. Verify a security control against reality, never against its own logs.
what breaks with this approach
The naive version of all of this is to open a chat with the agent that built your app and ask it whether the app is secure. You will get a green answer. You will get a confident, well-formatted, reassuring green answer, and it will mean nothing, because the agent is measuring the surface it wrote against the requirements it was given. It cannot see the requirement nobody stated. That is the definition of a blind spot, and asking the blind party for a report on its own blind spot is not a check.
The other failure mode is the opposite overcorrection: a founder reads a headline like this week's, panics, and starts a rewrite. Don't. Default-open failures are usually one policy, one flag, one header, one deleted write. The signature-trail bug that ran wrong for months was a single flag on the proxy config. The fix is almost never a rebuild. The fix is finding the four or five places where a property should have been stated and wasn't, and stating them.
questions that keep coming up
How do I check any of this if I can't read the code? Read the surfaces instead. Open your app's data layer and look at whether any policy is switched on at all, because "off" is the default and it is visible without reading a line of application code. Then open your frontend bundle in the browser and search it for the word "key," because anything your client can see, a stranger can see too. Those two look-ups take ten minutes and catch a large share of what shows up in these scans.
Can I have the agent audit its own app if I give it a good prompt? Partially, and it's worth doing, but change who is asking. Open a fresh session with no memory of writing the code and give it the adversary's job, not the author's: here's the schema and the endpoints, tell me how to read another user's data. That question produces a different answer than "is this secure," because it stops measuring the happy path.
Isn't this just a reason not to build with AI? No. Everything above is a spec problem, not a model problem, and vibe coding is not the variable that decides it. A vibe coder who states the write trigger, the read policy, and the verification method before generating will ship something closed. One who states none of them ships something open, and would have shipped something open by hand too, just slower. The speed didn't create the hole. The silence did.
If you've shipped something with AI and you're not sure what it's exposing, and you'd rather have someone think through the audit with you than guess at it alone, /work-with-us.
// part of the ai project rescue topic
// grab the free starter kit that makes your AI stop forgetting and stop guessing: get it →
// building with AI? the field manual has the structured lessons.
// hitting this on a real build? this is what I fix →