Skip to main content
友田 陽大
Vibe coding: shipping AI-generated code safely
セキュリティ
バイブコーディング
AI駆動開発
Supabase
RLS

Vulnerabilities in apps built with v0, Lovable, and Bolt — the AI app-builder pitfalls and a pre-launch inspection

A guide to closing, under your own responsibility, the vulnerabilities in already-launched apps with real users built by AI app builders like v0, Lovable, and Bolt. Read the real Lovable case CVE-2025-48757 (RLS gap, CWE-863, CVSS 9.3), then inspect the exported code with scan → hand-checking RLS → probe.

Published
Reading time
11 min read
Author
友田 陽大
Share

Let me state the conclusion first. AI app builders like v0, Lovable, and Bolt spit out a "working app" in minutes, but many of them "authenticate but don't authorize" — that is, login is implemented, yet the boundary of "who may read and write that row" (Supabase RLS) is left open when the app is launched. And this is not a hypothesis. The RLS gap in a Lovable-built site is registered in the NVD as CVE-2025-48757 (CWE-863, CVSS 9.3 CRITICAL) — a flaw that let an unauthenticated attacker read or write arbitrary tables.

I'm on the side that has built production SaaS solo × generative AI (Claude Code) myself. So I don't deny the speed of builders. What I want to say is the opposite — if you're already launched with real users, what you should do today is inspect the exported code "under your own responsibility." This article distills that inspection into three steps — scan → hand-check RLS → probe — and shows it with real code, from a developer's perspective.

For how to diagnose "vulnerabilities of AI-generated code in general" across four layers (SCA / secrets / SAST / DAST), see AI-generated code vulnerability assessment (2026 edition); for the buyer's-view productionization (hardening), see the vibe coding productionization guide. This article narrows to the holes specific to "builder-built × already launched."


1. What builders build, and what they don't

First, let me honestly carve out each tool's coverage. v0, Lovable, and Bolt differ in kind, but the one thing they share is "they don't build your app's specific authorization."

BuilderMainly buildsDoesn't build / leaves loose
v0 (Vercel)UI components / screensdata-access authorization, RLS, server-side validation
Lovablefull-stack scaffold + Supabase connectionrow-level owner scope (the contents of the RLS policy)
Boltfull-stack scaffold + DB connectiontenant isolation, IDOR countermeasures, state-transition validation

What builders add for you automatically is, in Aegis's terms, the plumbing close to "horizontal controls," plus the happy-path UI and login. A login screen appears, sign-up goes through, your own data shows up — up to here it's astonishingly fast.

The problem is what's beyond that — the "vertical risk." "If logged-in user A sends a legitimate request pointing at user B's ID, does B's data not come back?" This is not authentication (who you are) but authorization (what you may do), and because it depends on the app-specific 'who owns what,' builders can't fill it in as a matter of principle. Object-level authorization flaws (IDOR/BOLA) have stayed at #1 in the OWASP API Security Top 10 since 2019 precisely because this is the place that "doesn't close unless a human designs it."


2. The real case: reading CVE-2025-48757 (Lovable) from primary sources

To avoid ending in abstractions, let me cite the NVD's primary source. The description of CVE-2025-48757 reads, in the original:

"An insufficient database Row-Level Security policy in Lovable through 2025-04-15 allows remote unauthenticated attackers to read or write to arbitrary database tables of generated sites."

Let me drop the key points into a table (source: NVD — CVE-2025-48757).

ItemValue
CWECWE-863 Incorrect Authorization
CVSS 3.19.3 CRITICAL (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N)
AffectedLovable through 2025-04-15
Published2025-05-29
StatusDisputed (vendor disputes it)

Three things to read out of this:

  1. The precondition is "unauthenticated (PR:N)." That is, arbitrary tables were within reach without even logging in. This is the textbook case of "authorization is open outside authentication," exactly the structure of "authenticates but doesn't authorize".
  2. The root cause is an "insufficient RLS policy." Whether RLS was turned off entirely or was a wide-open policy like USING (true) differs by incident, but both converge on the single point that the design of "scoping rows to the owner" was missing.
  3. The status is Disputed — the vendor argues that "protecting the data of generated apps is each user's responsibility." This isn't an excuse; it should be read as a declaration of the responsibility boundary. The builder makes the plumbing, but row-level authorization is "your responsibility" — the CVE officially says so.

That's exactly why the "inspect it yourself" of the following chapters is the crux.


3. Why builder-built apps leak right here

The structural reasons boil down to three.

① "Login works" performs "looks safe"

The moment you wire up Supabase Auth and a sign-in screen appears, many people feel "I could authenticate, so it's protected." But authentication is merely identity checking at the entrance. Handing out entry passes and locking each room are different jobs. If the lock called RLS is weak, a legitimate entry pass (a valid login, sometimes anonymous access) gets you into every room.

② The anon key is handed out "on the premise it goes to the client"

Supabase's anonymous (anon) key is a public key premised on being embedded in the browser. That itself is as designed; exposure ≠ vulnerable. What's dangerous is the state of "the anon key being handed out to the world while RLS is not correct." The defense boundary is not the secrecy of the key but the contents of the RLS policy. Conversely, if the service_role key, which bypasses RLS entirely, slips into the client side or a builder preview, that's an instant fail — as Supabase officially states, service_role runs with PostgreSQL's BYPASSRLS and ignores RLS entirely.

③ A builder's default is optimized for "works first"

The first goal of the output is "the demo runs." So tables with no RLS, or wide-open USING (true) policies, tend to remain unquestioned. This isn't a gut feeling — it's a trend visible in a primary study of public Supabase apps:

In a primary study of 1,000 public Supabase apps, about 9.2% of apps with RLS had a policy that "authenticates but doesn't scope rows to the owner" (a lower bound, since the sample is public repositories; owner-unscoped ≠ instantly vulnerable, needs confirmation).

"About 1 in 10 as a lower bound," and that's the figure for public repositories with relatively higher security awareness. Among real products built with a builder and launched while staying private, I'd estimate it's felt to be higher than this.


4. Drawing the responsibility boundary correctly — the substance of "your responsibility"

You don't need to take CVE-2025-48757 being Disputed negatively. If anything, it's clear as a contract. Diagrammed, the boundary looks like this.

[ ビルダー/プラットフォーム側 ]        [ あなた(開発者/事業者)側 ]
  ・UI・画面の生成                       ・行レベルの認可設計(RLS の中身)
  ・Auth(サインイン)の配線             ・auth.uid() による所有者スコープ
  ・ホスティング・足回り                 ・service_role 経路の所有権チェック
  ・anon キーの発行                      ・テナント分離・IDOR 対策
  ─────────────────────                  ─────────────────────
  「動く」までを速く                     「漏れない」を担保する

In other words, the decision to use a builder itself is fine. But you need the awareness that "the moment you export/launch, the entire right column moves to you." Filling this in is the inspection procedure in the next chapter.


5. The post-export inspection procedure: scan → hand-check RLS → probe

On the premise that you're already launched with real users, here are the three steps that close the most impactful holes first. The order has meaning — suspect statically, confirm the meaning of authorization with your eyes, back it up dynamically.

Step 1. scan mechanically for "horizontal holes" and "RLS suspicions"

Right in the root of the exported code (or the repo the builder connected), run static analysis first. No install, no config.

# いまのプロジェクトをそのまま静的解析する
npx @aegiskit/cli scan

What scan picks up are the holes frequent in builder-built apps:

  • Tables with no RLS / wide-open USING (true) policies / missing WITH CHECK (reads supabase/migrations/**.sql)
  • Leaked secrets (whether a service_role key or token is baked into the code)
  • Missing security headers / CSP, rate limiting, CSRF (= horizontal controls)
  • Tainted input without an ownership scope = suspected IDOR (tracked with source→sink data flow)

What's important here is that Aegis draws the line honestly. Horizontal controls like headers and rate limiting are subject to automation and auto-fixing, but vertical risks like authorization/IDOR and RLS design are kept to "detect and warn." It never claims to have fixed what it can't. So read scan's output as a "list of suspicions," not "proof of safety."

Step 2. Read RLS by hand — confirm "authentication" and "authorization" separately

The RLS that scan flags is, in the end, for a human to confirm the meaning. The machine looks at the shape of the policy, but it doesn't know the business-rule meaning of "is this table intended to be shared, or owner-only?" There's one thing to read — whether rows are scoped to the owner with auth.uid().

-- ❌ ありがちなビルダー既定:認証はするが、行を所有者に絞らない
--    ログインさえしていれば、他人の行も全部読める(IDOR の温床)
create policy "read" on public.orders
  for select
  using ( auth.role() = 'authenticated' );

-- ✅ 所有者スコープ:自分が所有する行だけを読める
create policy "read own" on public.orders
  for select
  using ( auth.uid() = user_id );

-- ✅ 書き込みは USING だけでなく WITH CHECK も要る(なりすまし INSERT を止める)
create policy "insert own" on public.orders
  for insert
  with check ( auth.uid() = user_id );

USING (true) or auth.role() = 'authenticated' is not always wrong. If the intent is "this table really is shown to everyone," like public posts on a message board, it's correct. So the criterion is one thing only — "is this table intended to be public?" If the intent is "owner-only," it needs a scope via auth.uid(). This "authentication vs. authorization" split, and the write bypass when you drop WITH CHECK, are each covered in detail in the authenticates-but-doesn't-authorize problem (not repeated here).

Alongside, visually check these "builder-specific" items too:

Where to lookWhat to confirm
Where the service_role key liveswhether it's exposed to the client, preview, or a public repo (rotate immediately if so)
SECURITY DEFINER functionswhether search_path is pinned (unpinned is an entry point for privilege escalation)
Edge Functions / server processingwhether ownership is re-checked internally even for anon-routed calls
Tables with no RLSa "just created" table is exactly where a blank policy lingers

Step 3. probe to back up the "suspicions" at runtime

Static analysis is "suspicion"; dynamic analysis is "confirmation." Finally, send a non-destructive probe to a staging environment you own and check whether the suspicions found in Steps 1–2 actually reproduce.

# 自分のアプリ(所有物)へ、安全・スコープ固定・非破壊で裏取りする
# テストIDを渡すと missing-auth / IDOR の再現も確認できる
npx @aegiskit/cli probe https://staging.example.com --correlate

I'll stress this hard — the only thing you may point probe at is an environment you own and control. Sending it to someone else's site without permission can constitute unauthorized access, even if non-destructive. probe is designed to fall to the safe side (localhost default, scope-pinned, with a request budget), but "only against what you own" is the operator's responsibility.

Fix only what reproduced, as a "confirmed exposure," at top priority — this static × dynamic correlation is the crux of crushing only the real harm without drowning in noise.

And then pin it in CI

To keep a hole you closed from recurring, wire scan into the CI gate. Only high-confidence detections fail the build, and SARIF stays in GitHub code scanning. Every time you "regenerate" with a builder, the machine stops the same hole from reopening. The CI gate's design philosophy is in the quality gate for AI-driven development, and the approach of pinning authorization in the spec before generation is in the spec-driven development workflow.


6. The honest scope — what the tool "doesn't do"

Finally, to avoid breeding complacency, let me spell out the boundary. "I ran scan, so it's safe" is the mindset that produces the worst security outcomes.

  • npx @aegiskit/cli scan automates horizontal controls and keeps vertical risks (authorization/IDOR, RLS design, tenant isolation) to detect-and-warn. Closing them is your design decision.
  • A clean result means "you haven't stepped on the common traps," not "this code is safe."
  • Data-flow analysis is mostly intraprocedural (within a function); flows that cross modules or frameworks can be missed.
  • It does not replace RLS design, code review, threat modeling, and manual penetration testing — it complements them.

Even so, the value of mechanically closing the most frequent holes is large. Without killing the builder's speed, you can systematically close the "authenticates but doesn't authorize" state even after launch. Start with the free OSS, one command against your current project — Aegis (npx @aegiskit/cli scan). And if the vertical risks that scan surfaced need real design work to close, that's where I come in.

Summary

  • v0, Lovable, and Bolt build fast up to "it works," but row-level authorization (RLS) is your responsibility. CVE-2025-48757 (Lovable, CWE-863, CVSS 9.3, Disputed) officially shows that responsibility boundary.
  • Already launched with real users? It's not too late. With three steps — scan (suspect statically) → hand-check RLS (confirm the meaning of authorization) → probe (back it up against what you own) — you can mechanically close the most frequent holes.
  • Anon key exposure is normal; what's dangerous is exposure while RLS stays loose. Never expose the service_role key to the client. The defense boundary is not the key but RLS.
  • The tool automates horizontal controls and keeps vertical risks to detect-and-warn only. Closing them is human design — the tool doesn't replace it, it complements it.

Frequently asked questions

Is an app built with v0, Lovable, or Bolt safe to launch as-is?
No. The builder adds authentication (login) and UI automatically, but row-level authorization (Supabase RLS) — 'who may read that row' — is your responsibility. In fact, an RLS gap in a Lovable-built site was registered as CVE-2025-48757 (CVSS 9.3). Even after launch, you can close the holes by inspecting the exported code.
What kind of vulnerability is CVE-2025-48757?
The row-level security (RLS) policy of sites generated by Lovable (through 2025-04-15) was insufficient, letting an unauthenticated attacker read or write arbitrary tables. CWE-863 (Incorrect Authorization), CVSS 9.3 CRITICAL. The vendor disputes it as 'data protection is each user's responsibility' (disputed) — the responsibility boundary is exactly the point of contention.
I've already launched and have users. What should I do now?
First, inspect the exported (or connected) code and the Supabase migrations. Use `npx @aegiskit/cli scan` to surface no-RLS, USING(true), and leaked secrets; next, confirm by hand that rows are scoped to the owner with auth.uid(); finally, back it up at runtime against a staging environment you own with probe. These three steps mechanically close the most frequent holes.
The anon key is visible to the client — is that dangerous?
The anon (public) key is designed to appear in the browser; that alone is not a problem. What's dangerous is when the anon key is exposed while RLS is not correct. The defense boundary is not key secrecy but the RLS policy. Conversely, the service_role key bypasses RLS entirely, so it must never be exposed to the client.
If I add Aegis, will it become completely safe?
No. A tool that claims that is itself dangerous. `npx @aegiskit/cli scan` automates horizontal controls (headers, rate limiting, secret hygiene) and keeps vertical risks like authorization/IDOR and RLS design to detect-and-warn. Closing them is your design decision; the tool doesn't replace it, it only complements it.

References

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

Already live, with real users?

A security audit of your AI-generated app — authorization, RLS, tenant boundaries

An app that's live with real users needs more than “it works.” The vertical risks in authorization and RLS design that no tool can fix — I take them on as an audit, from review through fix design and implementation. Feel free to visualize the current state with the free OSS first.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading