Lovable.app Security Audit Checklist: 12 Things Every Vibe-Coded SaaS Must Lock Down Before Production

Lovable.app + Supabase + Vercel is the dominant vibe-coded stack for solo-founder SaaS apps going to production in 2026. The 12 most-failed security audit items in this cohort: RLS policies that read as USING (true), public Vercel preview deployments against production data, public Supabase storage buckets, wildcard CORS, unverified webhook signatures, absent rate limits, IDOR-vulnerable endpoints, service-role keys in client bundles, NEXT_PUBLIC_ env-var hygiene, JWT secret rotation, OAuth callback URL tightness and PII in URL parameters. Audit pre-production, before the first paid customer.

Lovable.app, Bolt.new, v0 and Replit are producing the dominant cohort of solo-founder SaaS apps going to production in 2026. The pattern is identical across the cohort: AI scaffolds a working app in hours, the founder iterates on features for weeks, then ships to paid users. The handoff from "working" to "production secure" is where most of these apps fail.

This is the checklist Sherlock Forensics applies to a Lovable.app + Supabase production audit. Twelve specific failure modes, ordered by likelihood of being broken in a real shipped app, with what to test and how to fix each one. Built for the founder who is about to take real customer money on a vibe-coded stack and wants to know what could end the company before the first churn report.

The 12 Items

1. Row Level Security policies are real, not USING (true)

What it is. Supabase RLS is enabled per-table in the database. When RLS is on, queries from the public anon key and the authenticated user key only return rows the policy explicitly allows. When the policy is USING (true), every row is allowed for every caller and the protection is purely cosmetic.

Why it matters. RLS misconfiguration is the single most common production breach pattern across the vibe-coded SaaS cohort. AI scaffolds turn RLS on to satisfy linting, then leave the policy at true because the AI does not have application context to write a meaningful predicate. The result reads as secured in code review but exposes every row to every caller in production.

How to test. Pull the schema. For every table, run a query as the anonymous role and a query as a different authenticated user. If either returns data that should be private (another user's records, internal admin tables, audit logs), RLS is failing. Specifically grep policies for USING (true) and WITH CHECK (true) patterns.

How to fix. Write per-table policies that reference auth.uid() or auth.jwt() ->> 'role' to constrain rows by ownership or role. Test with the Supabase studio impersonation feature before deploying.

2. Vercel preview deployments are not publicly indexable

What it is. Vercel creates a unique public URL on every git push by default. These preview URLs run the full application against the production database unless explicitly isolated and search engines crawl them.

Why it matters. Vibe-coded apps are usually deployed on Vercel with default settings. Every feature branch, every WIP commit, every untested experiment becomes a publicly-reachable URL the moment it pushes. If the preview points at production data, every preview is a soft data leak. If it points at staging, the staging credentials are still in client-side environment variables.

How to test. List all preview URLs from your Vercel project (Dashboard then Deployments). Open each one in an incognito window with no authentication. If the home page renders or the API returns data, the preview is publicly accessible.

How to fix. Enable Vercel deployment protection on preview deployments (Vercel Pro tier required) or use a Vercel password protection middleware. For free-tier Vercel, the workaround is to point preview deployments at an isolated database with no production data.

3. Public Supabase Storage buckets are explicitly chosen, not defaults

What it is. Supabase Storage buckets default to private but vibe-coded apps frequently flip them to public to make image uploads render without signed URLs. Once a bucket is public, every file in it is browsable to anyone with the URL pattern.

Why it matters. Founders set the bucket to public to fix a 401 error on image rendering, then forget the bucket exists. Customer-uploaded files (invoices, ID documents, contracts) accumulate in the public bucket and become accessible to anyone who guesses or enumerates the URL path.

How to test. In Supabase Dashboard then Storage, list every bucket and check the "Public bucket" flag. For each public bucket, sample three random file URLs and confirm whether the files SHOULD be public. If any file in the bucket should be private, the bucket is misconfigured.

How to fix. Flip the bucket back to private. For files that must render without authentication (public marketing assets), keep those in a separate bucket and route customer-uploaded content through signed URLs with short expiration windows.

4. CORS allowlists are real domains, not *

What it is. Cross-Origin Resource Sharing controls which browser origins can call your API. A wildcard * allows any origin to make authenticated requests if combined with credentials.

Why it matters. Vibe-coded apps often ship with CORS set to * on Supabase Edge Functions and Vercel API routes because the AI scaffolds it that way to avoid local-development friction. In production this opens credential-bearing requests from any attacker-controlled origin including malicious browser extensions and phishing pages.

How to test. Inspect every edge function and API route's CORS configuration. Look for Access-Control-Allow-Origin: * headers. If you find a wildcard combined with Access-Control-Allow-Credentials: true, the configuration is exploitable.

How to fix. Replace * with an explicit allowlist of your production and staging domains. Maintain a separate development allowlist for localhost:3000 and remove it before production deploy.

5. Webhook handlers verify signatures

What it is. Stripe, Lemon Squeezy, Paddle and similar payment providers send webhooks with a signature header. The handler must verify the signature against a shared secret before trusting the webhook body.

Why it matters. Vibe-coded payment integrations frequently skip signature verification because the AI scaffolds the webhook handler to read the JSON body directly without checking signatures. Without verification, anyone who knows your webhook URL can post forged payment confirmations and unlock paid features.

How to test. Grep the webhook handler code for the provider's signature verification helper (stripe.webhooks.constructEvent, lemonsqueezy.webhooks.verifySignature and the like). If the handler reads request.body or req.json() directly without a signature check, it is vulnerable.

How to fix. Add the provider's signature verification helper at the top of the webhook handler. Return HTTP 400 if the signature does not match. Ensure the webhook secret is in a server-side environment variable and not in the client bundle.

6. Rate limiting on auth endpoints and RPC functions

What it is. Login, signup, password reset and authenticated RPC functions need request-rate limits per IP and per account to prevent credential stuffing, account takeover via brute force and pricing-abuse exploitation.

Why it matters. Supabase Auth ships with default rate limits but vibe-coded RPC functions and custom auth flows usually inherit no limits. An attacker with a botnet can run unlimited login attempts against your auth endpoint or unlimited RPC calls against expensive functions (image generation, AI summarization and the like) and either compromise accounts or run up your infrastructure bill.

How to test. Hit your login endpoint 100 times in 10 seconds from a single IP with random passwords. If all 100 requests return responses, rate limiting is absent. Do the same for any RPC function that hits a paid third-party API.

How to fix. Implement rate limiting via Upstash Redis, Vercel Edge Config or Supabase RLS with a request log table. For Supabase Auth, configure the project's rate limit settings. For RPC functions, add an explicit rate limit check at the function entry point.

7. IDOR testing on every API endpoint

What it is. Insecure Direct Object Reference vulnerabilities happen when an API endpoint trusts a client-supplied ID without checking that the calling user owns that ID. The classic example is GET /api/orders/123 returning order 123 regardless of who is asking.

Why it matters. Vibe-coded apps construct API routes with IDs in the path or query string. The AI rarely scaffolds explicit ownership checks because the prompt was "build the orders endpoint" not "build the orders endpoint with strict ownership enforcement." The result is endpoints that work correctly when you test as yourself but expose other users' data when probed with adjacent IDs.

How to test. Create two accounts. Have account A create a record (an order, a document, a message) and capture the record ID. Then call every API endpoint that operates on that record while authenticated as account B. If account B can read, modify or delete account A's record, IDOR exists.

How to fix. Every API endpoint that operates on a record must verify the calling user owns the record. In Supabase this is RLS doing its job (see item 1). In edge functions and API routes, add an explicit ownership check before the operation runs.

8. Supabase service-role key never in the client bundle

What it is. Supabase issues two keys per project: anon (safe to ship to browsers) and service-role (bypasses RLS, must stay server-side). Shipping the service-role key in the client bundle gives every browser visitor full database admin.

Why it matters. Most Lovable.app projects correctly keep service-role server-side, but a single misconfigured environment variable in a Vercel build or a single accidentally-imported server module from a client component can leak it. The leak is invisible in the source code and only visible by inspecting the deployed bundle.

How to test. Open your deployed app in a browser, view the page source and search the bundled JavaScript for the string pattern eyJ (the start of any JWT). Inspect each JWT to determine if it is anon or service-role. Service-role tokens have "role":"service_role" in the decoded payload.

How to fix. If service-role key is in the bundle, rotate it immediately in Supabase Dashboard, audit every commit since the leak and refactor the import path so service-role-using code lives only in server routes.

9. Vercel NEXT_PUBLIC_ environment variable hygiene

What it is. Vercel's NEXT_PUBLIC_ prefix inlines an environment variable into the client bundle at build time. Any secret accidentally given that prefix becomes part of every served JavaScript file.

Why it matters. Founders set environment variables in the Vercel dashboard without always understanding which ones get inlined. A STRIPE_SECRET_KEY accidentally renamed to NEXT_PUBLIC_STRIPE_SECRET_KEY is now in the bundle. The same pattern affects API keys for OpenAI, Anthropic, internal admin endpoints and other backend services.

How to test. List every NEXT_PUBLIC_ variable in Vercel. For each one, decide whether the value should be safe to publish to every visitor's browser. If any value is sensitive (a secret key, an internal URL, a database credential), the prefix is wrong.

How to fix. Rename the variable to remove the NEXT_PUBLIC_ prefix and use it only in server-side code (API routes, edge functions, server components). Rotate any leaked credential before pushing the rename.

10. JWT secret rotation and secret-management discipline

What it is. Supabase JWT secrets sign the auth tokens your users carry. If the secret leaks, an attacker can forge tokens that pass server validation and impersonate any user.

Why it matters. Vibe-coded apps frequently log JWT secrets during debugging, commit them to git in .env files before realizing they need to be in environment variables or post them to AI chat windows asking why authentication is failing. Each disclosure is a permanent compromise until the secret is rotated.

How to test. Search your git history for the JWT secret value or for the JWT_SECRET= environment variable pattern. If the secret appears in any commit, public or private repository, it must be rotated. Also check error logs and analytics tools for accidental secret disclosure.

How to fix. Rotate the JWT secret in Supabase Dashboard. All existing sessions become invalid (users get logged out, which is acceptable). Implement a process for future secret hygiene: secrets in environment variables only, no secrets in git history, no secrets in AI prompts.

11. OAuth callback URL tight, no wildcards or localhost in production

What it is. OAuth providers (Google, GitHub, Microsoft) require an exact callback URL match for security. If your OAuth app allows wildcard subdomains or localhost in production configuration, attackers can redirect the OAuth flow through an attacker-controlled URL.

Why it matters. Vibe-coded apps set OAuth callbacks during local development as http://localhost:3000/auth/callback then forget to update or remove that entry for production. The localhost entry in a production OAuth app is exploitable through DNS rebinding or local proxy attacks during a phishing campaign.

How to test. Open every OAuth provider's app configuration (Google Cloud Console, GitHub OAuth Apps, Microsoft Azure AD). List every authorized callback URL. If any URL contains localhost, * or a non-production domain, the configuration is loose.

How to fix. Remove all localhost and wildcard callback URLs from production OAuth app configuration. Maintain separate OAuth apps for development and production so the development app keeps the localhost callback without contaminating production.

12. PII never in URL parameters or GET request bodies

What it is. URLs are logged by every CDN, every analytics tool, every proxy and every browser history. Anything in a URL parameter (email, customer ID, payment reference) becomes part of the access log on every system the request touches.

Why it matters. Vibe-coded apps frequently put email addresses, user IDs and even payment references in URL query strings because GET requests are easier to scaffold than POST requests. The result is permanent PII in CDN logs (Vercel, Cloudflare, AWS), analytics tools (Google Analytics, Plausible, PostHog) and any third-party service the page calls. PII in logs that survive customer deletion requests is a real GDPR and PIPEDA violation.

How to test. Audit every URL the app generates. Specifically check share URLs, password reset links, email verification links, payment confirmation pages and pre-filled form URLs. If the URL contains PII, the leak is happening every time that URL is loaded.

How to fix. Move PII out of URLs into POST request bodies or server-side session state. For shareable URLs that need to reference user records, use opaque short codes that resolve to PII server-side rather than encoding the PII in the URL.

When To Buy An Audit

Vibe-coded SaaS apps reach the audit-needed threshold at one of three points:

  • About to accept first paid customer or first sensitive PII (audit before customer money or customer data lands)
  • About to raise funding (investors and acquirers will diligence the security posture, the audit shortens the diligence cycle)
  • After a public security incident or near-miss (audit converts the panic into a documented remediation plan)

For each threshold, Sherlock Forensics offers a Lovable.app security audit specifically scoped to the 12 items above plus 14 additional Supabase-specific failure modes that surface in production deployments. Court-qualified examiners, chain-of-custody documentation if the audit surfaces evidence of an existing incident and a remediation roadmap with specific code changes per finding.

Frequently Asked Questions

Can I audit a Lovable.app myself?

The 12 items above are self-auditable for a founder who knows what to test. The depth-of-coverage problem is that vibe-coded apps fail in patterns specific to AI scaffolding choices that change quarterly. A professional audit catches the failure modes that are not yet on public checklists.

Is this checklist also relevant for Bolt.new, v0 or Replit apps?

Most items port across the vibe-coded SaaS cohort because the underlying stack (Supabase plus Vercel plus edge functions plus OAuth provider) is similar. Items 10 and 11 are Supabase-specific. The rest apply to any vibe-coded SaaS shipping to production.

How long does a Lovable.app security audit take?

Standard cycle is 5 to 10 business days. Faster engagements are available for pre-launch deadlines or post-incident triage with quote adjustment.

What does the audit deliverable look like?

A written findings report with one finding per discovered issue. Each finding includes severity, reproduction steps, root cause analysis and specific remediation guidance with code snippets where applicable. Chain-of-custody documentation if the audit surfaces existing-incident evidence.

Do you sign NDAs?

Yes. Standard mutual NDA before audit kickoff. Findings reports are not published without explicit client permission.

What if I just want one specific thing audited (RLS only or just the storage buckets)?

Spot audits are available for narrower scope at lower quote. The full 12-item audit is the most cost-effective per finding when the goal is comprehensive pre-production assurance.

See Also