You built something real with Lovable, v0, Bolt, or Claude Code. It works in the browser, it looks good, and you're ready to ship. But AI-generated code has predictable failure modes that don't show up during demos. They show up when a user tries something unexpected, when two requests hit the server at the same time, or when someone reads your database URL out of the client bundle. This guide walks you through a structured pre-launch audit you can run yourself in a few hours. No external consultant required, though a second set of eyes never hurts.

Key takeaways

  • Most vibe-coded apps fail in the same four areas: authentication, data exposure, error handling, and environment config.
  • Check your network tab first. A five-minute browser inspection will surface the most common data-leak patterns immediately.
  • Row-level security on your database is the single highest-use fix before launch.
  • Hardcoded secrets and public API keys are the fastest path from "launched" to "compromised."
  • You can complete a meaningful audit in half a day using only your browser, a terminal, and a free static analysis tool.

What You'll Need

  • Access to the codebase (read access to the repo is enough for most checks)
  • A browser with DevTools (Chrome or Firefox)
  • A terminal with Node.js or Python installed
  • The hosting dashboard for your deployment (Vercel, Netlify, Railway, Fly.io, etc.)
  • Your database dashboard (Supabase, PlanetScale, Firebase, etc.)
  • A free account on a secret-scanning tool such as Gitleaks or TruffleHog (optional but fast to set up)
  • About three to five hours of focused time

Step 1: Scan the Codebase for Hardcoded Secrets

Open a terminal in the root of your project and run a secret scanner before you do anything else. This catches the most urgent class of bug: API keys, database connection strings, and auth tokens committed directly into source files.

Run Gitleaks with one command:

gitleaks detect --source . --verbose

If you prefer TruffleHog:

trufflehog filesystem .

Read every finding. AI tools sometimes inline keys during scaffolding and never move them to environment variables. A single exposed Stripe secret key or OpenAI API key can rack up charges you did not authorize.

  • Move every flagged secret to an environment variable. Use .env.local for local dev and your hosting dashboard for production.
  • Add .env* to .gitignore if it is not there already.
  • Rotate any key that was ever committed to git history, even if the commit was later deleted. History is not private once it has been pushed.
  • Check for secrets in commented-out code. Scanners miss these sometimes.

Step 2: Audit Authentication and Authorization Logic

AI-generated auth is often structurally correct but incomplete. It checks whether a user is logged in. It frequently skips whether that user is allowed to access the specific resource they requested.

Walk through every route or API endpoint in the app and ask two questions:

  1. Does this endpoint check that the caller is authenticated?
  2. Does it verify the caller owns or has permission to access the specific record being requested?

The second check is the one that gets skipped. A typical failure looks like this: a user can view their own invoice at /api/invoices/123, but there is no check that invoice 123 actually belongs to them. Change 123 to 124 in the URL and you see someone else's invoice. This is called an Insecure Direct Object Reference (IDOR) and it is extremely common in vibe-coded apps.

  • Test it yourself. Log in as a test user, grab a resource ID from a request in DevTools, then try to access a different ID in the same format.
  • Look for any database query that uses an ID from the URL or request body without also filtering by the current user's ID.
  • If you are using Supabase, check that Row Level Security (RLS) is enabled on every table that holds user data. An unauthenticated SQL client can read your entire database if RLS is off.

Step 3: Inspect What the Client Actually Sends and Receives

Open DevTools in your browser, go to the Network tab, and use the app as a logged-in user. Click through every main feature. Watch what the API responses contain.

You are looking for two things:

  1. Fields in the response that the UI does not display but that the app returns anyway, such as password hashes, internal IDs, or other users' email addresses.
  2. Sensitive data passed in query string parameters (it shows up in server logs and browser history).

AI-generated backends often return entire database rows when they only need three fields. A user profile endpoint might return the user's hashed password, their internal role, and their billing tier because the query did SELECT *.

  • Fix this by selecting only the columns you actually need in every query.
  • If you use an ORM, check that you are not passing whole model objects to the response serializer without an explicit allowlist.
  • Look at POST request bodies too. Confirm you are not accepting fields the server should not trust from the client, like isAdmin: true.

Step 4: Check Environment Variable Configuration for Production

Open your hosting dashboard and look at the environment variables configured for the production deployment. Compare them to every variable referenced in your code.

Common gaps:

  • A variable is set in .env.local but was never added to the production environment, so the feature silently fails or falls back to a default that looks fine in tests.
  • Development and production share the same API key for a third-party service, which means a test email goes to a real customer's inbox.
  • A NODE_ENV check was added during dev but the variable was never set in production, so debug logging or stack traces are exposed to users.
  • Keep a .env.example file in the repo with every variable name and a placeholder value. Use it as your checklist.
  • Confirm that NODE_ENV is set to production in your deployment. Some frameworks change error verbosity and security headers based on this value.
  • Use separate API keys for every third-party service between dev and production environments.

Step 5: Test Input Validation and Error Handling

AI tools write happy-path logic well. They handle the case where everything goes right. They handle edge cases inconsistently.

Go through every form and API input in the app and try to break it deliberately:

  • Submit an empty form.
  • Submit a string where a number is expected.
  • Submit a very long string (paste 10,000 characters into a text field).
  • Submit SQL-like strings: ' OR '1'='1 in any text field that touches a database query.
  • Upload a file with the wrong type or a very large file to any file upload field.

Watch what happens. A well-handled error returns a clear message and a proper HTTP status code (400 for bad input, 422 for validation failure). A badly handled error returns a 500 with a stack trace, or worse, it succeeds silently and stores garbage in your database.

  • Every API endpoint should validate input on the server side, not just the client side. Client-side validation is for user experience. Server-side validation is for security.
  • Use a validation library. Zod for TypeScript projects is a good choice. It makes validation explicit and type-safe.
  • Log errors on the server but never return raw stack traces to the client in production.

Step 6: Review Third-Party Dependencies for Known Vulnerabilities

AI tools pull in packages quickly. They do not always choose the most current version. Run a dependency audit before you launch.

For a Node.js project:

npm audit

For a Python project:

pip install pip-audit
pip-audit

Read the output. Focus on high and critical severity findings. Many will be transitive dependencies (packages your packages depend on) and may require updating a top-level dependency rather than a direct fix.

  • Not every vulnerability is exploitable in your specific usage. Read the advisory and decide if the affected code path is reachable in your app.
  • Update dependencies before launch, not after. Post-launch updates introduce risk; pre-launch updates are just good hygiene.
  • Pin dependency versions in production to avoid surprise updates breaking the build.

Step 7: Validate Security Headers and HTTPS Configuration

Visit securityheaders.com and enter your production URL. It grades your HTTP response headers and tells you what is missing.

Headers to add before launch:

  • Content-Security-Policy: limits which origins can load scripts, styles, and other resources. This is the primary defense against cross-site scripting (XSS).
  • X-Frame-Options: DENY or SAMEORIGIN: prevents your app from being embedded in an iframe on another site (clickjacking).
  • X-Content-Type-Options: nosniff: stops browsers from guessing content types.
  • Strict-Transport-Security: forces HTTPS for all future visits once a user has visited once.
  • Referrer-Policy: strict-origin-when-cross-origin: controls what URL information is sent to third parties.

Most modern hosting platforms (Vercel, Netlify) let you set these in a config file. Vercel uses vercel.json, Netlify uses netlify.toml or a _headers file.

  • Set Content-Security-Policy to report-only mode first if you are worried about breaking things. It logs violations without blocking anything.
  • Confirm your app does not load anything over HTTP when served over HTTPS. Mixed content warnings are a sign something is misconfigured.

Step 8: Check Rate Limiting on Critical Endpoints

AI tools almost never add rate limiting. This means your login endpoint, your password reset endpoint, your signup form, and any AI-powered feature that costs money per call are wide open to abuse.

At a minimum, add rate limiting to:

  • Login and authentication endpoints
  • Password reset flows
  • Any endpoint that sends an email or SMS
  • Any endpoint that calls an external API you pay per use for
  • Any endpoint that creates records in bulk

If you are on Vercel, the @upstash/ratelimit library with a free Upstash Redis instance handles this in about 20 lines of code. If you are behind a CDN like Cloudflare, you can set rate limiting rules there without touching the app code.

Common Mistakes to Avoid

  • Trusting client-supplied data for authorization. Never decide what a user can access based on a field they sent in the request. Use their session identity on the server to look up their permissions.
  • Using a single database user with full admin rights for your app. Create a least-privilege database role for the application. It should not be able to drop tables.
  • Skipping the audit because the app "works." Working and safe are different properties. Many apps work fine and leak data simultaneously.
  • Running the audit once and treating it as permanent. Every time you add a feature, the surface area grows. Build the habit of re-checking auth and input validation on each new endpoint.
  • Fixing findings without rotating compromised credentials. If a secret was exposed, updating the code is not enough. You must revoke the old key and issue a new one.

FAQ

How long does a pre-launch audit take for a small vibe-coded app?

For a simple app with under 20 routes, plan for three to five hours if you work through the steps above methodically. The secret scan and dependency audit run in minutes. The manual steps, especially testing auth and inspecting network responses, take the most time because you are reasoning about behavior, not just running a tool.

Do I need to hire a security firm before launching an AI-built app?

For a consumer app handling payments or health data, a professional penetration test is worth the cost. For an early MVP with a small user base and no sensitive data categories, the self-audit steps in this guide cover the most common failure modes. Use the findings to decide whether you need to go deeper before expanding to more users.

What is the single most important check for a Supabase-backed app?

Row Level Security. Open your Supabase dashboard, go to Authentication, then Policies, and confirm that every table containing user data has RLS enabled with at least one policy. A table with RLS disabled and a public anon key in the client is readable by anyone who calls the API directly.

My app was built by an AI tool and I can't fully read the code. Can I still audit it?

Yes. Most of the checks in this guide are behavioral, meaning you test what the app does rather than reading every line. The network tab inspection, the broken input tests, and the security headers check require no code reading at all. For the secret scan and dependency audit, you run a tool against the files without needing to understand the full codebase.

When should I re-run this audit?

After any significant feature addition, after upgrading major dependencies, and before moving from a closed beta to a public launch. The authentication and input validation checks are fast enough to run on every new API endpoint as a habit rather than a one-time event.

If you run through all eight steps and find more than you can fix before your launch window, prioritize in this order: exposed secrets first, broken authorization second, missing input validation third. The first two have the highest probability of causing immediate, serious harm. The rest can follow in a post-launch sprint, provided you have real monitoring in place to catch anything that slips through.

If the audit reveals deeper structural problems, like an app that was scaffolded without any server-side logic or one where the entire data model needs to change, that is a different problem than a checklist can solve. It usually means the prototype needs to be rebuilt with production constraints in mind from the start. Clover Labs works specifically with teams in that position, taking AI-built prototypes and making them production-ready. If that is where you are, here is how we fix vibe-coded apps.