Supabase MCP Security

  • Jul 31

Supabase MCP Security: The Rules I Run Before an Agent Touches Any of My 14 Projects

What Supabase MCP really hands an agent, why read-only is not enough, and the five rules I run before any agent touches one of my 14 projects.

Every guide about connecting an AI agent to your database ends at the same sentence: don't connect it to production. Supabase says it in its own documentation. Security researchers say it. I agree with it.

It is also not an answer, and everybody who runs real software knows that.

I have around 14 launched products, each one sitting on its own Supabase project. Those projects are where the actual questions live. Why did signups stall on Tuesday. Which rows have a null that should never have been null. What does the schema look like right now, not what does the migration folder claim it looks like. Telling me to point my agent at an empty development branch does not answer any of those.

So this post is the version I wanted to read: what Supabase MCP actually hands an agent, what the real attack looks like, the two things every guide leaves out, and the five rules I run before an agent gets anywhere near one of my projects.

What does Supabase MCP actually give an AI agent?

The Supabase MCP server is the official bridge between an AI coding tool and your Supabase account. You add it once, and your agent gets a set of tools it can call directly: list your projects, read the schema, run SQL, apply a migration, deploy an edge function, create or delete a branch, read logs.

Two things about that list matter more than the setup instructions everyone leads with.

First, the default posture is admin, not user. Your app talks to Supabase through a key that respects Row Level Security. The MCP server does not work that way. It operates with account-level and service-level privilege, which means Row Level Security is not standing between the agent and your tables. Every policy you carefully wrote for your app's users is simply not in the path.

Second, the surface is wider than "run a query." Project creation, branch deletion, and edge function deployment are all tool calls. When people picture the worst case they picture a bad SELECT. The worst case is a tool you forgot was in the list.

Supabase gives you three knobs to shrink that, documented on the Supabase MCP server page: a read only flag that executes every query as a read-only Postgres user, a project scoping parameter that pins the connection to one project and disables the account-level tools, and feature groups that let you turn whole categories of tools off. They are query parameters on the server URL, and I will come back to how I set all three.

Is Supabase MCP safe to use with production data?

The honest answer is no, and the reason is more interesting than "AI is unpredictable."

Security researchers at General Analysis published a walkthrough of the attack that made this concrete, and it does not require the model to malfunction at all. The setup: a support ticketing app, with tickets written by members of the public. A developer opens their AI tool and asks it to review recent tickets. One of those tickets contains text written for the agent rather than for a human, instructing it to read a table of integration tokens and paste the contents back into the ticket thread as a reply.

The agent does it. Not because it was jailbroken, but because, as the researchers put it, models process instructions and data through the same channel. The ticket body is just more text in the context window. And since the MCP connection carries service-level privilege, the tokens table that the support staff could never reach is wide open.

Supabase responded publicly with a defense in depth post that is unusually candid for vendor writing. They shipped read only mode, project scoping, and feature groups. They also tested the softer mitigations, wrapping query results in warnings to the model and running classifiers over them, and reported that guardrails alone are not enough. Their own documentation now says the server is designed for development and testing, and that the result wrapping is not foolproof.

Read that carefully, because it changes what read only mode is for. Read only stops the agent from writing. It does not stop the agent from reading something it should not and then telling somebody about it. Exfiltration is a read. If your threat model is "the agent will corrupt my data," read only covers you. If your threat model is "the agent will leak my data," read only is one layer, and by itself it is thin.

The gap every Supabase MCP guide leaves open

Here is where the published advice and my actual working conditions stop matching, in two specific places.

Gap one: the approval gate does not exist in an unattended run. Nearly every guide, including Supabase's own, lists manual approval of each tool call as a top mitigation. That advice quietly assumes a human is sitting in front of the session, reading the tool call, and deciding. That assumption is fine for an afternoon of debugging in an editor. It is false for anything on a schedule.

This is not hand-waving, it is documented behavior. When Claude Code runs non-interactively, you pass tools you have pre-approved with an allowed tools flag, or you set a permission mode for the whole run. The programmatic execution docs describe a locked-down mode intended for CI that never prompts and denies anything outside your allow rules. There is a related detail worth knowing if you build your own MCP servers: a server can annotate a tool as requiring explicit human interaction, and the MCP reference states that in the never-prompt mode Claude Code denies that call rather than running it. The system is behaving correctly. It is refusing to fake a human.

So the mitigation everyone recommends is not a setting you forgot to enable in a scheduled run. It is a control that structurally cannot fire. Any tool you hand an unattended agent is a tool that runs without review, every time. That has to change what you hand it, and almost nobody writes that part down.

Gap two: a preview branch is empty, which is exactly why it does not solve your problem. The standard advice is to point the agent at a development branch instead of production. Good advice, with a catch that the guides skip. Supabase's branching documentation is explicit that new branches do not start with any data from your main project, and says plainly that this is meant to protect your sensitive production data.

That is the right default. It also means a branch gives your agent your schema and none of your reality. Every question in my opening paragraph, the stalled signups, the nulls that should not exist, is a data question. Sending the agent to a branch to answer it is sending it to look at an empty room.

Once you see both gaps, the actual problem sharpens up. It is not "production or branch." It is: which capability does this specific run need, and can the run be trusted to hold it without a person watching?

The 5 rules I run before an agent touches a Supabase project

These are the rules themselves, in the order I apply them. None of them are clever. The value is that they are written down in the project's instruction file rather than living in my head, which is the same principle behind every other rail I have written about in how I keep instruction files from drifting.

Rule 1: Read only is the default, and writes leave through a file, not a tool call.

Every project connects with the read only flag on. When schema needs to change, the agent does not apply it. It writes a migration file into the repo, I read the file, and the migration goes out through the normal deployment path with my hands on it. This costs me maybe two minutes per change. In exchange, there is no path where a confused turn or a poisoned row rewrites a live table, because the capability to rewrite a live table was never in the session.

The version of this rule I keep having to re-learn: "the agent can write, but I review the output" is not the same control. Reviewing a diff before it merges is review. Reviewing a database mutation after the tool already ran is a changelog.

Rule 2: Scope to one project, per repository.

The project scoping parameter pins the connection to a single project reference and turns off the account-level tools. With around 14 projects on one account, an unscoped connection means every session carries the blast radius of the entire portfolio, and the agent picks the target based on a name it inferred from conversation.

The failure I actually designed against was never a dramatic breach. It is me, moving fast on a Friday, in the wrong repository, asking a perfectly reasonable question that gets answered against the wrong project. Scoping makes that impossible instead of unlikely. One repository, one project reference, written into that repository's config.

Rule 3: Turn off the tool groups this repository does not use.

Feature groups let you enable only the categories a given project needs. Most of my repos need to read a schema and run a select. They do not need branch management, storage, or edge function deployment from an agent session. Those get switched off, not because I expect them to be misused, but because the smallest useful toolset is the cheapest security control that exists. Every tool you leave enabled is a tool that has to be safe under an instruction you did not write.

Rule 4: An unattended run gets no database mutation tools. Ever.

This is Gap One turned into a rule. My scheduled routines run without me. So the rule is absolute rather than judged case by case: anything running on a schedule gets read-only, project-scoped access with a minimal feature set, and no ability to change state in Supabase. If a scheduled job genuinely needs to write, it writes to a file in the workspace and the change waits for a human pass.

That is the same human-send gate I put on the marketing automations, for the same reason. The point of a system is that the guardrail is structural rather than a decision you make correctly every time, which is most of what separates a real system from a clever prompt. It applies with more force once you are running several agents at once, because a fleet of subagents multiplies every capability you granted by the number of workers holding it.

Rule 5: Treat every user-generated row as hostile text, and keep Row Level Security on anyway.

Any table where the contents came from the public, support tickets, form submissions, profile fields, comments, is an injection surface the moment an agent reads it. I do not have a way to make that text safe. What I do have is a habit: when a session is going to read user-supplied content, it is a read-only, tightly scoped session, and I do not chain it into anything that can send, publish, or write.

Row Level Security stays enabled on every table regardless. It is not what protects you from an MCP session, since the privilege used there sits above it. It protects you from everything else, and the day a key leaks or a policy gets stricter, you want that layer already in place rather than on a list.

If you want to see the whole pattern built rather than described, the walkthrough behind this post is the session on wiring an agent into your database and putting it on a schedule. The full replay of OpenClaw and Your Database: The 24/7 Workforce is available instantly, and it covers the part that is hard to write about in prose: the endpoints, the cron trigger, and what "wake up with results already saved" looks like when it is actually wired up.

How to get real agent help on production without handing over production

Rules are the constraint. This is the workflow that lives inside it, and it is the part I would have wanted three months ago.

For reading, use production, scoped and read only. This is the case everyone over-corrects on. A read-only, project-scoped session against the live project is genuinely useful and genuinely bounded: schema inspection, counts, distributions, "show me the five most recent rows in this table." I do this constantly. The agent cannot change anything, and the exposure is what the session reads.

For schema changes, use the repository as the airlock. The agent proposes a migration file. I read it. The deployment path applies it. Nothing about that requires the agent to hold write access, and the diff is reviewable in the place where diffs are already reviewed.

For anything needing realistic data, seed the branch. Since a preview branch starts empty, a branch is only useful once you put something in it. Seed it with generated or anonymized rows rather than a production copy. It is more work than cloning production, and cloning production would defeat the entire point of the branch.

Keep secrets out of the session's reach. Edge function secrets belong in the Supabase dashboard vault, not in files sitting in a repository the agent reads freely. If your keys live in a file in the working directory, then "read only database access" is a rounding error, because the session can just read the keys and act with them elsewhere.

Decide capability per run, not per tool. This is the whole thing compressed: the question is never "is Supabase MCP safe." It is "what can this specific session do, who is watching it, and what is the worst outcome if the text it reads was written by somebody hostile."

What this actually costs

I would rather give you the friction than a clean story.

It is slower. There are real moments where the agent says it cannot complete something because it lacks the tool, and I go do it by hand. Read-only sessions cannot fix the thing they just diagnosed, which feels wasteful right up until you remember what the alternative permission grant looks like.

I have not been breached this way. I also cannot prove I never will be. Nothing in this post is a guarantee, it is a reduction in blast radius, and the honest framing is that I have made the bad outcome smaller and less likely rather than impossible.

And the rules only hold because they are written into project instruction files that load automatically. Every rule here that depended on me remembering it in the moment eventually failed. The ones that survived are the ones a machine reads before I do.

Where to go deeper

If this is the kind of thing you want to build rather than read about, the Reinventing AI Accelerator is where I do it in the open: live training sessions each week plus the vault of AI apps, tools, and agents from every past session, including the database workforce build this post came out of. You get the working files, not just the theory.

One small ask: if you run Supabase and you have an agent connected to it right now, go check whether the read only flag is actually set. That check takes thirty seconds and it is the single highest-value thing in this article.

Frequently asked questions

Is read only mode enough to stop prompt injection in Supabase MCP?

No. Read only mode runs every query as a read-only Postgres user, which stops writes, and that is worth having. But the attack that matters most is exfiltration, and reading is exactly what a read-only session does. An injected instruction can still direct the agent to read a sensitive table and repeat the contents somewhere the attacker can see. Supabase tested softer guardrails on top and reported that they reduced risk without eliminating it. Treat read only as one layer of several, not as the fix.

Does Row Level Security protect me when using Supabase MCP?

Not in the way people expect. The MCP connection operates with privilege above Row Level Security, so the policies protecting your application's users are not in the path of an agent's query. This was the core of the published exploit: a support agent could not reach the token table, but the developer's AI session could. Keep Row Level Security enabled on every table anyway, because it protects every other access path. Just do not count it as your MCP control.

Does a Supabase preview branch include my production data?

No. Supabase's branching documentation states that new branches do not start with any data from your main project, and describes that as a deliberate protection for sensitive production data. Branches receive schema and migrations. If you want an agent to work against realistic rows on a branch, you have to seed it yourself, ideally with generated or anonymized data rather than a production copy.

What happens to tool approval when an agent runs on a schedule?

It stops existing, which is the part most guides never mention. In a non-interactive run there is nobody to answer a permission prompt, so tools are either pre-approved through an allow list or a permission mode, or the call is denied. The stricter modes deny anything outside your allow rules instead of prompting. This is correct behavior, but it means "approve each tool call manually" is not available as a mitigation for scheduled work. Restrict the toolset instead, and never grant database mutation capability to a run nobody is watching.

Should I use Supabase MCP at all, or connect through my own MCP server?

Use the official server if you want breadth and are willing to configure the scoping properly, which for me means read only, one project reference, and the smallest feature set the repository needs. Build a narrow custom server when you want the agent to have exactly three operations and nothing else, since a tool that does not exist cannot be misused by an instruction hidden in a table row. Both approaches are defensible. What is not defensible is an unscoped, write-enabled connection to a live project because the setup guide showed the shortest possible configuration.

Subscribe Now for More AI Insights

Subscribe for Updates from Reinventing AI

Stay current on the most cutting-edge AI solutions for ambitious entrepreneurs and marketers!

Get weekly AI training announcements, AI resources and insights.

0 comments

Joinor login to leave a comment