What should you check before shipping your vibe coded app?
The short answer
Eight checks, ordered by how much damage skipping them causes. Move every secret out of your code into environment variables, and rotate any key that ever touched a repo or a prompt. Turn on row level security for every database table, then test it logged out. Validate input on the server for anything that writes. Put rate limits on anything that costs money per call, AI endpoints first. Set up one channel that tells you when the app breaks. Walk the payment path and the sign-up path in production, on a phone, as a brand-new user. Know how to roll back your last deploy before you need to. Publish a privacy page that tells the truth. None of this requires a security background. It’s the small share of the work that prevents most of the disasters that hit vibe coded apps, and the top two risks on OWASP’s own list, broken access control and misconfiguration, are exactly what it targets.
The whole list, ordered by how much a miss costs
You can’t run a full security audit alone, and you don’t need to. What you can do is close the specific doors that account for most real incidents. OWASP’s Top 10 for 2025, the field’s standard ranking of web application risks, puts broken access control at number one and security misconfiguration at number two. In a vibe coded app, those abstractions usually mean something concrete: a database rule left off, or a key left in the code. Work the list top to bottom.
| Check | What skipping it has cost people |
|---|---|
| Secrets in environment variables, leaked ones rotated | Strangers spending on your Stripe or OpenAI account within hours |
| Row level security on every table, tested as a stranger | Any visitor reads, or edits, your whole user table |
| Server-side validation on anything that writes | Hostile or garbage input lands straight in your database |
| Rate limits on endpoints that cost money | A bot turns your AI endpoint into its free API, on your card |
| One error channel you already read | The app breaks quietly and users find out before you do |
| Money and sign-up paths tested live, on a phone | You launch with a checkout nobody can complete |
| A rollback you’ve rehearsed once | A broken deploy stays live while you search for the undo |
| A privacy page that tells the truth | Smaller than the rest, but trust is slow to win back |
Be honest with yourself about the boundary, too. Whether your agent introduced a subtle logic flaw, or a dependency ships a vulnerability, you can’t verify alone in an evening. The eight above, you can.
Get every secret out of the code, then rotate the ones that already escaped
A secret is anything that grants access to whoever knows it: API keys, database passwords, tokens, the service role key Supabase warns you about. Secrets belong in environment variables, never in the code itself, so your files can be committed, shared, and read by your agent without carrying the keys to your accounts inside them. Before you ship, search the project for sk_, secret, password, and key. If a real value turns up in a file, move it.
Then comes the step people skip: rotation. If a key was ever pasted into a public repository, a prompt, a screenshot, or a Discord message, treat it as leaked, even if you deleted it right after. Deleting the line doesn’t remove it from git history, and automated scanners watch public GitHub around the clock, so exposed keys get tried within minutes, not days. Rotating means opening the provider’s dashboard, revoking the old key, and issuing a fresh one. Stripe’s go-live checklist says exactly this: rotate any key that was stored outside your codebase during development. While you’re in those dashboards, shrink what each key can do. A key restricted to reading, scoped to one project, is least privilege in practice, and it turns a future leak from a catastrophe into an annoyance.
Turn on the row rules, then try to break in as a stranger
If your app runs on Supabase, the docs are blunt: row level security “must always be enabled on any tables stored in an exposed schema,” which by default means every table in public. Row-level security is the database itself deciding, row by row, who may read or change what. Without it, the publishable key that ships in the browser to every visitor can read any table it reaches.
Here’s the trap that catches vibe coders specifically. Tables created through the Supabase dashboard get RLS enabled automatically. Tables created through SQL don’t, and SQL is exactly how your agent creates tables when it runs a migration. An app built end to end by an agent can ship with every table open even though you never chose that.
Run two checks. First, open the Security Advisor in your Supabase dashboard; it flags every table in public with RLS disabled, along with tables that have RLS on but no policies. Second, test as a stranger: open the live app in a private window, logged out, and try to reach another person’s data. Better still, create a second account and confirm user A can’t read user B’s rows. One reassurance for when you flip the switch: enabling RLS with no policies written locks the table completely, so if parts of your app suddenly show nothing, that’s failure in the safe direction. Write policies to open exactly what should be open, and nothing else.
Validate on the server, and put a ceiling on anything that costs money
Every check that happens in the browser is a courtesy, nothing more. Anyone can skip your forms entirely and send requests straight to your server with a single curl command. So any endpoint that writes data needs input validation on the server: is this field the right type, a sane length, one of the allowed values? Your agent will build this well if you ask for it. The asking is the step that gets missed. Injection, the classic attack where crafted input gets executed instead of stored, still holds fifth place on the 2025 OWASP Top 10, partly because apps keep trusting input the browser already “checked.”
A rate limit is the same idea applied to your wallet. Any endpoint that costs you money per call needs one, and AI endpoints come first, because every request bills your account. A bot that finds an unmetered AI endpoint has found a free language model with your card behind it, and it will not use it politely. Sign-up and email-sending endpoints come next, since both can be abused for spam under your name. Ask your agent for a per-user and per-IP cap on each paid endpoint, plus one global daily ceiling that no amount of traffic can push past. The ceiling is the part that lets you sleep.
Give the app one way to tell you it’s broken
Logging and alerting failures sit at number nine on the OWASP Top 10 for a reason most rankings don’t capture: silence feels like success. An app that throws errors nobody sees looks exactly like an app that works, right up until a user emails you, annoyed, about something that broke days ago.
You need one channel, and only one, that you already look at. Your host’s function logs can be enough to start: on Vercel, open your project and read the Logs tab the morning after launch. A free error-tracking account, Sentry is the common choice, goes further by emailing you when a new kind of error appears, so you hear about the 2 a.m. checkout failure at breakfast instead of next week. Resist building a monitoring stack. The bar to clear is a single honest answer to one question: if the app broke right now, would anything tell you?
Walk the money path and the sign-up path in production, on your phone
Stripe’s live mode is a separate world from its sandbox. Webhook endpoints you registered for testing don’t carry over; register the live ones. Products and coupons created in the sandbox don’t exist in live mode; recreate them. Keys are different too, which is why this test comes after your secrets are sorted.
While you’re still in the sandbox, run both branches: the success path with test card 4242 4242 4242 4242, and the decline path with 4000 0000 0000 0002, so you know what a failed payment looks like to your user. Those cards work only in test mode. After you deploy, the honest test is a real one. On your phone, in a private window, sign up with a fresh email, buy the cheapest thing in your app with your own card, then refund yourself in the Stripe dashboard. The phone matters because a checkout that looks fine in desktop dev tools can be unusable on a small screen, and a large share of your first visitors will arrive on one. Stripe’s own checklist suggests having someone who isn’t the developer run through the flow. A friend with a phone qualifies.
Know your undo, and tell the truth about data
Find the undo before you need it, while you’re calm. On Vercel, the project overview has an Instant Rollback button that points your domain back at a previous production deployment; the Hobby plan lets you return to the deployment immediately before, paid plans reach further back. One catch worth knowing ahead of the panic moment: after a rollback, new pushes still build, but Vercel stops putting them live until you promote a deployment again, so a rollback is a pause, and you have to un-pause. If your host offers nothing similar, a git revert of the bad commit plus a redeploy does the same job. Rehearse whichever path applies to you once, today. Shipping feels different when you know the way back.
The privacy page takes twenty minutes and closes the list. Write what’s true for your app: what you collect, which third parties see it (Stripe for payments, Supabase for data, your AI provider for prompts), and what you’d delete if someone asked. A short page in your own words beats a copied template that claims things your app doesn’t do, because the template’s false claims become your false claims the moment you publish them.
What Glim would tell you
Glimfly is building Glim, a firefly that sits beside your coding agent and explains, in plain language, what just changed and what to check before it goes live. A list like this one is the habit Glim exists to grow: knowing what your app does before strangers meet it. So run the eight checks tonight, write down the ones you couldn’t finish, and hand your agent that list one item at a time, asking it to explain each fix as it goes. Every term above has a plain-language entry in the dictionary for when the explanation moves too fast. And if you’d like Glim watching the terminal with you on your next deploy, the waitlist is open.
People also ask
“I pushed an API key to GitHub and deleted it a minute later. Am I safe?”
No. Automated scanners watch public repositories and try leaked keys within minutes, and the key is still in your git history after you delete the line. Go to the provider's dashboard (Stripe, OpenAI, Supabase), revoke that key, and create a new one, then store it in an environment variable instead of a file. Deleting the code only hides the secret from view; rotating it is what makes the copied version worthless.
“How do I check that strangers can't read my Supabase data?”
Open the Security Advisor in your Supabase dashboard: it flags every table in the public schema with row level security disabled, which is the most common hole in vibe coded apps. Then test like an outsider: open your live app in a private window while logged out and try to reach another person's data, and create a second account to confirm one user can't see another's rows. If a table shows nothing at all after you enable RLS, that's expected until you write policies.
“I only have one evening before launch. Which checks matter most?”
Work from the top of the damage-ordered list: search your code for hardcoded keys and rotate anything that ever leaked, confirm row level security is on for every table, and add rate limits to any endpoint that spends money, AI calls first. Then spend the last half hour as a stranger: sign up fresh, buy the cheapest thing in your app on your phone, and refund it. Skip the polish; these are the items where a miss costs real money or your users' data.
Words from this guide
Your API key is exposed: what happens, what to do, how to prevent it
An exposed API key is a secret that leaked somewhere public, and from that moment it works for whoever finds it, not just you.
databasesWhat is RLS (Row Level Security)? The setting that keeps your Supabase data private
Row Level Security is the Postgres rule set that decides, row by row, who can read or write data, and in Supabase it's the only thing standing between your public anon key and your entire database.
securityWhat is input validation? (and why AI code often skips it)
Input validation is the server-side check that incoming data (form fields, URL parameters, API request bodies) has the type and shape you expect before your app stores it or acts on it.
filesWhat is an environment variable? (and why your app has a .env file)
An environment variable is a named value your app reads from its surroundings at runtime, instead of having it hardcoded, so secrets and settings can change without touching a line of code.
webWhat is a rate limit? (why the API told you to slow down)
A rate limit is a cap an external service puts on how many requests you can send it per minute, and going over it gets you a 429 error instead of your data.
gitHow do you undo in vibe coding? Rollback, revert and version history
Rollback, revert, and version history are different ways of getting your project back to a state that worked before the last change broke it.
securityWhat is the principle of least privilege? (why your agent keeps asking permission)
The principle of least privilege means every key and account gets the minimum access it needs to do its job, so one leak or one bad command can't take everything down.
webWhat does deploying actually mean? (from your laptop to the internet)
Deploying is the step where your app stops running only on your own machine and starts running on a server that stays on all the time, at an address anyone can visit.
See this in your own project Glim reads it in plain words.
Paste what your agent just did, a git diff, your terminal, or its summary, and Glim tells you what changed and what to check before you ship. Nothing stored.
Explain my session →