AI-built app security checklist: don't launch before checking these 15 things

AI-built apps can look finished while they are still leaking keys, exposing database rows, or trusting unsafe webhooks. Use this checklist before you put real users, client data, or payments into production.

Share
AI-built app security checklist: don't launch before checking these 15 things
Developer reviewing pre-launch security risks in an AI-built web application

The dangerous part of an AI-built app is not that it looks broken. It is that it looks finished.

The login works. The dashboard loads. Stripe returns a successful payment. The client says the app feels good. Then someone opens the JavaScript bundle and finds a secret key. Or a logged-in user changes one ID in the URL and sees another customer's data. Or a webhook endpoint accepts fake payment events because the signature check never made it into the generated code.

That is the security trap with vibe-coded apps. Cursor, Lovable, Bolt, Replit, v0, Claude Code, and similar tools can get you to a working product fast. They do not automatically prove that your auth model is correct, your database rules are locked down, or your paid API keys are safe.

There is research behind this concern. In the paper "Do Users Write More Insecure Code with AI Assistants?", researchers found that participants using an AI coding assistant wrote less secure code overall than participants without one. Just as important, the AI-assisted group was more likely to believe their code was secure.

That second part is the scary one. The app can feel safe because it works.

There is not yet a reliable public statistic that says "most vibe-coded apps are vulnerable." The category is too new, and most of these projects are private. But the pattern is already familiar: AI-assisted code can be functional, confident, and still miss basic security checks. If the app stores user data, takes payments, or connects to paid AI APIs, you need a pre-launch security pass before real users arrive.

This checklist is for founders, freelancers, and small teams building web apps with AI-assisted tools. It is not a replacement for a professional security audit. It is a practical first pass for the mistakes that show up again and again in fast-built apps.

Who should use this checklist

Use it before launching if your app has any of these:

  • user accounts or private dashboards
  • Supabase, Firebase, Postgres, Prisma, or another database layer
  • Stripe, Lemon Squeezy, Paddle, or other payment flows
  • OpenAI, Anthropic, Replicate, or other paid API calls
  • admin routes, client portals, upload forms, or internal tools
  • generated code you have not reviewed line by line

If the app is only a static landing page, you probably do not need every item here. If the app stores user data or handles money, do not skip the boring parts.

Quick version

Before launch, check these 15 areas:

Check Why it matters
API keys Secret keys in frontend code can be stolen by anyone
Environment variables Bad defaults can leak private services in production
Authentication Private pages must actually require a logged-in user
Authorization Logged-in users should only see their own data
Admin routes /admin, /dashboard, and debug pages are common leaks
Database rules Supabase RLS and Firebase rules are easy to misconfigure
Stripe webhooks Unsigned webhooks can fake payments or plan upgrades
CORS Loose CORS rules can expose authenticated APIs
Cookies Missing flags make session theft easier
Security headers Headers reduce common browser-side attacks
Source maps Production maps can expose source code and secrets context
File uploads Upload forms need size, type, and storage controls
AI endpoints LLM calls need rate limits, input limits, and cost controls
Dependencies Old packages may include known vulnerabilities
Error messages Stack traces and raw errors can leak internals

1. Make sure secret API keys are not shipped to the browser

This is the first thing to check because it is easy to miss and expensive to fix after launch.

Open your production site in the browser, view the page source, and inspect the loaded JavaScript files. Search for patterns like:

sk_live_
sk_test_
sk-proj-
sk-ant-
ghp_
AKIA
supabase_service_role
STRIPE_SECRET_KEY
OPENAI_API_KEY
ANTHROPIC_API_KEY

Some public keys are expected. For example, a Stripe publishable key starts with pk_, and a Supabase anon or publishable key may be visible in frontend code. Those are not automatically leaks. The problem is when a server-side secret key ends up in JavaScript that any visitor can download.

A useful rule: if the key can charge money, read private data, send emails, modify infrastructure, or bypass user permissions, it does not belong in frontend code.

What to fix:

  • keep secret keys only on the server
  • call paid APIs from server routes, not directly from the browser
  • rotate any secret that was exposed publicly
  • check deployment logs and old commits if the key may have been committed

2. Review your environment variables

AI-generated projects often include .env.example, .env.local, or copied deployment instructions. Those files can be helpful, but they can also hide bad assumptions.

Check for these mistakes:

NEXT_PUBLIC_OPENAI_API_KEY=
NEXT_PUBLIC_STRIPE_SECRET_KEY=
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=
DATABASE_URL=postgres://...
ADMIN_PASSWORD=admin
JWT_SECRET=changeme

In Next.js and similar frameworks, anything prefixed with NEXT_PUBLIC_ is meant to be available in the browser. Do not put secrets there.

Also check your hosting provider. A project can be clean locally but unsafe in production if someone added the wrong variable in Vercel, Netlify, Railway, Render, Fly.io, or a VPS .env file.

3. Test authentication like a stranger

Do not only test the happy path. Test the app as someone who is not logged in.

Open a private browser window and try direct URLs:

/dashboard
/account
/settings
/admin
/projects
/api/user
/api/projects
/api/admin

A private page should redirect to login or return 401/403. It should not show real user data. It should not return a JSON response with private records.

Generated apps often protect the UI but forget to protect the API route behind it. The page may require login, while /api/projects still returns data to anyone who calls it directly.

What to fix:

  • protect server routes, not just React components
  • check auth on every API route that reads or writes private data
  • avoid trusting userId, email, role, or plan from the request body
  • get the current user from the server-side session or token

4. Check authorization, not just login

Authentication answers: who is this user?

Authorization answers: what is this user allowed to access?

Many AI-built apps stop after the first question. That creates cross-account data leaks.

Example bug:

User A owns project 123.
User B is logged in.
User B opens /dashboard/projects/123.
The app shows User A's project because it only checked that User B was logged in.

Every database query for private data should include ownership filtering.

For example, do not only query by project ID:

where: { id: projectId }

Also scope it to the current user or tenant:

where: { id: projectId, profileId: currentUser.id }

This is one of the most important checks for client portals, SaaS dashboards, admin panels, and agency tools.

5. Look for exposed admin and debug routes

AI tools are good at creating convenience routes. They are also good at leaving them in place.

Check common paths:

/admin
/api/admin
/debug
/api/debug
/internal
/dev
/test
/config
/config.js
/.env
/.env.local
/.git/config
/package.json
/swagger
/api-docs
/openapi.json

Not every exposed route is automatically critical. A public robots.txt file is normal. A public package.json is usually not ideal but may not be catastrophic. A public .env, database dump, admin page, or debug endpoint is serious.

The test is simple: if a route exposes secrets, private data, internal configuration, or privileged actions without authentication, fix it before launch.

6. Check Supabase row level security

Supabase is popular in AI-built apps because it is fast to set up. The common mistake is using the public anon key without proper Row Level Security policies.

The anon key is not a secret. Your RLS policies are the real protection.

Check every table that stores user or client data:

  • Is RLS enabled?
  • Can unauthenticated users read rows?
  • Can one logged-in user read another user's rows?
  • Can users update fields they should not control, such as role, plan, isAdmin, or ownerId?
  • Are insert and update policies stricter than select policies?

A quick manual test:

  1. Open the app in a logged-out browser session.
  2. Use the Supabase REST endpoint with the public key.
  3. Try reading common tables such as profiles, users, projects, orders, messages, or payments.

If you get real rows back without authentication, stop and fix RLS before launch.

7. Verify Stripe webhook signatures

Payment pages can look perfect while the webhook handler is unsafe.

A secure Stripe webhook route should verify the signature using Stripe's webhook secret. It should not trust a JSON body just because it looks like a Stripe event.

Look for code like this:

stripe.webhooks.constructEvent(rawBody, signature, webhookSecret)

Be suspicious if the route simply does this:

const event = await request.json()

That pattern may be fine for an internal test route, but it is not enough for a production Stripe webhook. Without signature verification, an attacker may be able to fake subscription events, mark accounts as paid, or trigger business logic they should not control.

Also check that your webhook updates the correct user by Stripe customer ID or a trusted client_reference_id, not by an email address taken from an unverified event body.

8. Check CORS settings

CORS bugs are easy to introduce when generated code tries to "fix" browser errors by allowing everything.

Watch for these headers on authenticated API routes:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

That combination is dangerous. Also be careful with code that reflects any incoming origin:

Access-Control-Allow-Origin: https://whatever-site-sent-the-request.com

For most small SaaS apps, you do not need broad CORS. Allow your own frontend domain. If you have a public API, separate those routes from authenticated dashboard routes.

If your app uses cookies for sessions, check the Set-Cookie headers.

Session cookies should usually have:

HttpOnly
Secure
SameSite=Lax or SameSite=Strict

HttpOnly helps protect the cookie from JavaScript access after an XSS bug. Secure prevents the cookie from being sent over plain HTTP. SameSite reduces some cross-site request risks.

Do not store session tokens in localStorage unless you have a strong reason and understand the tradeoffs. It is a common shortcut in generated apps, but it increases the damage from XSS.

10. Add basic security headers

Security headers will not save a broken app, but they are cheap protection.

Check for:

Content-Security-Policy
Strict-Transport-Security
X-Content-Type-Options
X-Frame-Options or frame-ancestors in CSP
Referrer-Policy
Permissions-Policy

A weak Content Security Policy is better than nothing only if it does not give you false confidence. If your CSP includes broad values like these, review it again:

script-src *
script-src 'unsafe-inline' 'unsafe-eval'
default-src *

For a small app, start strict and loosen only what breaks for a known reason.

11. Disable production source maps unless you need them

Source maps help debug JavaScript. They can also expose readable source code, comments, route names, and sometimes enough context to make attacks easier.

Check your production JavaScript files for references like:

//# sourceMappingURL=app.js.map

Then try opening the .map URL directly.

If source maps are public, decide whether you really need them. Some teams upload source maps privately to an error tracking service instead of serving them to everyone.

12. Review file upload handling

Upload forms are a common weak spot in quick app builds.

Check:

  • maximum file size
  • allowed MIME types and extensions
  • server-side validation, not only frontend validation
  • where files are stored
  • whether uploaded files can execute as code
  • whether private files require authorization to download
  • whether filenames are sanitized

Do not rely on the browser's accept attribute. It improves the UI, but it does not secure the server.

13. Add rate limits to API and AI endpoints

AI endpoints cost money. That changes the risk model.

A normal API abuse problem can become a billing problem if an attacker can repeatedly call an endpoint that triggers OpenAI, Anthropic, Replicate, ElevenLabs, or another paid service.

At minimum, rate limit:

/api/chat
/api/generate
/api/analyze
/api/upload
/api/scan
/api/stripe/checkout
/api/auth/*

Also add input limits. A user should not be able to paste a huge payload and force your app to send it to a model unchecked.

For small apps, a simple per-user and per-IP limit is often enough for launch. You can make it more advanced later.

14. Check dependencies and generated package choices

AI tools sometimes install old packages because the generated code was based on old examples.

Run an audit before launch:

npm audit
pnpm audit
npm outdated

For Python projects:

pip-audit

For containers:

docker scout cves

Do not blindly run a forced upgrade on a production app right before launch. Review breaking changes. But do not ignore high severity issues in packages that touch auth, file parsing, payments, or request handling.

15. Clean up error messages and logs

Turn off verbose errors in production.

A production error should not reveal:

  • stack traces
  • database URLs
  • API keys
  • internal file paths
  • SQL queries with private data
  • full request bodies containing tokens or personal information

Check your logs too. It is common for generated code to log entire request objects during development and then forget about it.

Search the codebase for:

console.log
console.error
JSON.stringify(request)
JSON.stringify(body)
process.env

Logging is useful. Logging secrets is not.

A simple pre-launch workflow

Use this order before putting real users on the app:

  1. Deploy to a staging domain.
  2. Create a test user and a second test user.
  3. Try to access each user's data from the other account.
  4. Open the app logged out and test private routes directly.
  5. Search production JavaScript for secret key patterns.
  6. Check Supabase or Firebase rules with the public client key.
  7. Verify Stripe webhooks with the real signature flow.
  8. Run dependency audit tools.
  9. Review logs for secrets and private data.
  10. Fix critical issues, then test again.

Do not wait until the app is popular to do this. Security gets harder after customers, subscriptions, uploads, and real database records are already in the system.

What AI tools tend to miss

AI coding tools are useful, but they are not security reviewers. They often optimize for code that runs, not code that fails safely.

Common misses include:

  • checking login but not ownership
  • putting server secrets in public environment variables
  • trusting webhook JSON without verifying signatures
  • creating admin routes without server-side guards
  • leaving permissive database policies in place
  • removing rate limits to make a demo work
  • logging sensitive data while debugging
  • copying old Stack Overflow patterns into a modern app

The fix is not to avoid AI tools. The fix is to add a review step before launch.

FAQ

Are AI-built apps less secure than hand-written apps?

Not automatically. A careful developer using AI can produce secure code. The risk is that AI tools make it easy to produce a large amount of working code before anyone has reviewed the security model. Speed increases the chance that boring checks get skipped.

Is a public Supabase anon key a security leak?

Usually no. Supabase anon or publishable keys are designed to be used by client apps. The real question is whether Row Level Security policies prevent unauthorized reads and writes. A public anon key with weak RLS is a problem.

Should I hide all API keys from frontend code?

Hide secret keys. Some keys are meant to be public, such as Stripe publishable keys or certain browser API keys with proper restrictions. When in doubt, check the provider's documentation and restrict the key by domain, permissions, and quota.

Do security headers make an app secure?

No. Headers are a hardening layer. They reduce the impact of some browser attacks, but they do not fix broken authentication, exposed secrets, or bad database rules.

Do I need a professional audit?

If the app handles sensitive user data, payments, healthcare, finance, legal records, or business-critical workflows, a professional review is worth considering. For a small MVP, this checklist is a good first pass, but it is not a guarantee.

Final thought

Fast-built apps fail in boring ways.

A secret key lands in the frontend. A database policy is left open. A webhook trusts the request body. A private route only hides behind the UI. None of this feels dramatic while you are building. It feels like progress.

Before launch, slow down for one pass. Check the keys. Check the database rules. Check the payment webhooks. Check the routes that were only supposed to exist during development.

The goal is not perfect security. The goal is to catch the obvious mistakes before a stranger does.