To secure an app built with Lovable, Bolt, Cursor or v0, check four things before launch: no secret keys in the browser bundle, database access rules (Supabase RLS or Firebase rules) on every table, authentication on every API route, and basic security headers. Most real exposures in AI-built apps come from the first three.
AI coding tools are good at producing something that works on the happy path. Security failures live off the happy path: the request nobody in the demo made, the user who changes an ID in the URL, the key that was convenient to paste into the frontend. The tool did what you asked. Nobody asked it to say no.
This checklist is ordered by how much damage each item does when it goes wrong, not by how hard it is to fix. Most items take under an hour.
1. Secrets shipped to the browser
Anything in your frontend code, including environment variables bundled at build time, is readable by every visitor. Open DevTools, go to Sources, and search the JavaScript for “sk_”, “sk-”, “AKIA”, “service_role” and “PRIVATE KEY”. If an AI tool wired a third-party API straight from a React component, the key is almost certainly there.
Some keys are public by design. A Supabase anon or publishable key and a Firebase web config are meant to ship to the browser. Google Maps and Firebase browser keys are fine too, provided they are restricted to your domain and the APIs you use. Everything else is not.
| Key | Safe in the browser? | If it is exposed |
|---|---|---|
| Supabase anon / publishable key | Yes, if RLS is on for every table | Nothing, by itself. Check RLS (item 2). |
| Supabase service_role / secret key | Never | Rotate now. It bypasses every RLS policy. |
| Stripe secret key (sk_live_, sk_test_) | Never | Rotate now. A live key can issue charges and read payment data. |
| OpenAI or Anthropic API key | Never | Rotate now, then proxy AI calls through your backend. |
| AWS access key | Never | Rotate the key pair and review what it could reach. |
| Google / Firebase browser key | Yes, if restricted | Restrict it to your domains and the APIs you use. |
Rotating means issuing a new key and revoking the old one. Deleting the key from your code is not enough: the old bundle and your git history still contain it.
2. Supabase Row Level Security on every table
Supabase's own documentation is direct about this: the publishable key only reaches what Row Level Security allows, so RLS should be enabled on every table before you deploy. A table with RLS disabled is readable and writable by anyone holding your public key, which is everyone.
In the Supabase dashboard, open each table and confirm RLS is enabled. Then read the policies. A policy of “true” for select is the same as no policy. The pattern you usually want is that a row is visible only when its “user_id” matches “auth.uid()”. The Supabase RLS guide has worked examples.
Then test it the way an attacker would. Sign in as user A, copy a request from the Network tab that fetches user A's data, and replay it with user B's session, or with no session at all. If you get data back, the policy is wrong.
3. Firebase security rules
Firebase has the same failure in a different shape. Rules of “allow read, write: if true;” are fine for an afternoon of prototyping and dangerous everywhere else. Google's own page on insecure rules says never to use that rule set in production, because anyone who guesses your project ID can read, change or delete your data. Your project ID is in your web config, so assume it is known.
Write rules that check “request.auth.uid” against the owner of each document, and use the Rules Playground in the Firebase console to try reads as a signed-out user and as a different user.
4. Authentication on every API route
AI tools often protect the page and forget the endpoint behind it. The dashboard redirects signed-out visitors to the login screen, but “/api/orders” happily returns every order to anyone who calls it directly. Hiding a button is not access control.
Go through every server route, edge function and serverless function and confirm two things: it rejects requests with no valid session, and it only returns records the signed-in user owns. That second check is what OWASP calls broken object level authorization, and Broken Access Control is ranked first in the OWASP Top 10:2025.
The quick test: find a request that includes a record ID, such as “/api/invoices/1042”, and change it to “1041”. You should get a 403 or a 404. Guessable sequential IDs make this attack easier, so prefer UUIDs, but UUIDs do not replace the ownership check.
5. Admin routes and internal tools
Search your codebase for “admin”, “internal”, “debug” and “seed”. AI-generated apps often contain an admin page protected only by not being linked anywhere, or a role check done in the frontend. Both fall to anyone who reads your JavaScript. Admin checks belong on the server, against a role stored in your database, not in a flag the client sends.
6. Security headers
Headers are cheap defence in depth. They will not fix a broken policy, but they limit the damage of the bugs you have not found yet. The four worth setting on day one, all explained in the OWASP HTTP headers cheat sheet:
- Content-Security-Policy. Restricts where scripts can load from, so one injected script cannot quietly run with full access to the page. Start in report-only mode; the MDN CSP guide explains the directives.
- Strict-Transport-Security. Tells browsers to use HTTPS only, so a first visit cannot be downgraded to plain HTTP. See MDN on HSTS.
- X-Frame-Options or CSP frame-ancestors. Stops other sites embedding yours in an iframe to trick users into clicking (clickjacking).
- X-Content-Type-Options: nosniff. Stops browsers guessing file types, which can turn an uploaded file into executable script.
7 to 12. The rest of the pre-launch list
7. Rate limit anything that costs money or guesses passwords
Login, signup, password reset, and every endpoint that calls a paid AI model. Without a limit, one script can try thousands of passwords or run up your OpenAI bill overnight.
8. Keep secrets out of URLs
Tokens and API keys in query strings end up in server logs, browser history and Referer headers. Send them in a header or the request body.
9. Load everything over HTTPS
A single “http://” request from an HTTPS page can expose whatever it carries. Check the Network tab for any request that is not HTTPS.
10. Do not leak errors
Stack traces, SQL errors and file paths in API responses or the browser console tell an attacker what you run and where it breaks. Return a generic message and log the detail server-side.
11. Lock down CORS
An API that echoes back whatever Origin it is sent, with credentials allowed, lets any website make signed-in requests from your users' browsers and read the answers. List your own domains explicitly instead of reflecting the request.
12. Audit dependencies
Run npm audit and fix high and critical advisories. AI tools pin whatever version they last saw, which is sometimes one with a known vulnerability.
What an outside scan can and cannot see
Some of this list can be checked from outside your app, by loading it the way a visitor does. Truewick's free health check does that in a real browser. On the security side it searches the page HTML, browser storage and your same-origin JavaScript bundles for high-confidence key patterns: Stripe secret keys, OpenAI and Anthropic keys, AWS access keys, GitHub, Slack and SendGrid tokens, private key blocks, and Supabase service_role keys (it decodes the JWT and only flags the service role, so a normal anon key is not reported as a leak). Google API keys are flagged as needing restriction rather than as leaks.
It also reports missing Content-Security-Policy, HSTS, clickjacking protection and nosniff headers, requests sent over plain HTTP from an HTTPS page, tokens or keys passed in URL query strings, and API calls that use guessable sequential IDs, which it flags as a prompt to run the ownership test in item 4 by hand. Alongside that it runs Lighthouse for performance, accessibility, best practices and SEO, an accessibility check with axe-core, a broken-link and console-error pass, and screenshots at phone, tablet and desktop widths.
Without an account, the scan covers one page and shows the scores, the top general findings, and how many security issues it found and how severe they are. The details of security findings appear only after you sign up. That is deliberate: anyone can type anyone's URL into a public form, and listing another company's exposed keys to a stranger would make the scan a reconnaissance tool.
When to get help
If the scan or your own check turns up a live secret key, rotate it first and investigate second. If it turns up a table with RLS off and real user data in it, assume the data has been read: check your provider's logs and take advice on whether you need to notify users.
If the list is longer than you can work through before launch, a code audit by someone who did not write the app is the fastest way to find the access-control gaps. Sprout, the studio that builds Truewick, runs fixed-price audits and fixes for AI-built apps. Any competent engineer who reads the code and tests as a second user will find the same class of problems.