Skip to content
Undrop

Undrop — a safety net for your Supabase MCP

Rows your agent doesn't get to delete.

Undrop sits between your coding agent and your Supabase database. It blocks destructive SQL before it runs and holds it for your approval, and it takes a restore point before anything risky.

Example — the statement that doesn't get to run

DELETE FROM users;

no WHERE clause · public.users · 1,204,553 rows

The verdict is real — run this exact statement through undrop-guard yourself and you'll get the same one. The scenario and the number are illustrative: what a block looks like, at the scale of the statement shown.

The proxy that blocks is live and serving in production, and the classifier that decides what gets blocked is on npm — you can run it yourself right now. Create an account, connect your database with the guided setup, and Undrop guards it from that moment on: blocked statements land on your dashboard, where you approve or deny each one yourself. Snapshot and restore are built and verified end to end against real Postgres.

Free blocks destructive SQL forever on one project, no card required. Pro is $19 a month per project.

The seatbelt for your Supabase MCP. Your agent keeps its full power — you get an undo button.

The agent is the engineer now. Nobody reviews the migration.

A CLAUDE.md rule, a prompt, a local hook — all inside the blast radius, all ignorable by the thing they're meant to restrain. Protection has to live outside the agent's reach.

Supabase's own docs say it
“Never connect the MCP server to production data. Supabase MCP is only designed for development and testing purposes.” Most solo builders do it anyway — production is the only database they have.
It already happened, publicly
An agent emptied SaaStr’s production database in July 2025. It made the fear mainstream, but Supabase community threads had reports before that — usually via an agent-run migration, with no backup, because the Free tier ships with none.
The exposure is measured, not hypothetical
CVE-2025-48757: 170+ apps built on Lovable shipped without row-level security. A scan of 20,052 indie-directory apps found 11% exposing Supabase credentials, some of them service_role keys.

Blocked, then snapshotted, then restorable

Three states, in this order, every time something risky happens to your database.

  1. 01

    BlockedThe statement stops before it runs

    TRUNCATE, DROP, a DELETE or UPDATE with no WHERE clause, RLS getting disabled — caught by a real SQL parser, not a regex guess. The agent gets a structured error back, and the statement waits for a human decision on the dashboard — nothing on the agent's channel can approve it.

  2. 02

    SnapshottedA restore point exists before the risk does

    Before anything risky runs — including statements you approve — Undrop saves a restore point first: your data as a pg_dump archive, a changed function or view as the plain SQL that defined it. Both restore with stock Postgres tooling — pg_restore or psql — even if Undrop vanished tomorrow.

  3. 03

    RestoredBack to before it happened

    Full database or a single table, to any restore point. The restore itself is snapshotted first — restores are undoable too.

What this doesn't do

A safety product earns trust by admitting where the edges are, not by hiding them.

Guard mode has a real edge

An agent that reads your raw connection string from .env and runs psql directly goes around the proxy entirely — that's not a bug, it's what a proxy is. What bounds that damage: full-database snapshots at session start and daily, taken whether or not the risky statement came through us. We don't say “impossible,” because for a proxy it isn't true. What's true: every MCP-path risk is blocked or snapshotted, and everything is reversible.

We log statements and row counts. Never your data.

Undrop reads every statement to classify it and reports row counts for the safety line on the approval screen. Query results stream through to your agent and are never written to disk or logs on our side — not cached, not inspected, not a feature we plan to add.

It's not observability, a query optimizer, or a migration tool

We watch for destructive SQL, including statements that disable RLS. We don't summarize what your agent is doing, tune your queries, or help you author migrations. If that's what you're after, this isn't it.

No SOC 2 yet

We're a solo-built product. What we do instead: the enforcement core is open source and auditable, credentials are scoped and never the service_role key, and the whole block-snapshot-restore path is exercised end to end against real Postgres before anyone is onboarded — including a restore-and-diff check, run by hand today, that asserts on restored content, never on exit codes. The bar we're still building toward: that same check running nightly, automated, without a human starting it. We'd rather state that bar than pretend we've already cleared it.

Pricing

One paid tier. No seat math, no usage overages, no hidden metering.

Free

$0forever, 1 project

Full destructive-statement blocking, no time limit, no card required. Enough to survive the first accident.

Start free

Pro

$19/mo per project

30-day retention and unlimited restores. Cheaper than upgrading to Supabase Pro just to get a 7-day backup.

Upgrade to Pro
Feature comparison between Free and Pro plans
FeatureFreePro
Projects1Per project
Destructive-SQL blockingFull, foreverFull, forever
Auto-snapshot before every risky statementIncludedIncluded
Snapshot retentionLast 3 risk points, or 24 hours — whichever is more30 days
RestoresUnlimited, within retentionUnlimited

Also coming, not yet shippable to sell: RLS/policy drift alerts, a CI-friendly CLI, and a $49/mo team tier once its own features (restore drills, migration dry-runs) exist. A weekly email digest is on the same list.

Why route production through a solo-built service

That question deserves an architecture-visible answer, not a marketing one. Undrop is new and has no customer proof to offer — every point below is something you can verify yourself. Full detail is in the privacy policy.

The enforcement core is open source

The SQL classifier that decides what's destructive ships as a public library. Read exactly what gets blocked and why — the moat isn't the classifier, it's the hosted snapshot and restore history behind it.

Scoped credentials, never service_role

Connecting a project creates a dedicated Postgres role with only the grants it needs, with explicit REVOKEs on Supabase's auth and storage schemas. The connect flow rejects a service_role key outright, and the credential is sealed with AWS KMS envelope encryption before we store it.

Snapshots are encrypted, standard-format, and yours

pg_dump archives for data, plain .sql for function and view pre-images — both restore with stock Postgres tooling alone. Encrypted at rest, exportable at any time — not just at cancellation. No proprietary format, no export fee.

If we go down, writes fail closed

A control-plane outage doesn't mean an unprotected agent — it means blocked writes and a clear error, with a documented break-glass path.

The part you can see

You're asked to route production through a service you can't see inside. This is the part you can — MIT-licensed, shipped with its full source and its 349-case test corpus. Read it, run it, or fork it before you trust anything else.

undrop-guard

Takes a SQL statement, returns one of four verdicts — safe, risky, destructive, or unknown, where unknown is treated as destructive. It's the library the gateway runs against every statement — the hosted build can be a release ahead of npm between publishes — with no network calls, no filesystem access, no database connection of its own. Same input, same verdict, every time.

npm i undrop-guard

The 349 cases that pin this behavior are authored independently of the classifier — the code doesn't grade its own homework — and they ship with the package, so you can run them against your own install instead of taking this page's word for it.

349-case public corpus · runnable independently · MIT

Input → verdict

DELETE FROM orders WHERE created_at < now()
safe
TRUNCATE /* not really */ users
destructiveTRUNCATE
DO $$ BEGIN DELETE FROM users; END $$
unknownDYNAMIC_SQL

The comment and the DO block are the interesting rows: the classifier parses the statement rather than pattern-matching on keywords, and it fails closed — syntax it can't read is blocked, not waved through.