Risk #1: Misconfigured Row-Level Security
What it is: Row-Level Security (RLS) is the Postgres feature that decides which rows a database query is allowed to see or change, based on who's asking. When it's off, or on with an overly permissive policy, any client holding your app's public API key can potentially read or write any row in any table — not just their own.
Most vibe-coded apps run on Supabase (a hosted Postgres platform that Lovable and Bolt.new wire up automatically), and Supabase's security model leans entirely on RLS. The catch: Supabase tables ship with RLS disabled by default, and even when a builder enables it, the fastest way to stop a demo from throwing permission errors is a policy like USING (true) — which technically satisfies "add a security policy" while granting access to everyone. Because the browser-side anon key is, by design, embedded in every page your app serves, an open or overly-permissive policy means that key is functionally a skeleton key to your database.
| Pattern | What It Looks Like | Real-World Exposure |
|---|---|---|
| RLS disabled entirely | Table created via the builder's UI, no policy ever added | Anyone with the anon key can read/write the full table via the REST API |
| Permissive policy | USING (true) or WITH CHECK (true) | Same result as disabled — the policy exists but grants everyone access |
| Missing per-operation coverage | A SELECT policy exists but INSERT/UPDATE/DELETE are unguarded | Users can't be listed, but any user can overwrite any row |
| Correct policy | USING (auth.uid() = user_id) on every operation | Each user can only touch rows they own |
To detect it, open Authentication → Policies in the Supabase dashboard and confirm every table shows RLS as enabled — not just the ones you remember building. Then try the attacker's version of the test: open your app's network tab, copy a request to the Supabase REST endpoint, swap the row ID or user filter for someone else's, and see what comes back. If you get another user's data, the policy is broken regardless of what the dashboard claims.
To fix it, enable RLS on every table without exception, write an explicit policy for each operation (select, insert, update, delete) scoped to the authenticated user's ID or role, and test each one from a second, non-admin account. Because this is genuinely the highest-stakes item on this list, our companion deep dive, the Supabase Row-Level Security checklist for Lovable, Bolt, and Bubble apps, walks through actual policy syntax and a table-by-table audit process. If you want an outside party to verify this, that's exactly what a security audit is for. Supabase's own Row Level Security documentation is worth reading end to end even if you hire this out.
Risk #2: Hardcoded and Client-Exposed API Keys
What it is: secret credentials — Stripe secret keys, OpenAI keys, email/SMS provider keys, or worst of all a Supabase service_role key — end up shipped inside the JavaScript bundle your app sends to every visitor's browser, where anyone can extract them with dev tools.
This happens for a mundane, very human reason: modern frameworks distinguish "public" from "private" environment variables purely by naming convention. In Next.js, any variable prefixed NEXT_PUBLIC_ gets baked into the client bundle at build time; in Vite, which powers most Lovable and Bolt.new frontends, the same is true of anything prefixed VITE_. An AI assistant asked to "connect Stripe" or "hook up Supabase" will sometimes reach for whichever key is on hand and put it behind the public-prefixed variable, because from a pure "does the feature work" standpoint, that key does make the feature work in the demo. The most dangerous version of this mistake is a Supabase service_role key ending up client-side — that key bypasses Row-Level Security entirely, so even a perfectly configured Risk #1 fix is worthless if this key leaks.
- Check your built output, not your source.: Open your deployed site, view source or open dev tools → Sources, and search the bundled JS for strings like sk_live, service_role, sk-, or your provider's known secret-key prefixes.
- Check the Network tab.: Load your app and watch outgoing requests — a secret key sent as a header or query param client-side is exposed the moment the request fires, regardless of where it lives in your source.
- Know your framework's public/private split cold.: If a key doesn't strictly need to be readable by the browser, it should never carry the public prefix, full stop.
To fix it, move every privileged call — Stripe charges, sending email/SMS, anything using a service-role key — into a serverless or edge function that runs server-side, and have the client call that function instead of the third-party API directly. Rotate any key you find exposed immediately; assume it has already been scraped. This is one of the fastest, highest-leverage fixes on this list, and it's the first thing we check in a Vibe Code Health Check.
Risk #3: Missing Auth on Admin and Internal Routes
What it is: an admin panel, internal dashboard, or debug page exists at a predictable or leaked URL with no server-side check confirming the visitor is actually authorized — protected only by the fact that, in theory, nobody knows it's there.
This is "security by obscurity," and it's a natural byproduct of how these apps get built. A founder prompts "add an admin page where I can manage users and see signups," the AI generates a route and a UI, and because the founder is testing it while already logged in as themselves, everything appears to work correctly. What's missing is a server-side authorization check — one that runs regardless of what the client-side UI shows or hides. A hidden nav link is not access control; if the route itself doesn't verify the requester's role before returning data, anyone who finds the URL gets full access.
| What's Protecting the Route | Is It Actually Secure? |
|---|---|
| A hard-to-guess URL slug | No — URLs leak via referrers, screenshots, logs, and browser history |
| Hiding the nav link unless the user is an admin | No — the route itself is still reachable directly |
| A client-side redirect if !isAdmin | No — client-side JavaScript can be bypassed or simply disabled |
| A server-side session/role check before the handler runs | Yes — this is the only layer that can't be bypassed by the client |
To detect it, log out (or open an incognito window) and navigate directly to every admin, internal, or debug route your app has. If any of them render data or accept actions without redirecting you to a login screen, the route is unprotected. To fix it, add a real authorization check — verifying an authenticated session and the correct role — at the top of every sensitive route handler and API endpoint, not just in the page component that renders the UI. Pair this with the RLS policies from Risk #1 so the database layer independently refuses to return data the requester doesn't own. This is one of the clearest examples of the gap our vibe coding vs. professional development article covers in more depth.
Risk #4: No Rate Limiting on Public Endpoints
What it is: login forms, signup forms, password resets, contact forms, and any public API route accept an unlimited number of requests in an unlimited amount of time, leaving the door open to brute-force login attempts, credential stuffing, spam, scraping, and — for anything that calls a paid API — runaway bills.
Rate limiting doesn't show up in a demo because nobody hammers their own login form a thousand times while testing a feature, so it's never something a prompt asks for explicitly, and most default serverless hosting doesn't throttle requests unless you configure it yourself. The risk compounds when an AI-generated feature wraps a metered third-party API — an OpenAI call, a Twilio SMS send, a geocoding lookup — because an unthrottled public endpoint in front of a per-request-billed API isn't just a security gap, it's a direct line to an unbounded bill if someone scripts requests against it.
- Detect it.: From a terminal, script a loop of repeated requests against your login endpoint or a public form submission route and watch whether anything blocks, slows, or CAPTCHAs you after the first handful of attempts.
- Detect cost exposure.: Check your OpenAI, Twilio, SendGrid, or similar dashboards for usage spikes disconnected from real user activity — an early warning sign that an endpoint is being abused, or could be.
- Fix it.: Add rate-limiting middleware (Upstash Ratelimit and Cloudflare are common, low-effort choices), add CAPTCHA or equivalent friction to public forms, apply exponential backoff to repeated auth failures, and set hard spend caps or budget alerts on any metered API your app calls.
Teams planning to grow past MVP scale should treat this as table stakes rather than a nice-to-have — our guide to scaling a vibe-coded MVP covers the infrastructure changes that tend to accompany real traffic, and rate limiting is one of the cheapest of them to get ahead of.
Risk #5: Trusting AI-Generated Database Queries
What it is: database queries built by string-concatenating user input directly into SQL, or update logic that writes every field a form submits without checking whether the user should be allowed to set that field — two different roads to the same outcome, an attacker changing data or reading data they shouldn't.
Most AI-generated code defaults to safe patterns — parameterized queries through an ORM or query builder — which is genuinely reassuring. The failure mode we see isn't the AI ignoring safe defaults wholesale; it's the AI reaching for a shortcut on a specific feature, most often a "flexible search" or "custom filter" feature where a raw, interpolated query feels like the natural way to satisfy an open-ended prompt like "let users search by any field." The second, subtler pattern is mass assignment: an update function that takes an entire form payload and writes it straight to the database row, including fields the user submitted but should never control — a role, an is_admin flag, someone else's user_id. Neither pattern requires malicious intent from the AI; both are exploitable the moment a real user, or an attacker probing your forms, sends unexpected input.
To detect it, search your codebase for string concatenation or template-literal interpolation feeding directly into a SQL string, rather than parameter placeholders. Separately, check every update/edit form: can it, even in principle, submit a field like role, isAdmin, or userId, and if so, does your backend explicitly reject or ignore those fields for non-privileged requesters? Trying a basic injection payload in a search box is a fast smoke test, and the OWASP Top 10 for LLM Applications is worth reading for how these classic risks resurface around AI-assisted development. To fix it, use parameterized queries or your ORM's query builder exclusively, and for every write path, explicitly allowlist which fields a given request is permitted to set rather than trusting the shape of whatever payload arrives.
Risk #6 & #7: Leaked GitHub Secrets and Unmonitored Dependencies
Risk #6 — what it is: when a vibe-coded project gets exported or pushed to GitHub — to hand off to a developer, to deploy through a platform like Vercel, or simply as a backup — the .env file or hardcoded keys inside the codebase go with it, and if that repo is public (or becomes public later), those secrets are now public too. This is common because vibe coding platforms make pushing to GitHub a one-click convenience, and a founder who isn't a developer has no particular reason to know that a .gitignore entry for .env needs to exist before the first commit — and once a secret has been pushed, even privately, deleting it later does not remove it from git history, where it remains fully recoverable.
- Detect it.: Search your full git history, not just the current file tree — tools like gitleaks or truffleHog scan every past commit for credential-shaped strings, and GitHub's own secret scanning does this automatically on public repos.
- Detect it faster.: Check the repo's visibility setting directly. A surprising number of exported vibe-coded projects are public by default because nobody changed it.
- Fix it, in this order.: Rotate every exposed credential in its provider's dashboard immediately, scrub the repository's history with git filter-repo or the BFG Repo-Cleaner, then add a proper .gitignore and enable GitHub secret scanning and push protection so it can't recur.
If you're planning to hand a vibe-coded project to an agency or new developer, get this cleaned up first, or make sure it's part of what the takeover engagement explicitly checks — our guide to hiring an agency to take over a vibe-coded app covers what a proper handoff should include.
Risk #7 — what it is: the npm packages, SDKs, and third-party APIs an AI coding tool pulled in while building your app were never reviewed for maintenance status or known vulnerabilities, and nobody has set up ongoing monitoring since — so vulnerable, deprecated, or abandoned dependencies quietly accumulate. AI assistants add whatever package solves the prompt in front of them, and that's often fine, but "often" isn't "always." A professional dev team typically has Dependabot, Snyk, or an equivalent running continuously; a vibe-coded app usually has none of that wired up.
| Check | What It Catches |
|---|---|
| npm audit / pnpm audit | Known CVEs in your current dependency tree |
| Manual review of package.json | Abandoned packages, leftovers from removed features, license mismatches |
| Third-party API/integration inventory | Deprecated endpoints, integrations added for a feature that no longer exists |
| Automated scanning (Dependabot / Snyk / Renovate) | New vulnerabilities disclosed after launch — the ongoing risk, not just the day-one one |
To fix it, run a dependency audit before launch and again before any major release, prune packages left behind by abandoned features, and put continuous scanning in place so new CVEs surface automatically. This is exactly the kind of ongoing hygiene covered in our app maintenance and support guidance, and it's one of the standing responsibilities we build into an ongoing development retainer rather than a one-time fix.
How to Check Your Own App: A Self-Audit Checklist
Direct answer: you can check for all seven risks yourself in an afternoon with nothing more than your browser's dev tools, a terminal, and about twenty minutes per item.
| # | Risk | Quick Self-Check |
|---|---|---|
| 1 | Row-Level Security | Every table shows RLS enabled in Supabase; a second test account can't read another user's rows |
| 2 | Exposed API keys | Search your deployed JS bundle for service_role, sk_live, sk- |
| 3 | Unprotected admin routes | Logged out, every admin/internal URL redirects to login instead of rendering |
| 4 | No rate limiting | A scripted loop of requests to your login/contact form gets throttled, not accepted forever |
| 5 | Unsafe queries | No string-concatenated SQL; update endpoints reject fields like role from normal users |
| 6 | Leaked GitHub secrets | gitleaks/truffleHog scan of full git history comes back clean; repo visibility is intentional |
| 7 | Unmonitored dependencies | npm audit is clean and a scanner (Dependabot/Snyk) is actively running |
If you make it through that list and find one or two issues, most are fixable in a day or two of focused work. If you find more than a couple, or you're not confident you tested them adversarially enough, that's the specific gap a structured, outside review closes. Our Vibe Code Health Check runs $1,500-$3,500 over 3-5 business days and covers exactly these seven categories plus an architecture and code-quality pass, ending in a prioritized fix roadmap with cost estimates. If issues turn up that need immediate remediation, a Stabilization Sprint ($5,000-$15,000, 1-3 weeks) is the next step — full detail on every pricing tier lives in our complete 2026 pricing guide. If your app also needs feature work finished, not just secured, our guide to finishing a vibe-coded app for production covers that broader scope.
Why Frenchy Digital Runs These Audits
Frenchy Digital runs Vibe Code Health Checks on apps built with Lovable, Bolt.new, Replit, Base44, Bubble, and every other major AI builder — a focused security and architecture review that tells you exactly what's exposed and what it costs to fix, in plain language, in under a week. We're a Lovable-first agency ourselves for building new apps, which means we know precisely where AI builders tend to leave these seven gaps, because we watch the tools generate them every day. Our teams are based in Los Angeles, with colleagues in Geneva, Switzerland and Paris, France, giving founders in any timezone a fast path to a real answer about what's actually at risk in their app.
What's Included in a Vibe Code Health Check
- A full pass through all seven risk categories in this guide, tested adversarially with a second non-admin account, not just your own session.
- Row-Level Security policy review for every Supabase table, plus a check of client bundles for exposed keys.
- Admin route and rate-limit testing, dependency and git-history secret scanning.
- A prioritized fix roadmap with cost estimates — not just a list of problems.
- A clear path to a Stabilization Sprint if critical issues need immediate remediation.
Ready to find out exactly what's exposed in your vibe-coded app? Schedule your free discovery call and let's talk through which of these seven risks apply to your app before someone else finds out for you.
Ready to Build Your App?
Schedule a free strategy consultation with our team to discuss your project.
1517 S Bentley Ave Unit 204, Los Angeles CA 90025

