Back to BlogSecurity & Compliance

Vibe-Coded App Security Checklist: 20 Checks Before Launch

Rupak Amin

Founder & Lead Engineer, RAITHub

13 min read

Before launching a vibe-coded app, run 20 pass/fail checks in six areas: data access, secrets, authentication, input handling, platform hardening and recovery. Every check below comes with a test you can run in minutes. Fix every failure in the first two areas before a single real user signs up, because those are the ones that expose other people's data.

"Vibe-coded" here means an app built mostly by prompting an AI tool such as Lovable, Bolt, Cursor or Replit, usually on Supabase or a similar backend. The checks are written for a founder or a developer with a browser, a terminal and an hour. They are pass/fail on purpose: a check you cannot fail is not a check.

Why does a vibe-coded app need its own security checklist?

Because the tools optimise for code that runs, and security failures do not stop code from running. Veracode's 2025 GenAI Code Security Report found that 45% of the AI-generated code samples it tested introduced an OWASP Top 10 vulnerability. The public example for app builders is CVE-2025-48757: Lovable projects whose missing row-level security let anyone read data with the public key. The researcher's statement counts 170 affected projects out of 1,645 analysed.

The list below maps onto the OWASP Top 10:2025, where Broken Access Control is A01 and Security Misconfiguration is A02, but it is ordered by what a generated app usually gets wrong first. Built-in scanners help: Lovable's security features include a scan before publishing and a deeper on-demand scan. Lovable also says these tools "do not replace a thorough security review", and several checks below, such as the second-user test, need a person or a written test. For the wider production picture, including tests, cost and config drift, see how to make a Lovable, Bolt or Cursor app production-ready. This page is the security gate only.

What are the 20 checks?

Checks 1 to 5 cover data access, 6 to 8 secrets, 9 to 12 authentication, 13 to 15 input and output, 16 to 18 platform hardening, and 19 and 20 recovery.

No.CheckHow to test itPass when
1Row-level security is on for every exposed tableSupabase Security Advisor, or query pg_class for public tables where relrowsecurity is falseZero ERROR findings; the query returns no rows
2No write policy is trueList pg_policies; look at insert, update and delete rowsEvery write policy compares the row to the signed-in user or tenant
3User B cannot reach user A's recordsCopy a record ID as A; request it as B in a private window, through the app and the APIB gets nothing, or a 403 or 404
4File storage is private unless meant to be publicList buckets; open a private file's URL while signed outSigned-out request fails; uploads are limited to the user's own folder
5Views do not bypass row-level securitySecurity Advisor "security definer view" findingsNone, or each view uses security_invoker = true
6No secret in the front-end bundleBuild, then search the output for service_role, sb_secret_, sk_live_, sk-No matches; only publishable keys in the browser
7No secret in git historygit grep across git rev-list --all; check the repository's secret scanning alertsNo live secret anywhere in history
8Every paid key has a spending capOpen each provider's billing settingsMonthly caps and alerts set for AI, email, SMS and maps providers
9Email confirmation is on and one-time codes expireSign up with a new address; try logging in before confirmingUnconfirmed accounts cannot sign in; OTP expiry is one hour or less
10Roles are checked on the serverAs a normal user, call an admin endpoint or edit your own role field directlyRefused; roles live where only the server can write them
11Sign-up, sign-in and reset resist botsSubmit the sign-up form 20 times in a minuteCAPTCHA or a rate limit stops it
12The accounts that run the app use MFACheck Supabase, GitHub, hosting, domain registrar and the AI tool itselfEvery admin account has multi-factor authentication
13Every write is validated on the serverCall the API with an empty field, 10,000 characters and an extra "role": "admin"Rejected with a 400; unknown fields ignored or refused
14Errors do not leak internalsBreak a request on purpose and read the responseA plain message; no stack trace, SQL or file path
15User content cannot run as codeSave <img src=x onerror=alert(1)> as a name or comment and view itIt shows as text; no alert box
16Security headers are sentcurl -sI your live URLHSTS, CSP, frame protection, nosniff, Referrer-Policy and Permissions-Policy present
17Login, sign-up, reset and paid endpoints are rate limitedHit each 20 to 50 times in a minuteHTTP 429, or the provider's limit, before the damage is done
18Dependencies have no known high or critical flawsnpm audit --omit=dev in a clean cloneNo high or critical findings, or each one explained in writing
19The database can be rebuilt and restoredRebuild an empty database from migration files; restore a backup to a scratch projectBoth succeed, and you know how long a restore takes
20Security-relevant errors reach a personTrigger a failed login burst and a server errorAn alert arrives somewhere a person reads daily

How do I run the data access checks?

Checks 1 to 5 decide whether one user can see another's data, so they come first. On Supabase, the dashboard's Security Advisor covers most of checks 1, 2 and 5, and its advisors documentation says to rerun it after every fix. The query for check 1:

select c.relname
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind in ('r', 'p') and not c.relrowsecurity;

Check 3 is the one no scanner does for you. It is the insecure direct object reference test, or IDOR: change an ID in a request and see whose data comes back. Do it through the app's own API routes as well as the database API, because a server route that uses the secret key skips row-level security entirely.

For check 4, Supabase's storage access control guide notes that public buckets "are already publicly accessible", and that uploads need a policy. A policy that confines each user to a folder named after their ID:

create policy "Users upload to their own folder"
on storage.objects for insert
to authenticated
with check (
  bucket_id = 'uploads' and
  (storage.foldername(name))[1] = (select auth.jwt() ->> 'sub')
);

How do I check for leaked secrets before launch?

Search the build output, because that is exactly what a visitor downloads. For a Vite-based app, which older Lovable projects are, the output folder is dist. This CI step fails the build if a secret prefix appears in it:

npm run build
if grep -rEq "sb_secret_|service_role|sk_live_|rk_live_|sk-proj-|sk-ant-" dist; then
  echo "Secret found in build output"
  exit 1
fi

Vite's environment variable guide explains why this matters: variables prefixed VITE_ "will be exposed in client-side source code after Vite bundling". The same applies to NEXT_PUBLIC_ in Next.js. For check 7, GitHub's secret scanning scans a repository's entire history on all branches, and runs automatically on public repositories.

Which authentication settings matter most?

Email confirmation, short-lived codes, bot protection and server-side roles. Supabase's production checklist recommends enabling email confirmations, setting OTP expiry to "3600 seconds (1 hour) or lower", offers CAPTCHA on sign-up, sign-in and password reset, and asks you to protect the Supabase account itself with MFA. It also lists a default limit of 2 emails an hour on its email endpoints and recommends a custom SMTP server, which you will need before launch anyway.

Check 10 catches a common generated pattern: a user's role stored somewhere the user can edit, and checked only by hiding a button. Store roles in a table that only server code can write, and check them on the server or in a policy for every privileged action.

Which security headers should the app send?

Six, all recommended by OWASP's HTTP headers cheat sheet. This website sends all six on every response, set in its Next.js config:

HeaderThis site's valueWhat it stops
Strict-Transport-Securitymax-age=63072000; includeSubDomains; preloadDowngrade to plain HTTP, for two years
Content-Security-Policydefault-src 'self', frame-src 'none', object-src 'none', base-uri 'self', form-action 'self', plus named analytics hostsScripts, frames and form posts from places you did not approve
X-Frame-OptionsDENYClickjacking through framing
X-Content-Type-OptionsnosniffBrowsers guessing file types
Referrer-Policystrict-origin-when-cross-originFull URLs leaking to other sites
Permissions-Policycamera=(), microphone=(), geolocation=()Device features the site never uses

An honest note: this site's content security policy still allows 'unsafe-inline' scripts, which OWASP advises against. It is a known gap, not a recommendation to copy.

A generated single-page app hosted on Vercel can send the same headers from a vercel.json file. Replace the Supabase host with your own project's, and add preload to HSTS only once you are sure every subdomain serves HTTPS, because preloading is slow to undo:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains" },
        { "key": "X-Frame-Options", "value": "DENY" },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
        { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" },
        { "key": "Content-Security-Policy", "value": "default-src 'self'; connect-src 'self' https://YOUR_REF.supabase.co wss://YOUR_REF.supabase.co; img-src 'self' data: https://YOUR_REF.supabase.co; frame-ancestors 'none'; object-src 'none'; base-uri 'self'" }
      ]
    }
  ]
}

Then run check 16 against the live URL:

curl -sI https://your-app.example | grep -iE "strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"

Six lines back is a pass. Expect to adjust the CSP once or twice: open the browser console, and each blocked resource names the directive to widen.

What should be rate limited, and how hard?

Anything an attacker can repeat for profit: login, sign-up, password reset, contact forms, uploads and every endpoint that calls a paid API. The limits on this website are a worked example, running on serverless hosting with a Postgres-backed limiter and no Redis:

  • Login, three buckets: 5 attempts a minute per email and IP, 20 a minute per IP, and 50 per 15 minutes per email across all IPs. One bucket keyed only on email would let anyone lock the real user out.
  • Admin API: 30 writes a minute per IP.
  • Media uploads: 10 a minute per IP, because each upload costs money at the image host.
  • Public API: 120 requests a minute per IP.
  • Confirmation emails: 3 per recipient address per day, so the contact form cannot be used to flood someone else's inbox.

The code, the trade-offs and why an in-memory counter fails on serverless are in the rate limiting explainer. For a vibe-coded app, the paid endpoints matter most: an AI feature that anyone can call in a loop is a bill, not just a vulnerability.

What about backups, migrations and alerts?

Checks 19 and 20 do not stop an attack. They decide how bad the day after is. If the schema only exists in a dashboard, nobody can rebuild it; if the only backup has never been restored, it is a hope. Supabase's production checklist recommends Point in Time Recovery once a database is expected to pass 4 GB. And an alert that goes to an unread inbox is not an alert: route failed-login bursts and server errors somewhere a person looks every day.

Which failures block launch, and which can wait a week?

Failed checkLaunch?Why
1 to 7, 10, 13NoEach one lets a stranger read or change data, or spend your money
8, 9, 11, 12, 15, 16, 17Only with a dated fix planThey raise the cost of an attack; fix within days, before paid traffic
14, 18, 19, 20Yes, fix in the first sprintThey limit damage and recovery time rather than prevent access

For everything outside security that a launch needs, such as critical-path tests, performance and monitoring, use the pre-launch QA checklist alongside this one.

Why RAITHub for this

  • The checks become tests. A one-off audit ages the day the next prompt changes the code. RAITHub turns failing checks into automated tests gated in CI, as on Sundor Skin, where the build fails if a buyer-scoped table lacks a row-level-security policy and an IDOR suite tries to read other buyers' data.
  • Worked examples you can inspect. The headers and rate limits above are running on this site today, with their gaps stated.
  • Fixed scope. A free 15-minute technical audit, then a written, fixed quote, with the fixes delivered as pull requests you own.

RAITHub has not published a case study of hardening an AI-generated app, so this is engineering guidance, not a claim of past rescues.

When to use a tool instead

  • Every check passes and someone owns the app. Keep the Security Advisor, dependency audit and the bundle search in CI, and rerun this list before each major release.
  • Only one or two checks fail. Each has a documented fix linked above; a single issue is a job for the fix one issue page, not a rescue.
  • You need an independent penetration test or a certified vendor. RAITHub is not SOC 2 or ISO 27001 certified and does not sell penetration testing.

If several checks in the first two groups failed, the problem is usually structural. Code Rescue is built for that.

Last reviewed: 29 September 2026. Veracode, OWASP, Supabase, Vite, GitHub and CVE sources checked on 29 September 2026.

Send the checklist results, pass or fail per line, through Code Rescue on the contact form, and the free 15-minute audit starts from your findings.

Frequently asked questions

What is the most common security issue in vibe-coded apps?

Broken access control: one user can read or change another user's data, usually because row-level security is missing or a policy allows everything. It is A01 in the OWASP Top 10:2025, and it was the root cause of CVE-2025-48757.

How long does this security checklist take?

About one to two hours to run all 20 checks on a small app, if you have access to the database, the repository and the hosting settings. Fixing failures takes longer, from minutes for a header to days for access control across many tables.

Are AI tool security scanners enough before launch?

They are a useful first pass, not a gate. Lovable's own documentation says its scans do not replace a thorough security review. Checks such as the second-user test and server-side role checks need a person or an automated test.

Which security headers does a Lovable or Bolt app need?

Strict-Transport-Security, Content-Security-Policy, X-Frame-Options or a frame-ancestors directive, X-Content-Type-Options, Referrer-Policy and Permissions-Policy. Static hosts such as Vercel let you set them in a config file.

Do I need rate limiting if Supabase already limits auth?

Yes, for everything Supabase does not cover. Its auth endpoints have built-in limits, but your own Edge Functions, API routes and any endpoint that calls a paid API need limits you set yourself.

Can I launch with some checks failing?

Not with failures in data access, secrets, server-side roles or input validation. Those expose data or money. Header, bot-protection and recovery gaps can ship with a dated fix plan, fixed before paid traffic arrives.

vibe coded app security checklistAI-generated code securityLovableSupabasesecurity headersrate limitingOWASP Top 10

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.