EngineeringCodeCross Team
Supabase hardening after an AI builder (2026)
After Lovable Cloud, Bolt, FlutterFlow, Rocket, or a0 leave you on Supabase: prove Auth subjects, RLS+grants, no client service_role, Storage policies, and a restore drill before ads. CodeCross LLC frames the 2026 evidence gates.
Engineering
13 min
- RLS
- Grants
- Auth
- Subjects
- Restore
- Drill
Revoke, then re-grant
JWT + roles
Not a host badge
Citation-ready definition: Supabase hardening after an AI builder in 2026 is the evidence sequence that proves Auth subjects, Postgres Row Level Security plus grants, no client `service_role`, Storage policies, and a restore drill you have actually run—before ads or stores. You take a Lovable Cloud, Bolt, FlutterFlow, Rocket.new, or a0 export that already sits on Supabase and make the project org-owned, policy-tested, and recoverable. Connecting Git is not the harden.
The expensive 2026 failure is rarely “we picked the wrong builder.” It is “we bought traffic on a schema that still grants `anon` and `authenticated` every verb, with RLS off or policies that nobody tested, and a `service_role` string in the browser.” A converting preview hides that gap. A stranger with the publishable key will not.
This article is the database ownership + RLS evidence playbook after any AI builder that left you on Supabase. It is not the founder calendar on AI-builder exit plan for founders, not the stay/harden/rewrite verdict on rewrite vs harden, and not the Lovable web-exit how-to on get off Lovable without a full rewrite. Those pieces stay the calendar, the verdict, and the tool exits. This page is the Postgres proof. Soft CTA: when grants or restore are ambiguous, book.
Use the short landers for punchy lists: supabase-hardening is the RLS / Auth / restore punch list; auth-and-data names sessions and rules; security-checklist is the shared pre-ads scan; production-ready is the operating contract; MVP hardening is the pre-ads list. The vibe coding hub maps tool exits. This long-form essay stays the evidence playbook.
Why builder-generated schemas fail open
AI builders are paid to make the preview work tonight. Postgres is paid to refuse the wrong row. Those incentives collide. The generated table looks finished because the canvas can insert. The database is unfinished because nobody revoked the default grants.
Supabase’s securing your API guide is explicit: on existing projects, tables created in `public` receive `SELECT`, `INSERT`, `UPDATE`, and `DELETE` for `anon`, `authenticated`, and `service_role` by default. Functions receive `EXECUTE`. Those grants make new objects reachable through the Data API even when you did not intend to expose them. The Row Level Security guide adds the second half: adding policies does not take those grants back. A table “protected only by policies” still hands `anon` an insert path if you never revoked the grant.
Five fail-open patterns show up after a builder export:
- RLS is off, or RLS is on with no policies. Dashboard-created tables often start with RLS on. Tables created in a generated SQL migration often do not. Either way, a role with a grant and no matching policy is a hole or a lockout—you cannot tell until you test.
- Policies exist; grants were never revoked. The builder wrote `create policy … using (true)` and left `anon` with every verb. The policy is theater.
- Views stay `SECURITY DEFINER`. Postgres creates views as security definer by default. A view over a protected table hands out every row the policies were meant to withhold. The RLS guide says to set `security_invoker = true` on Postgres 15+.
- `service_role` is in the client. The secret key bypasses RLS. Securing your data is blunt: secret and service role keys are never safe to expose. Older projects still show an `anon` key—treat it like a publishable key, not a secret, and still pair it with RLS.
- Storage buckets inherited “public” because the upload demo needed it. Storage access control starts closed. Builder prompts often open it. Restore of the database does not restore deleted objects.
None of that is a reason to rewrite the screens. It is a reason to stop buying ads until you can name who reads a row after a former teammate leaves. If you cannot map that, mark the layer on rewrite vs harden.
Ownership map: seats the database actually needs
The founder exit calendar seats Git, secrets, billing, and restore. This page seats the Supabase project. Exit of the database is complete when a second person, using a company org seat, can rotate the service role, restore onto a project you admin, and prove `anon` cannot insert. Until then you are renting a generated backend.
Supabase ownership map — name a human on every row
Project org seat
Own
Company org + 2nd owner
Proof
Second human can rotate
Fail
Personal GitHub-OAuth only
Auth settings
Own
Redirects + providers you list
Proof
Session subject = auth.uid()
Fail
Builder callback still live
Migrations in Git
Own
Org repo + reviewable SQL
Proof
Clean apply on a new project
Fail
Dashboard-only schema
service_role vault
Own
Server/edge only, named vault
Proof
Client bundle has no secret
Fail
Key in .env committed / browser
Storage buckets
Own
Policies per operation
Proof
Stranger cannot list or overwrite
Fail
Public because the demo uploaded
Edge functions
Own
Secrets in project, not chat
Proof
Function uses service_role, client does not
Fail
Browser calls privileged RPC
Backups / PITR
Own
Plan you can restore from
Proof
Restore to a project you admin
Fail
“Supabase has backups”
Write the proof next to the name. “We have Supabase” is false until the project lives in a company organization and a second owner can open it. “We have Auth” is false until Supabase Auth issues a JWT whose `auth.uid()` is the subject your policies check—and a signed-out request is the `anon` role, not a fake user. “We have backups” is false until you have restored. Database backups cover the database, not Storage objects. Free-tier projects are told to `db dump` and keep an off-site copy. Deleting the project deletes the backups.
Builder docs do not contradict this. Lovable Cloud is a built-in backend on Supabase’s foundation; Git sync keeps migration files and never the rows. There is no one-click Cloud → your Supabase move. Bolt’s Supabase integration is claim-or-connect: claim the Bolt Database into an org you own, or connect a project you already admin. Connecting over an existing Bolt Database can replace the connection and lose data. FlutterFlow’s Supabase setup will happily let you uncheck RLS “to get started,” then reminds you to turn it back on before deploy. Push to GitHub overwrites the `flutterflow` branch—it does not write policies.
Evidence gates: mark once, then refuse slides
A working session should produce marks, not vibes. Open the Supabase dashboard, the org repo, and a second browser that is not signed in. Walk each gate once. The RLS procedure is the vendor order: enable RLS, set grants, write a policy per operation, write a `supabase/tests/` file, run `supabase test db`. Until the suite passes, you do not know whether the policies do what you intended.
RLS evidence gates — mark each table once
RLS + grants
Mark
Enabled; revoke; re-grant
Proof
42501 without the grant
Fail
Policies only, grants intact
Policy per operation
Mark
select/insert/update/delete
Proof
No FOR ALL theater
Fail
using (true) on writes
supabase test db
Mark
pgTAP allow + deny
Proof
Suite green on CI
Fail
“We clicked Policies”
Indexes on policy cols
Mark
Leading btree on filters
Proof
user_id / team_id indexed
Fail
Seq scan per row check
No recursive policies
Mark
Membership via definer fn
Proof
Policy does not re-enter
Fail
team_members reads itself
Views + secrets
Mark
security_invoker; no client secret
Proof
View obeys table RLS
Fail
Definer view / service_role in JS
Close a gate with an artifact. “RLS is on” is not evidence until `anon` gets `42501` on a write you never granted, and `authenticated` as user A cannot read user B. “We have tests” is not evidence until `supabase test db` is in CI and the file names the real table—not a fixture only the tests use. “We indexed” is not evidence until the filter column is first in a `btree`; a composite primary key on `(team_id, user_id)` does not index `user_id`. The RLS guide’s recursive-policy section is the membership trap: a policy that reads `team_members` to decide access to `team_members` loops until you wrap the lookup in a `security definer` function owned by a role that can bypass RLS.
Auth is a subject, not a login screen. Supabase Auth authenticates; RLS authorizes. The JWT is what the Data API scopes. A policy that never mentions `auth.uid()` and never limits `TO authenticated` is a public table with extra steps. Pair this page with auth-and-data when the session path is the gap; stay here when the gap is grants, views, and tests.
Week sequence: inventory before traffic
The founder calendar sequences Git and seats. This sequence sits inside those weeks and owns the database. The ranges below are studio-observed operator weeks for a small product with a readable schema—not a bid, not a vendor SLA, and not a reason to pad a sales deck. Use them to reserve calendar. Feature work that adds a table waits until the matching gate has a name.
Supabase harden weeks after any AI builder
01 →
Inventory
Org seat, tables, views, buckets, keys, Auth providers
02 →
Enable RLS + grants
Revoke anon/authenticated, re-grant the verbs you mean
03 →
Policies + tests
One policy per operation; supabase test db / pgTAP
04 →
Auth / session
Redirects, JWT subject, signed-out = anon
05 →
Storage
Bucket policies; no public list you did not intend
06 →
Restore rehearsal
Dump or PITR onto a project you admin; Storage is separate
07
Traffic
Staging campaign only after the suite and the drill
Illustrative operator weeks on a Supabase harden calendar
weeksUnpriced schema sprint in-builder
6–12 wks
More tables, same open grants. Incident later.
Illustrative operator weeks — not measured traffic, not a Source: Admin analytics series. Unpriced schema-sprints in the builder often cost more than the harden sequence when the first stranger arrives.
Read the chart as a reservation, not a promise. Week 0 is cheap and non-negotiable: if you cannot list every public table, every view, and every place `service_role` is referenced, later weeks thrash. Weeks 1–2 are the Postgres weeks—the ones the builder skipped. Weeks 2–3 are Auth and files. Weeks 3–4 are the first weeks that feel like operations: a restore you watched fail once, then succeed. The last bar is the silent kill priced as calendar: a schema sprint that never closed a grant.
Overlap the bars when you have two humans. Do not overlap them with a new onboarding table. If a builder prompt would add a bucket, an RPC, or a `SECURITY DEFINER` function, it waits until `supabase test db` is green on the tables you already have. The production-ready ship gate still applies at the end of week 4: no cutover without a tested rollback. The security checklist is the punch list; this page is whether you are allowed to be filling it yet.
Builder-specific notes — not a noun-swap FAQ
The gates are shared. The first proof is not. Do not paste a Lovable Cloud export onto a FlutterFlow `flutterflow` branch, or a Bolt claim onto a Rocket connector, and call the harden done.
| Builder | What you inherit | First database proof | What does not change |
|---|---|---|---|
| Lovable | Cloud (built-in backend) or a connected Supabase project | Org seat on your Supabase, or a Cloud export + rebuild; Git sync has migrations, not rows | RLS+grants+tests still required; no one-click Cloud→Supabase |
| Bolt | Bolt Database or connected Supabase | Claim into an org you own; do not Connect over live data | Version History does not restore the database |
| FlutterFlow | Firebase or Supabase; OAuth or pasted anon key | Setup with RLS re-enabled; `users` table + `auth.users` FK | Push to GitHub overwrites `flutterflow`; policies live in Supabase |
| Rocket.new | Supabase/Stripe connectors on Launch hosting | Remap connectors to credentials you admin; staging≠prod | Launch URL is not a restore |
| a0.dev | Export or Git sync onto a host you choose | Same gates on your project ref | The export is a tree, not Auth subjects |
Lovable Cloud versus your own Supabase is a product decision, not a slogan. Cloud stays supported; existing Supabase integrations stay supported; remixing a Cloud project produces another Cloud project. If you need org-owned SQL, vault, and a restore you can drill without asking Lovable to export, move. If Cloud already has the rows and you cannot yet export safely, stay on Cloud through rehearsal and treat the export as a door on the founder exit calendar—not as a reason to skip RLS on the instance you still have. Tool-exit punch lists stay on Lovable MVP hardening and get off Lovable.
Bolt persist is claim-or-connect. Claim when the rows already live in a Bolt Database and you are a Supabase org owner. Connect when you already have a project and the Bolt project does not hold data you care about. Custom SMTP for Auth mail only applies after the project is under your account. That is a persist fact, not a reason to noun-swap the Lovable Cloud FAQ.
FlutterFlow is Firebase-or-Supabase. If the project is on Firebase, this page is the wrong essay—do not pretend Firestore rules are RLS. If it is on Supabase, the canvas will bind to whatever schema Get Schema last saw. Re-enable RLS before a closed test. Custom code lives off the `flutterflow` branch. The store path is a different spine; the row-access proof is still this one.
Rocket.new connectors and a0 exports change the host and the env tabs. They do not change grants. If Launch or the a0 preview still holds the only `service_role`, you have not hardened. Remap, then run the same tests.
Kill criteria: pause features, not the product
A harden without kill criteria is a wish. Write the date you will stop prompting new tables, the date you will hire, and the evidence that sends you back to a bound rewrite of the store. Put those dates on the same calendar as the gates.
Pause feature work when any of these are true:
- Paid traffic, a partner demo, or store Submit is inside two weeks and `anon` still holds a write grant, or RLS is off on a public table.
- `service_role` or a secret key still exists in a client bundle, a committed `.env`, or a chat transcript.
- You have never restored the database onto a project you admin, or you cannot name whether Storage objects would come back.
- A new prompt would add payments, uploads, health/finance data, or a `SECURITY DEFINER` RPC.
- Two people disagree about who is org owner on the Supabase project.
Hire (or book a partner for a named gate) when the founder cannot be the second project owner and cannot run `supabase test db`; when views are definer and nobody can rewrite them; or when week-0 inventory slipped past the kill date. Hire for grants+tests+restore—not for a greenfield UI. Austin app development company is the studio brief if you want that conversation; Austin mobile app development if the next door is a store artifact that still sits on this database.
Stop this harden and reopen [rewrite vs harden](https://www.codecross.com/articles/rewrite-vs-harden-after-ai-builder-2026) only when the row-access gate is irreparable: you cannot map who reads a row; identity collides across Auth and a shadow `users` table with no supported bridge; or the only way to “make it work” is to keep `service_role` in the browser. That is a verdict on the model. Write it down. Do not “harden” a store you have proven you cannot authorize by adding prettier policy names.
If none of those have fired, keep the screens. Sequence the gates. The cheapest honest path in 2026 is RLS evidence on DNA users already accepted—not a second backend, and not another month of builder tables.
FAQ
Does enabling RLS mean my Supabase project is hardened?
No. RLS without revoked grants is incomplete. Policies without a `supabase test db` file are unverified. The vendor order is enable, revoke, re-grant, policy per operation, then pgTAP. A green Policies page in the dashboard is a start. It is not the gate.
Is the anon key a secret I must rotate out of the client?
No. Treat the publishable key—and the older `anon` key on existing projects—as a project identifier. It must ride with RLS and least-privilege grants. Rotate and vault the secret and service_role keys. Those bypass RLS. If either is in the browser, stop shipping features and pull it into an Edge Function or a server you admin.
Do Lovable Git sync or FlutterFlow Push to GitHub move my rows?
No. Lovable’s Git sync keeps code and migration files, never database rows. FlutterFlow’s Push to GitHub overwrites a `flutterflow` branch with generated Dart. Rows, Auth users, Storage objects, and the service_role stay in the backend you have not yet seated. Connecting Git is a tree door. This page is the data door.
Should I claim a Bolt Database or connect a new Supabase project?
Claim when the live rows already sit in a Bolt Database and you are a Supabase org owner—Bolt’s own warning is that Connect on a project that already has a Bolt Database can replace the connection and lose data. Connect when you already admin a project and the Bolt preview does not hold data you need. After either path, you still run the same RLS+grants+tests sequence. Version History will not restore the database.
Does a Supabase daily backup restore my uploaded files?
No. Backups cover the Postgres database. Storage objects are not in that file; the database only holds metadata. A restore also does not bring back custom-role passwords on daily backups. Drill a restore onto a project you admin, and separately prove you can recover the buckets you care about. “Supabase has backups” is not a drill.
When do I pause new tables versus bound-rewrite the store?
Pause tables when a grant is still open, `service_role` is still in the client, or restore has never been run and traffic is close. Bound-rewrite the store when you cannot name who reads a row after a teammate leaves, or when Auth subjects and a shadow `users` table collide with no supported bridge. Closable gaps—missing tests, a definer view you can recreate, a public bucket you can lock—are harden work on this calendar.
Is there a CodeCross supabase-hardening lander I should use instead?
Yes. Use supabase-hardening for the short punch list. This essay stays the evidence playbook for the gates. Use auth-and-data and the security checklist when the gap is sessions or the shared pre-ads scan. Use the founder exit plan for the calendar around the database.
Next steps
Walk the gates in order. Inventory the project. Move it to a company org. Enable RLS, revoke, re-grant. Write a policy per operation and a test file that names the real table. Pull `service_role` off the client. Lock Storage. Restore onto a host you admin. Then—and only then—rehearse traffic. Pause any prompt that opens a new table while a gate is red.
CodeCross LLC is an Austin-registered product studio (1606 Headway Cir STE 9212, Austin, TX). We help operators turn a generated Supabase into a database they can authorize: grants first, tests second, a rewrite of the store only when the row-access gate is irreparable. The Austin app development company page is the studio brief; Austin mobile app development is the store-binary engagement. Company-level evidence lives on proof. When you want a second pair of hands on the policies, book a conversation.
The goal is not to punish vibe coding. It is to stop treating a working preview as proof that `anon` cannot insert. Ownership of Supabase is evidence. RLS is a grant plus a policy plus a test. Get those three right and most teams never need a second backend.
Directional range in a few questions — not a binding quote.
Ready to price an Austin build?
Bring the problem, the users, and a budget ceiling. We’ll tell you whether an app is the right next spend — and what the first year actually costs.
Prefer writing? Send project details on the contact page.