← All field notes
App Security

Is Bolt.new Safe for Production Apps? Reading the Security Fine Print

Bolt.new ships full-stack apps in minutes. Here's the RLS default and env-var trap its speed skips, plus a 90-second self-check.

Matthew TurleyJuly 16, 20265 min read

Bolt.new's pitch is four words: prompt, run, edit, deploy. Nothing in there about what happens to your database once it's live. Fair enough, that's marketing, not a security spec. But I'm usually the guy who shows up after the deploy button gets clicked, looking at what a full-stack app built in fifteen minutes actually shipped with underneath. So let's read past the pitch.

I review AI-built apps for a living. Lovable, Bolt, Replit, v0, Base44, whatever a founder used to get from idea to live URL in a weekend. Bolt comes up constantly, and for good reason, it's fast in a way that still catches me off guard after 20 years of watching build tools get faster.

What Bolt actually gets right

Bolt runs your dev environment, frontend and backend, entirely in the browser using StackBlitz's WebContainers, a full Node.js runtime compiled to WebAssembly. No Docker. No local install. It just runs. StackBlitz shipped that engine years before Bolt existed, and it holds up. Wire in Supabase, click deploy through Netlify or Vercel, and you've got a live, working app before your coffee's cold. Multi-file edits across the stack don't quietly break what already worked either, which is more than I can say for some earlier-generation tools. None of that is the problem.

The fine print: two gaps, one root cause

The database gap is the one every Supabase-backed builder shares. Bolt wires up the Supabase client correctly, hands you a public anon key, creates your tables. What it doesn't do is turn on row-level security by default. RLS is a Postgres feature that decides which rows a given request is allowed to see. Skip it, and the anon key that's supposed to sit safely in your JavaScript bundle becomes a master key to the whole table, no login required. The app demos fine either way, that's the trap. Nothing looks broken until someone opens dev tools and asks the database a direct question.

The second gap is more Bolt-specific, and it's the one that surprised me the first time I ran into it. Bolt's environment variable panel lets you mark a variable as exposed to the client. Reasonable feature on its own, plenty of values genuinely need to reach the browser. The problem is how easy it is to flip that toggle on a key that shouldn't cross the line, a Stripe secret or a service_role key, without registering what "exposed to client" actually means. It means the value ships inside the JavaScript bundle. Every visitor's browser downloads it. Yes, every one. At that point a "secret" key is functionally identical to pasting it straight into your HTML.

What the data says

I scanned 66 live apps built on Lovable, Bolt, Cursor, Base44, and Tempo (full methodology in the exposure report), and 32 of them ran on Supabase. Thirteen, 41%, had at least one table any anonymous request could read using nothing but the public anon key already sitting in the bundle. Five exposed something that actually mattered: user tables, subscriptions, chat logs. That pattern isn't unique to one builder, it's the default state of an unconfigured Supabase project, and it shows up across every AI builder wiring one up for you. The best-documented version of it happened to be Lovable's: CVE-2025-48757, 170 of 1,645 scanned apps hit with the same missing-RLS bug. Bolt's own version of the same root cause is the WebContainers-plus-Supabase combination above, and I go deeper on the broader pattern in the audit findings post.

The 90-second self-check

Two checks, both free, neither one touches your Supabase dashboard.

# 1. Is a table readable with no login?
curl "https://<your-project>.supabase.co/rest/v1/<table_name>?select=*&limit=1" \
  -H "apikey: <your-anon-key>"

# 2. Did a secret key ship in the bundle?
# devtools -> Sources -> search the main JS file for:
# "sk_live", "sk-", "AKIA", "service_role"

Grab your anon key from the Network tab, it's the apikey header on any request to your supabase.co domain. Run the first command against every table your app touches. Rows come back, that table has no RLS policy stopping anonymous reads. Empty array or a permissions error, you're fine on that one. For the second check, if any of those string patterns show up in a file your browser actually downloads, that key is public whether you meant it to be or not.

If you find something

Fix the RLS gap with a policy, not a rewrite. Turn on row-level security for the table, then scope reads to the owner, something like auth.uid() = user_id. Re-run the curl command and confirm it now comes back empty. Fix a leaked key by rotating it immediately in the provider dashboard, Stripe, OpenAI, AWS, whoever issued it, then move whatever called it server-side, an API route or an edge function, so the key never reaches the browser again. Neither fix touches Bolt's generated code structure. Both take under an hour.

Where Bolt sits in the pack

Is Bolt less safe than Lovable or the rest of the category? Not really, they share the same underlying pattern because they share the same underlying database. What's specific to Bolt is that env var toggle, and how confident it feels right up until the moment you find out it wasn't.

If you want someone else to run both checks against your live app, the RLS probe and the bundle scan, plus the auth and storage-bucket checks a public scan can't reach on its own, Ship Check is one option: a fixed-price, overnight pass with a ranked report, not a sales call. Run the curl command above first either way. It costs ninety seconds and it's the same test.

M
Matthew Turley, Continuum

Fractional CTO and embedded technical partner. 20+ years shipping production software.

Run a free Leak Check →