Vibe Coding Security Checklist: 12 Checks You Should Actually Run (2026) An August 2026 scan of roughly 31,000 live apps built with Lovable, Bolt, v0 and Replit found that 57% of the reachable Supabase databases returned their data to anyone without a login, according to research published by Reeve. The breaches stem from shipped misconfigurations — open databases, keys exposed to the browser, and routes that never verify the caller — as illustrated by the Moltbook AI social network, where Wiz researchers found the database key in the site's JavaScript and row-level security disabled within four days of its late-January 2026 launch, exposing about 1.5 million API tokens and tens of thousands of email addresses. The report pairs those findings with 12 testable checks, six of which run against the built app or live URL rather than source code, and notes a March 2026 study (Mao et al.) finding AI-written code produced slightly fewer scanner warnings and fewer hardcoded secrets than human-written code. In an August 2026 scan, 57% of the reachable vibe-coded Supabase databases were readable without a login. Twelve checks, each with the command, the failing result, and the cost of the fix. Key Takeaways - A scan of live vibe-coded apps in August 2026 found that 57% of the Supabase databases it could test were readable without a login . - The usual causes are an open database, a key shipped to the browser, or a route that never checks who is asking, and all three are misconfigurations you can test for. - Each of the 12 checks below gives you a command, shows what a failing result looks like, and says how long the fix takes. - Half of the checks run against the built app or the live URL, because a code scanner cannot see an open database. - A coding agent with a terminal can run ten of the twelve for you the prompt is below , and three of them belong in CI. In August 2026 a researcher ran automated checks against about 31,000 live apps built with Lovable, Bolt, v0 and Replit. Of the Supabase databases the checks could reach, 57% returned their data to anyone who asked, with no login Reeve, August 2026 https://reeve.page/research/vibe-coded-app-security-2026 . Those databases were open to the internet by default, and nobody had switched that off. The first time I shipped something vibe-coded, I fixed an “undefined” error at midnight by putting NEXT PUBLIC in front of a Stripe secret key. The error went away, and so did the secrecy of the key: it sat in plain text inside .next/static, in every browser that loaded my app, for nine days. Then I ran check 2 below and a single grep printed it straight back at me. Rotating the key took four minutes; moving the one call that needed it into a server route took another hour. What I took from it is that the checks that matter run against the built app, not the source code I had been rereading. Twelve checks follow. Each one gives you the command, what a failing result looks like, and how long the fix takes, and six of the twelve run against your built app or your live URL instead of your source code, because that is where the breaches in this post happened. The code that AI writes is roughly as safe as the code people write. A March 2026 study compared tens of thousands of AI-written code units in real repositories with human-written ones and found the AI code set off slightly fewer scanner warnings and contained fewer hardcoded secrets Mao et al., March 2026 https://arxiv.org/abs/2603.27130 . The breaches come from what the app ships with. Moltbook is the clearest example this year. It was an AI social network launched in late January 2026 by a founder who said he “didn’t write one line of code.” Within four days, researchers at Wiz found the database key in the site’s JavaScript and row-level security turned off, so anyone could read and write the whole database. That exposed about 1.5 million API tokens along with tens of thousands of email addresses Wiz, February 2026 https://www.wiz.io/blog/exposed-moltbook-database-reveals-millions-of-api-keys . The same month, an exam platform that Lovable had featured on its own Discover page leaked 18,697 user records. The app’s login check was written backwards, the database had no row-level security, and nothing checked which role a user had. The researcher who found it called the main bug “a classic logic inversion that a human security reviewer would catch in seconds” The Register, February 2026 https://www.theregister.com/2026/02/27/lovable app vulnerabilities/ . Two months later the platform itself had a flaw that let any free account read other users’ projects https://vibeready.sh/blog/ai-code-quality-2026/?utm source=medium&utm medium=syndication&utm campaign=vibe-coding-security-checklist when-it-got-real . Those stories share one cause: getting an app in front of users used to mean configuring authentication yourself, because nothing worked until you did. AI tools removed that step, so the app keeps running on its default settings, and the default for a new database is open. That is why this checklist spends more time on your deployed app than on your source code. Run all twelve before your first real signup, again before you take a payment, and once a month after launch. You do not have to run them by hand: the prompt at the end hands the whole list to your coding agent and gets you a report. The commands assume a Next.js App Router project, npm, and a Unix shell Windows: Git Bash or WSL . Checks 3 to 5 are Supabase-specific, because Supabase is what Lovable, Bolt and most vibe-coding tools hand you, and open Supabase tables are the failure behind the opening scan. If your database sits behind your own API Prisma, Drizzle, Neon , skip them and lean on check 7; on Firebase the equivalent failure is a Firestore rule that allows reads to everyone. Checks 6 and 7 use Clerk’s names, with Auth.js, Better Auth and Supabase Auth in the table under Authorization; other package managers are in check 8, Vite in check 2. Values such as app.example.com, abcdefghijkl.supabase.co, the keys and the ids are examples; use your own, and take request URLs and cookies from DevTools Network, right-click a request, Copy as cURL instead of typing them. If your app lives inside Lovable, export it to GitHub first, because seven of the checks read the code. Four scanners are separate installs; Homebrew has all of them, and Linux and Windows get release binaries from each project. brew install gitleaks trufflehog osv-scanner semgrep Most of the damage in this post lives outside the source code: a database setting, a key that ended up in a deployed file, a route that answers the wrong user. A scanner that reads your repo cannot see any of that. That is why six of the twelve checks talk to the built bundle or the live URL, and why the code scan comes last. Keys leak in three places: your git history, the JavaScript you send to the browser, and the wrong kind of database key. The third is Supabase-specific. AI assistance makes this worse. GitGuardian’s 2026 report found that commits written with Claude Code leaked a secret about twice as often as the average public commit GitGuardian, March 2026 https://blog.gitguardian.com/the-state-of-secrets-sprawl-2026/ . The same report found that most keys leaked years ago still worked, because nobody had rotated them. So the fix for every check in this section starts the same way: rotate the key first, then clean up. gitleaks git . --redact -vtrufflehog git file://. --results=verified --fail Fails when: gitleaks prints a finding and exits with code 1. The older detect subcommand is deprecated. The trufflehog line narrows the list to keys the provider confirms still work. Cost: the tools are free. Rotating a key takes a few minutes. Rewriting history to remove it takes about an hour, and rotation matters more, because a key in a public commit has already been copied. npm run build && grep -rIoE \ -e 'sb secret A-Za-z0-9 - +' -e 'sk live|test A-Za-z0-9 +' \ -e 'sk- A-Za-z0-9 - {20,}' -e 'postgres ql ?:// ^:/ +: ^@ +@' \ .next/static | sort -u Fails when: anything prints. Next.js copies every variable that starts with NEXT PUBLIC into the browser bundle at build time, as plain text. So when someone fixes an "undefined" error by adding the prefix to STRIPE SECRET KEY, the key becomes public. The patterns cover Supabase secret keys, Stripe and Clerk live and test keys, OpenAI keys, and database URLs with a password in them. On Vite the folder is dist/assets and only VITE values are inlined. Cost: free. Rotate the key and move the code that needed it into a server route, about an hour. grep -rhoE 'sb publishable|secret A-Za-z0-9 - +' .next/static | sort -ugrep -rhoE 'eyJ A-Za-z0-9 - {10,}\.eyJ A-Za-z0-9 - {10,}' .next/static \ | sort -u decode the middle part of each token the second grep printednode -e 'console.log Buffer.from process.argv 1 .split "." 1 , "base64url" .toString ' eyJhbGciOi... Fails when: the first grep finds an sb secret key, or the decoded payload of any token in the bundle says "role":"service role". Supabase has two kinds of key. The publishable key formerly called anon is meant for browsers and is limited by row-level security. The secret key formerly service role ignores row-level security entirely, so shipping it hands out your whole database. The older keys are tokens JWTs : the second grep lists any in the bundle, and the node line decodes the middle part of one so you can read its role. Cost: free. Rotate it in the dashboard and redeploy, about half an hour. It is cheaper to stop a key from being written than to rotate it later. If you use Claude Code, a ten-line hook https://vibeready.sh/blog/claude-code-primitives/?utm source=medium&utm medium=syndication&utm campaign=vibe-coding-security-checklist hooks-vs-skills refuses edits to .env before they happen. In a Supabase app the browser talks to the database directly, so the database has to decide who may read which row. That job belongs to row-level security RLS , and it is off by default on every table you create in SQL. An open table is what the 57% in the opening scan measured. select tablename, rowsecurityfrom pg tableswhere schemaname = 'public'order by rowsecurity, tablename; Fails when: any row shows rowsecurity = f. That table can be read and written by anyone who holds your publishable key, which is everyone, because the key is in the browser bundle. Then open Security Advisor https://supabase.com/docs/guides/database/database-advisors in the Supabase dashboard and act on 0013 rls disabled in public, 0010 security definer view, 0002 auth users exposed and 0024 permissive rls policy a policy that says USING true first. Cost: free. Writing policies takes ten to thirty minutes per table once you have done the first one. curl "https://abcdefghijkl.supabase.co/rest/v1/orders?select= &limit=5" \ -H "apikey: sb publishable AbC123..." Fails when: real rows print. Your project URL and publishable key are under Settings, then API, in the Supabase dashboard, and the table is any of yours. An empty array on a table you know has rows means RLS is working, and an error object with code 42501 means the anonymous role has no access to that table at all, which is also fine. Repeat for every table that holds user data; this is the same request that found Moltbook’s database and the open databases in the Reeve scan. Cost: the fix is the same as check 4. If you use Prisma behind your own API routes, RLS does not apply and these two checks pass on their own. Your version is check 7. The Lovable-specific failures, including a disputed 2025 vulnerability report a CVE , are covered in where Lovable hits the wall https://vibeready.sh/blog/why-ai-app-builders-hit-a-wall/?utm source=medium&utm medium=syndication&utm campaign=vibe-coding-security-checklist lovable-limitations . Broken access control is the top item on the OWASP Top 10:2025 https://top10.owasp.org/2025/ . In plain terms, the app checks that you are logged in and then hands you a record without checking that it is yours. That was the bug in both Lovable incidents this year. find app src/app -name 'route. ' 2 /dev/null | wc -lgrep -rlE 'auth\ \ |protect\ |getSession\ |getClaims\ |getUser\ ' \ --include='route. ' app src/app 2 /dev/null | wc -l Only for apps with their own API routes. If your app talks to Supabase directly, its authorization is RLS, and checks 4, 5 and 7 cover it. Fails when: the second number is smaller than the first by more than your auth provider’s own catch-all route and your webhook receivers, which verify a signature instead. The table above names the auth call for each provider. On Clerk, clerkMiddleware protects nothing by itself; the docs https://clerk.com/docs/references/nextjs/clerk-middleware say middleware "is not the best place to protect routes" and to check access "as close to the resource as possible, in the code that reads or mutates the data." Cost: free, five to fifteen minutes per route. The plain version needs no terminal. Sign in as user A and open something that belongs to you: an order, a document, a settings page. Then change the id in the address bar to one that belongs to user B, a record you created with your second test account. If the page loads, you failed. The commands below are the same test from a terminal. For a Supabase app, run the check 5 request again with user A’s access token, which you find in DevTools under Application, Local Storage, the sb-…-auth-token entry. For an app with its own API, open Network in DevTools, right-click the request that loaded your record, choose Copy as cURL, swap the id for user B's, and add -si | head -1 to print only the status line. Supabase apps: the same request as check 5, signed in as user Acurl "https://abcdefghijkl.supabase.co/rest/v1/orders?select= " \ -H "apikey: sb publishable AbC123..." \ -H "Authorization: Bearer eyJhbGciOi..." own-API apps: the request DevTools copied, with the id swapped for user B'scurl -si 'https://app.example.com/api/orders/9f2c' \ -H 'cookie: session=eyJhbGciOi...' | head -1 Fails when: the Supabase request prints rows that belong to other users, or the API request comes back 200 for user B's id. You want an empty array, a 403 or a 404. Run the API version once with your own id first: it must return 200, or the cookie is not being sent, and a request that always fails looks exactly like a pass. Clerk's cookie expires about 60 seconds after you copy it; the cookie name for each provider is in the table above. Cost: free. Add a condition to each query so it also matches the caller's organization, for example where: { id, organizationId }. That is twenty to sixty minutes across a small app, and it is the fix AI tools skip most often, because nothing visibly breaks when it is missing. Your package.json can hold two kinds of problem: packages with known vulnerabilities, and packages that were never real. npm audit --audit-level=high --omit=devosv-scanner scan source -r . Fails when: the exit code is non-zero and the table shows high or critical rows. The --omit=dev flag the current spelling of --production keeps build-tool noise out of the report. The other package managers have the same audit, in the table below; yarn needs -R or it audits direct dependencies only. The osv-scanner line catches what npm audit cannot see: packages from other ecosystems and advisories npm has not mirrored. Cost: free, ten to sixty minutes. Run npm audit fix first. Treat --force as a major version bump, because that is what it does. js node -p "Object.keys require './package.json' .dependencies .join '\n' " \ | while read -r p; do printf '%-40s ' "$p"; npm view "$p" time.created; donenpm query ':attr scripts, postinstall , :attr scripts, preinstall ' \ | grep '"name":' Fails when: a dependency was created only weeks ago, has one or two versions, and lists no repository. Or when the second command names a package you do not recognize, because a script that runs at install time is how a fake package gets to run code on your machine. pnpm 10 and bun block install scripts by default; pnpm ignored-builds and bun's trustedDependencies list are the allow-lists to check. AI models invent package names, and they invent the same wrong names again and again: a USENIX Security 2025 study found that commercial models invented a package name about one time in twenty, and open-source models about one time in five Spracklen et al., 2025 https://arxiv.org/abs/2406.10279 . That was measured on 2024-era models and has not been repeated. Attackers register those invented names and wait, a move called slopsquatting : in February 2026 npm put unused-imports, a name models produce when they mean eslint-plugin-unused-imports, under a security hold after people had been installing it Aikido, February 2026 https://www.aikido.dev/blog/slopsquatting-ai-package-hallucination-attacks . Cost: free. A few minutes per suspect package, and npm ci --ignore-scripts in CI so the next fake one cannot run on install. The next two checks need no account, which is why an attacker tries them first. The last one is the code scanner. for i in $ seq 1 40 ; do curl -s -o /dev/null -w "%{http code} " \ 'https://app.example.com/api/generate' \ -H 'content-type: application/json' -d '{"prompt":"hi"}'done; echo Fails when: all forty responses come back and none of them is a 429, the "too many requests" status. Click the expensive button in your app generate, send, export , find that request in DevTools under Network, Copy as cURL, and paste it in place of the curl inside the loop; the -w flag prints only each status code. Run it against a preview deployment, never production. Sign-in is usually limited by the provider already Clerk hosts it on its own servers, so a Clerk app has no sign-in route to test ; Auth.js is the exception, because it ships no limiter. Cost: a free Upstash tier or your host's firewall rules, and thirty to sixty minutes. for p in /.env /.env.local /.git/config; do printf '%s ' "$p" curl -s -o /dev/null -w "%{http code}\n" "https://yourapp.com$p"donecurl -sI https://yourapp.com | grep -iE \ -e 'strict-transport-security|content-security-policy' \ -e 'x-frame-options|x-content-type-options'find .next/static -name ' .map' | head Fails when: a dotfile returns 200, the header grep prints nothing, or the source-map listing shows files. The dotfile probes only matter if you host the app yourself with the repo as the web root. Vercel adds the HSTS header itself, so the other three prove your headers block is live. A source map hands over your unminified code; about one vibe-coded app in eight in the Reeve scan served them. Cost: free. Twenty to forty minutes for a headers block in next.config.js and productionBrowserSourceMaps: false. semgrep scan --config p/owasp-top-ten --config p/secrets --error . Fails when: it exits 1 with any finding. The p/owasp-top-ten ruleset follows the same categories this post is organized around, and p/secrets overlaps check 1 for anything still in the working tree. Do not add a severity filter; most of the JavaScript rules in the OWASP set are warnings. Cost: free. Budget one to four hours to go through the first run. After that it is a CI step that only speaks up about new code. Bookmark this table. It lists the same twelve checks with the failing result and the cost side by side. You do not have to run these by hand. Ten of the twelve are shell commands, so a coding agent with a terminal Claude Code, Cursor, Codex can run them and hand you a report. Check 4 works through the official Supabase MCP server, whose get advisors tool returns the Security Advisor findings. Check 7 needs two test accounts you create first. Check 10 stays with you, because nobody should let an agent choose which URL to hit forty times. Paste this into the agent from the repo root, with a preview URL, and read the report before you fix anything: bash ~/my-app $ claude Run the twelve checks from https://vibeready.sh/blog/vibe-coding-security-checklist/ against this repo and the preview deployment at