Free security self-check

Vibe-coded app security checklist: 24 checks in about 10 minutes.

For founders who built an app with Lovable, Replit, Cursor or Bolt and want to know what a stranger could reach. Each check says why it matters and how to test it yourself.

Last reviewed 2026-10-27. Nothing you tick leaves your browser.

How to use it

Work through the five groups below with your live site open next to you. Tick a box only after you have tested it, not because you think the AI tool probably did it. Items marked critical come first in the summary. Some checks need a terminal, a browser and two test accounts, and a few need a look at your Supabase, Stripe or host dashboard.

The checklist

ExcenityAI · excenityai.com/tools/vibe-coded-app-security-checklist

Vibe-coded app security self-check · last reviewed 27 October 2026

0 of 24 checked

Ticks are saved in this browser only. Nothing is sent anywhere.

Critical checks still open

10 of 10 critical checks are not ticked yet. Start here.

Secrets & keys

What a stranger can copy out of your site, your repo or your chat history.

critical

Anything in your JavaScript bundle is public, including variables prefixed NEXT_PUBLIC_ or VITE_. A secret there is a secret for everyone.

How to check: Open your live site, then DevTools, Sources and Network. Search all files for sk-, sk-ant-, sk_live, sb_secret_, service_role and AIza. Publishable keys (Supabase publishable key sb_publishable_ or the legacy anon key, Stripe pk_) are meant to be public and are not a finding.

critical

Apps generated in Lovable, Bolt or Cursor sometimes call the model API straight from the browser, which puts your key in every visitor's hands.

How to check: With the Network tab open, use your AI feature. Requests to api.openai.com or api.anthropic.com from the browser mean the key is exposed. You should see calls to your own route or Edge Function instead.

high

A key committed once stays in git history after you delete the file. AI assistants will also write a real key into a file or a code comment if you paste one into a prompt.

How to check: Run git ls-files | grep -i env and git log --all --oneline -- .env. Search the repo for key prefixes. On Replit, keys belong in the Secrets tool rather than in files. In Cursor, a .cursorignore entry keeps .env out of what the assistant reads.

critical

Removing a leaked key from the code does not make it safe. Anyone who copied it can still use it until you revoke it.

How to check: List every key you pasted into a chat prompt, committed, screenshotted or shared. For each, open the provider dashboard (OpenAI, Anthropic, Stripe, Supabase), revoke it, create a new one and update your host's environment variables.

high

A leaked Stripe secret key can read customer data and move money. An unsigned webhook endpoint lets anyone fake a successful payment.

How to check: Only the pk_ key should appear in browser code. Server code should use a restricted key (rk_) with only the permissions it needs. Confirm your webhook handler verifies the Stripe-Signature header, for example with stripe.webhooks.constructEvent, and rejects requests that fail.

Auth & access

Who can call your backend, and what they can reach once they do.

critical

A button you hide in the interface protects nothing. The route behind it still answers anyone who calls it directly.

How to check: From the Network tab, copy each API request as cURL, remove the cookie or Authorization header and run it. You want 401 or 403, not data. For Supabase Edge Functions, the verify_jwt setting alone is not proof of a logged-in user, because the project's API keys can also get a request through. Confirm each function verifies the caller itself, for example with getUser or getClaims on the Authorization header, or verifies something else such as a webhook signature or a key checked in code.

critical

AI-generated code often checks that you are logged in but not that the record is yours, so changing an ID in a request returns someone else's data.

How to check: Create two test accounts. As A, copy a request that loads A's record, then replay it with B's session or B's record ID. Repeat for update and delete. Any success is a broken permission check.

high

If a role is stored somewhere the user can change it, such as localStorage, a request body or Supabase `user_metadata`, anyone can promote themselves.

How to check: Search for where your code decides who is an admin. It should read a database table or Supabase app_metadata, checked on the server or in an RLS policy. Then try an admin-only action as a normal user and confirm it fails.

high

A loose redirect allow-list can send users, with their login token, to a page an attacker controls after sign-in or password reset.

How to check: In Supabase, open Authentication, then URL Configuration. The Site URL should be your production domain, and Redirect URLs should list only addresses you use. Remove stray wildcards, old preview URLs and localhost entries you no longer need. Check the same setting in any other auth provider you use.

medium

An app meant for invited users that still allows open sign-up, or skips email confirmation, lets strangers create accounts and reach whatever logged-in users can.

How to check: Try to sign up with a throwaway email in a private window. If the app is invite-only it should refuse. In Supabase, check Authentication settings for whether new sign-ups are allowed and whether email confirmation is required.

Database & RLS

Row Level Security, grants and storage, tested from outside.

critical

Supabase exposes your tables through an API that the browser calls with the publishable (or legacy anon) key. Without RLS, that key reads and writes the table.

How to check: Run select tablename, rowsecurity from pg_tables where schemaname = 'public'; in the SQL editor. Every row should say true. The Table Editor also flags tables without RLS. Lovable and Bolt projects on Supabase are not exempt, so check the tables the AI created last.

critical

A policy that always passes turns RLS on in name only. It looks fine in a quick test because everything still works.

How to check: Run select tablename, policyname, roles, cmd, qual, with_check from pg_policies where schemaname = 'public';. Look for true in qual or with_check, a missing to authenticated role, and any policy that applies to anon on a private table.

critical

This is the same request any visitor can make using the key in your frontend. It shows what is actually exposed, whatever the settings say.

How to check: Run curl "https://<project>.supabase.co/rest/v1/<table>?select=*" -H "apikey: <publishable or anon key>" for each table that holds user data. Expect an empty list or an error. Try an insert and an update too.

high

Views run with their owner's rights by default and can bypass your policies. New tables in the public schema can also start with broad privileges for the anon and authenticated roles.

How to check: On Postgres 15 and later, create views with security_invoker = true. Review grants with select grantee, table_name, privilege_type from information_schema.role_table_grants where table_schema = 'public' and grantee in ('anon', 'authenticated'); and revoke what the app does not need.

high

IDs, invoices and private photos in a public bucket are readable by anyone who finds or guesses the URL.

How to check: In Supabase Storage, list each bucket and its policies. Open one private file in a private window with no login. For Firebase, confirm Storage and Firestore rules are not in test mode. For S3, check Block Public Access. Serve private files through short-lived signed URLs.

Cost controls

Limits that stop one bug or one abuser from running up a bill.

critical

Alerts tell you about a runaway bill after the fact. A hard limit makes requests fail once the amount is reached.

How to check: OpenAI: in Project settings (or Organization limits), open Spend, choose Edit spend limit, enter a monthly amount and turn on Enforce a hard limit; without that toggle you only get alerts. Requests then fail with a 429 error at the limit. Anthropic: in the Claude Console, set a spend limit on the workspace the app uses (the default workspace cannot take one). Use a separate project or workspace per app.

critical

An open endpoint that calls a paid model is a free API for anyone who finds it, and a bot can drain a budget in hours.

How to check: Call each AI route logged out and expect 401. Then, logged in, send 30 to 50 requests in a loop. You should start getting 429 responses or a clear refusal. If nothing limits it, add per-user and per-IP limits.

medium

One request with a huge prompt or an unbounded reply, repeated by a loop, costs far more than a normal one.

How to check: Search your code for max_tokens (or max_output_tokens, max_completion_tokens, depending on the API) on every model call. Check that the chat history sent each time is trimmed, and that user input has a length limit.

high

Most budgets only notify you. Without one, the first sign of a problem is the invoice.

How to check: Vercel: on a Pro plan, set an On-Demand Budget in Spend Management and decide whether to turn on Pause Production Deployments. AWS: create an AWS Budgets cost budget. Google Cloud: set a billing budget and restrict each API key to the APIs it needs. Treat all of these as alerts, not caps, unless the provider says otherwise.

Hosting & operations

Environments, accounts, backups and knowing when something breaks.

high

Preview URLs are easy to share and sometimes public. If they use live keys or the live database, a leak there is a production leak.

How to check: Vercel and Netlify let you scope each environment variable to Production, Preview or Development (Netlify calls them deploy contexts). Check that live Stripe, AI and database keys are production only, and that previews are behind your host's deployment protection or use test data.

medium

Source maps hand over your original code, comments included. Forgotten test pages, seed routes and admin panels are common in generated apps.

How to check: In DevTools, look for .map files in the Network tab and try opening one. Browse your route list for /admin, /debug, /test and /api/seed, and request each on the live site while logged out.

medium

A broken checkout or a spike of failing requests is not something users reliably report. Logs nobody reads do not help during an incident.

How to check: Trigger a deliberate error in production, for example call an API route with bad input. Confirm it appears in your host's logs and that someone is notified by email or chat, through your host or a tool such as Sentry.

high

AI tools can regenerate a migration that drops a table. An untested backup is an assumption, not a safety net.

How to check: Find out what your database plan includes, since automatic backups differ by plan and provider. Then do one restore into a scratch project and confirm the data is there. Write down the steps.

high

A stolen password for your host, GitHub, database or AI provider gives an attacker everything above in one step.

How to check: Check security settings on GitHub, your host (Vercel, Netlify or Replit), Supabase, Stripe and each AI provider. Remove people who no longer need access and write down where each key would be rotated.

If you found gaps

Fix critical items first, then re-test the same way you found them. These guides go deeper:

Prefer a second pair of eyes? The free 30-minute audit call reviews these same areas with your app on screen.

FAQ

Using the checklist.

What does critical mean here?

A critical check is one where a failure can expose user data, let a stranger act as someone else, or run up a large bill without any special skill. We set the labels by how bad and how easy the failure is. It is our judgment, not a formal scoring system.

Does ticking every box mean my app is secure?

No. It means you have checked the most common gaps in apps built with AI tools. It does not replace a code review or a penetration test, and it can only be as accurate as your own testing.

Is anything I tick sent to ExcenityAI?

No. Your ticks are stored in your own browser's local storage so they survive a refresh. Nothing is uploaded, and the page works if storage is blocked, you just lose the saved ticks.

How do I save a copy?

Use the Download / print PDF button and pick Save as PDF in your browser's print dialog. The printout hides the site menu and shows every check with its how-to-check steps.

I use Firebase or another backend, not Supabase. Does it apply?

Most checks do: keys, logged-out requests, two-account testing, storage, spend limits and backups. The Supabase RLS items map to Firebase Security Rules or the equivalent access rules in your database.

How often should I run it?

Before launch, and again after any large change from your AI tool, such as a new table, a new payment flow or a new AI feature. Regenerated code can drop a policy or add a key to the frontend.

Not sure what you found? We'll look with you.

A free 30-minute review of your AI-built app, then a written fix plan and a quote.