Altered Craft

Altered Craft

Your Coding Agent Forgot to Lock the Door

A practical guide to securing the Supabase project your AI helped you build

Sam Keen's avatar
Sam Keen
Feb 18, 2026
∙ Paid

Coding agents are remarkably good at building things. They scaffold databases, wire up auth, and deploy functional apps in minutes. What they consistently fail to do is secure what they’ve built.

The numbers bear this out. Security researchers found that 83% of exposed Supabase databases involved Row Level Security misconfigurations. The Moltbook breach alone exposed 4.75 million records, including 1.5 million API tokens. A CVE against Lovable revealed 170 more AI-generated apps, all with completely open databases. In each case, the pattern was the same: the app worked, the data was exposed, and the developer didn’t know.

If you’ve built a Supabase project with a coding agent, here’s how to find out where you stand.

What to check first

Supabase gives you a Postgres database with auto-generated REST APIs, auth, and storage out of the box. That convenience is why coding agents reach for it, and why these security gaps are so common. Before you start, install the Supabase CLI if you haven’t already. Between the CLI and the Dashboard’s Security Advisor, you can run a full security audit locally before you ever ship.

Three things matter most, in order of severity.

RLS on every table

Row Level Security is the single most important security control in Supabase. It determines who can read and write which rows. Without it, anyone with your project’s public API key (which is embedded in your frontend JavaScript, by design) can query your entire database.

The critical detail: RLS is disabled by default on tables created via SQL or migrations. Tables created through the Supabase Dashboard have had RLS enabled by default since 2025, but if your coding agent generated migration files or ran SQL directly, it may have skipped this step.

How to check: Open your Supabase Dashboard, navigate to Database > Security Advisor, and look for an error mentioning “RLS Disabled”.

This flags any table in the public schema without RLS enabled. If you see results here, enable RLS immediately and add appropriate policies before doing anything else. Supabase’s Hardening the Data API guide covers additional measures worth reviewing, including restricting table-level grants and exposing a custom schema instead of public.

The Supabase Security Advisor showing an error for missing RLS

If you don’t use Supabase’s REST or GraphQL API at all (for example, you only connect via a backend, not a web app), consider disabling the Data API entirely under API Settings. No API surface means no API exposure.

It helps to understand the two layers at work. Table-level privileges control which operations are possible (SELECT, INSERT, UPDATE, DELETE). RLS policies control which rows are accessible. You need both. A table without RLS has the first layer but not the second, meaning anyone with API access can operate on every row.

One nuance: enabling RLS without adding any policies doesn’t expose data. It does the opposite. It locks everyone out, including your application. That’s a different kind of broken, but at least it’s not a breach. Add policies after enabling RLS, not before.

One performance detail worth knowing: if you’re writing policies that check auth.uid(), wrap it in a subquery as (select auth.uid()). Without the subquery, Postgres re-evaluates the function for every row. With it, Postgres evaluates once per query.

Service role key exposure

While I was writing this piece, Supabase overhauled their API key model. The timing is relevant. New projects now get publishable keys (sb_publishable_...) for frontend use and secret keys (sb_secret_...) for backend operations. Secret keys cannot be used in browsers at all. They return a 401. If a coding agent accidentally drops a secret key into your frontend code, it won’t work there. That’s a safety net the legacy service_role key never had.

But a published secret key is still a serious problem. It won’t work in a browser, but anyone who finds it can use it from a backend script to bypass all RLS and access your entire database. Search your codebase for any elevated key: .env files that might be committed, client-side code, configuration that gets bundled into your frontend. Coding agents sometimes use elevated keys during development because it’s the path of least resistance. If one is exposed anywhere, rotate it immediately from your Dashboard, then refactor the app to not require it.

If your project still uses legacy keys (check your Dashboard under API Keys), the same principle applies. The anon key is public by design, safe as long as RLS is doing its job. The service_role key bypasses all RLS. Legacy keys are deprecated and will be removed late 2026. Migrating to the new model gives you instant revocation and per-key access logging.

Storage bucket policies

Storage security follows the same RLS model as database tables, and it’s the thing most people forget to check. If you’re storing user uploads, verify that your storage policies restrict access to the file owner. The common pattern is folder-based: each user’s files live under a folder named with their user ID, and the policy enforces that boundary.

Without storage policies, uploaded files are accessible to anyone who can guess (or enumerate) the file path.

What I found on my own project

User's avatar

Continue reading this post for free, courtesy of Sam Keen.

Or purchase a paid subscription.
© 2026 Sam Keen · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture