← All posts
July 25, 2026guideai-generated-appssecuritypre-launchsupabase

How to Secure an AI-Generated App Before You Launch

A practical security workflow for apps built with Lovable, Bolt, Cursor, Replit, v0, or another AI coding tool: secrets, authorization, RLS, APIs, dependencies, headers, and launch verification.

An AI coding tool can take an app from prompt to deployment in an afternoon. That speed changes where security work happens: instead of reviewing a design before code exists, you often inherit a working application, a database, several API integrations, and deployment settings all at once.

The right response is not to stop using AI. It is to treat generated code as an untrusted first draft and verify the controls that determine what an attacker can read, change, trigger, or consume.

This guide gives you a practical launch workflow for apps built with Lovable, Bolt, Cursor, Replit, v0, Claude Code, or similar tools. It focuses on controls you can verify, not on whether the generated code looks polished.

Short version: protect secrets, enforce authorization on the server and in the database, constrain every public API, patch dependencies, harden browser behavior, then test the deployed app as more than one user.

If your app is already deployed, run a free VibeScan security scan first. It checks the public surface without requiring repository access and gives you prioritized findings and fix prompts. Use the result as an input to the workflow below, not as a claim that the whole application is secure.


1. Write down the security boundary

Before changing code, make a one-page inventory:

  • Which routes are intentionally public?
  • Which data belongs to a specific user or organization?
  • Which actions require an authenticated user?
  • Which actions require an owner, administrator, or billing role?
  • Which services can create cost: AI APIs, email, SMS, storage, image generation, or background jobs?
  • Which credentials can read production data or change infrastructure?

This turns “make the app secure” into testable statements. For example:

A signed-in user may read and update rows where user_id equals their server-verified identity. They may never choose a different user_id in a request and gain access.

Keep this inventory beside the code. When the AI adds a feature, update the boundary before accepting its implementation.

2. Find and rotate exposed secrets

Search more than the current source tree. A secret can survive in:

  • Git history and old branches
  • client-side JavaScript bundles
  • .env files copied into prompts
  • build logs and error messages
  • AI coding chat history
  • screenshots, tickets, or shared documents

Browser-safe identifiers and privileged secrets are different. A Supabase publishable or legacy anon key is designed for public clients when Row Level Security is correct. A Supabase secret or service_role key is privileged and belongs only in a protected backend. Stripe secret keys, webhook signing secrets, database passwords, and private API tokens also belong server-side.

If a privileged credential was exposed, rotate or revoke it first. Removing it from the latest commit does not make an already copied credential safe. GitHub's official secret-scanning guidance likewise recommends rotating a leaked credential when an alert is found.

Then add prevention:

  • keep privileged values in your deployment platform's encrypted environment settings;
  • ensure only explicitly public variables receive a client-exposed prefix;
  • enable repository secret scanning or a pre-commit check;
  • use separate development and production credentials;
  • never paste a real production secret into an AI prompt.

3. Enforce authorization in trusted code

Authentication answers “who is this?” Authorization answers “may this identity perform this action?”

A hidden button is not authorization. A client-side redirect is not authorization. The check must run in code the user cannot modify: a server route, server action, backend function, or database policy.

For every read and write route:

  1. derive the user identity from a verified session or token;
  2. reject missing or invalid identity;
  3. check ownership or role on the server;
  4. scope the database query to that identity;
  5. reject client-supplied ownership fields that conflict with it.

Test object-level authorization explicitly. Create two accounts, A and B. As B, replace record IDs in URLs and API requests with IDs belonging to A. Repeat the test for reads, updates, deletes, exports, attachments, and background-job triggers.

OWASP's access-control guidance recommends deny-by-default rules, server-side enforcement, and record-ownership checks. Those are especially important for generated apps because a route can be functionally correct while omitting one ownership condition.

4. Verify Supabase RLS policy by policy

For Supabase apps, Row Level Security is part of the authorization layer. Supabase recommends enabling RLS on every table in an exposed schema and using policies to restrict rows.

Do not stop at the “RLS enabled” badge. Review each operation:

alter table public.projects enable row level security;

create policy "Users read their own projects"
on public.projects
for select
to authenticated
using ((select auth.uid()) = user_id);

create policy "Users create their own projects"
on public.projects
for insert
to authenticated
with check ((select auth.uid()) = user_id);

Also review UPDATE and DELETE policies. For UPDATE, check both which existing rows may be selected and what the new row is allowed to contain. Audit views and database functions too: a privileged security definer view or function can change how RLS applies.

Red flags include:

  • using (true) on user-owned data;
  • granting every authenticated user access without checking ownership;
  • accepting user_id or organization_id directly from the browser;
  • using a service-role client in browser or shared session code;
  • assuming a view automatically inherits the underlying table's RLS behavior.

Test policies with two real accounts rather than only reading the SQL.

5. Constrain every public API

Generated endpoints often implement the happy path and omit limits. For each public route, define:

  • accepted methods and content types;
  • a request schema and maximum field lengths;
  • maximum upload, batch, page, and response sizes;
  • authentication and authorization requirements;
  • per-user and per-IP rate limits;
  • execution timeout and concurrency limits;
  • a spending cap or alert for paid downstream services;
  • a safe error response that does not expose internals.

Prioritize password reset, magic-link, email, SMS, AI-generation, upload, webhook, and search routes. These can create direct cost or consume disproportionate resources. OWASP's API guidance calls out missing limits on request frequency, payload size, execution time, records returned, and third-party spending.

For webhooks, verify the provider signature against the raw request body before processing the event. Make handlers idempotent so a retry cannot create duplicate credits, orders, or messages.

6. Patch and control dependencies

Run the package manager's audit and review the actual lockfile, not only package.json. A version range can resolve differently from the version deployed in production.

For each high-severity advisory:

  1. confirm which installed package and version is affected;
  2. identify whether it is reachable in your application;
  3. upgrade to a patched version;
  4. regenerate and commit the lockfile;
  5. run tests and a production build;
  6. deploy and verify the affected route.

Add dependency review to pull requests so new risk is visible at the point of change. GitHub's dependency-review feature, for example, shows dependency changes and their security impact in the pull-request diff.

Do not run a broad forced upgrade moments before launch. Patch deliberately, preserve a rollback path, and verify behavior after every framework or authentication-library change.

7. Harden the browser and deployment

Security headers tell the browser which behavior is allowed. At minimum, evaluate:

  • Content Security Policy
  • Strict Transport Security
  • anti-framing protection with CSP frame-ancestors or X-Frame-Options
  • X-Content-Type-Options: nosniff
  • Referrer Policy
  • Permissions Policy

Next.js supports response headers through the headers configuration. Start a Content Security Policy in report-only mode if necessary, observe violations, then enforce it. Avoid copying a permissive policy that includes broad wildcards or unsafe script behavior without understanding why.

Also verify that production has no debug routes, source maps containing sensitive context, directory listings, public storage buckets, sample admin accounts, or development-only CORS origins.

8. Test the deployed application, not just the repository

A secure-looking source tree can be deployed with the wrong environment variables, headers, database policies, or cached bundle. Verify the real production URL.

Use this launch test:

  1. create two ordinary accounts and one appropriately scoped admin test account;
  2. exercise account creation, sign-in, reset, and sign-out;
  3. try cross-account reads and writes by changing identifiers;
  4. inspect browser network responses and public JavaScript for sensitive data;
  5. verify headers on HTML and API responses;
  6. send malformed, oversized, repeated, and unauthorized API requests;
  7. confirm logs record rejected security-relevant actions without recording secrets;
  8. apply one representative fix and rerun the same check.

VibeScan's public-surface scan can automate repeatable checks for exposed secrets, insecure configuration, headers, and known dependency issues. Manual multi-user testing covers the application-specific authorization and business rules a passive scan cannot infer.

A practical release gate

Do not make “security reviewed” a vague checkbox. Require evidence:

  • [ ] No privileged secrets exist in the client bundle, current source, or known history
  • [ ] Every protected route verifies identity and permission server-side
  • [ ] Every exposed database table has operation-specific ownership policies
  • [ ] Two-account isolation tests pass
  • [ ] Public APIs enforce schemas, limits, timeouts, and abuse controls
  • [ ] Webhook signatures and idempotency are verified
  • [ ] High-severity dependency findings are patched or explicitly risk-accepted
  • [ ] Production headers and CORS match the intended origins
  • [ ] Logging and alerts cover repeated auth failures and expensive abuse
  • [ ] A deployed scan and manual smoke test pass after the final build
  • [ ] Rollback instructions and credential-rotation owners are known

If a check fails, record the owner, fix, and retest result. That small discipline is more valuable than asking an AI model for a general assurance that the code is secure.

What automation can and cannot tell you

Automation is strong at consistent, observable checks. It can identify known secret patterns, dependency advisories, missing headers, public endpoints, and some configuration mistakes. It is weaker at understanding whether your product's exact roles, ownership rules, billing rules, and workflows match your intent.

Use three layers:

  1. Automated checks on every meaningful change
  2. Manual adversarial tests for identities, objects, roles, and abuse cases
  3. Independent review when the app handles regulated, financial, health, identity, or otherwise high-impact data

That is the durable pattern for securing AI-generated apps: let AI accelerate implementation, but require evidence before trusting the result.


Primary references

Check your own app

Free scan — no GitHub access needed. Takes 30 seconds.

Scan my app free