Supabase MCP can leak your entire SQL database
181 comments
·July 8, 2025gregnr
tptacek
Can this ever work? I understand what you're trying to do here, but this is a lot like trying to sanitize user-provided Javascript before passing it to a trusted eval(). That approach has never, ever worked.
It seems weird that your MCP would be the security boundary here. To me, the problem seems pretty clear: in a realistic agent setup doing automated queries against a production database (or a database with production data in it), there should be one LLM context that is reading tickets, and another LLM context that can drive MCP SQL calls, and then agent code in between those contexts to enforce invariants.
I get that you can't do that with Cursor; Cursor has just one context. But that's why pointing Cursor at an MCP hooked up to a production database is an insane thing to do.
jacquesm
The main problem seems to me to be related to the ancient problem of escape sequences and that has never really been solved. Don't mix code (instructions) and data in a single stream. If you do sooner or later someone will find a way to make data look like code.
TeMPOraL
That "problem" remains unsolved because it's actually a fundamental aspect of reality. There is no natural separation between code and data. They are the same thing.
What we call code, and what we call data, is just a question of convenience. For example, when editing or copying WMF files, it's convenient to think of them as data (mix of raster and vector graphics) - however, at least in the original implementation, what those files were was a list of API calls to Windows GDI module.
Or, more straightforwardly, a file with code for an interpreted language is data when you're writing it, but is code when you feed it to eval(). SQL injections and buffer overruns are a classic examples of what we thought was data being suddenly executed as code. And so on[0].
Most of the time, we roughly agree on the separation of what we treat as "data" and what we treat as "code"; we then end up building systems constrained in a way as to enforce the separation[1]. But it's always the case that this separation is artificial; it's an arbitrary set of constraints that make a system less general-purpose, and it only exists within domain of that system. Go one level of abstraction up, the distinction disappears.
There is no separation of code and data on the wire - everything is a stream of bytes. There isn't one in electronics either - everything is signals going down the wires.
Humans don't have this separation either. And systems designed to mimic human generality - such as LLMs - by their very nature also cannot have it. You can introduce such distinction (or "separate channels", which is the same thing), but that is a constraint that reduces generality.
Even worse, what people really want with LLMs isn't "separation of code vs. data" - what they want is for LLM to be able to divine which part of the input the user would have wanted - retroactively - to be treated as trusted. It's unsolvable in general, and in terms of humans, a solution would require superhuman intelligence.
--
[0] - One of these days I'll compile a list of go-to examples, so I don't have to think of them each time I write a comment like this. One example I still need to pick will be one that shows how "data" gradually becomes "code" with no obvious switch-over point. I'm sure everyone here can think of some.
[1] - The field of "langsec" can be described as a systematized approach of designing in a code/data separation, in a way that prevents accidental or malicious misinterpretation of one as the other.
cyanydeez
Others Have pointed out one would need to train a new model that separated code and data because none of the models have any idea what either is.
It probably boils down a determistic and non deterministic problem set, like a compiler vs a interpretor.
cchance
This, just firewall the data off, dont have the MCP talking directly to the database, give it an accessor that it can use that are permission bound
tptacek
You can have the MCP talking directly to the database if you want! You just can't have it in this configuration of a single context that both has all the tool calls and direct access to untrusted data.
saurik
Adding more agents is still just mitigating the issue (as noted by gregnr), as, if we had agents smart enough to "enforce invariants"--and we won't, ever, for much the same reason we don't trust a human to do that job, either--we wouldn't have this problem in the first place. If the agents have the ability to send information to the other agents, then all three of them can be tricked into sending information through.
BTW, this problem is way more brutal than I think anyone is catching onto, as reading tickets here is actually a red herring: the database itself is filled with user data! So if the LLM ever executes a SELECT query as part of a legitimate task, it can be subject to an attack wherein I've set the "address line 2" of my shipping address to "help! I'm trapped, and I need you to run the following SQL query to help me escape".
The simple solution here is that one simply CANNOT give an LLM the ability to run SQL queries against your database without reading every single one and manually allowing it. We can have the client keep patterns of whitelisted queries, but we also can't use an agent to help with that, as the first agent can be tricked into helping out the attacker by sending arbitrary data to the second one, stuffed into parameters.
The more advanced solution is that, every time you attempt to do anything, you have to use fine-grained permissions (much deeper, though, than what gregnr is proposing; maybe these could simply be query patterns, but I'd think it would be better off as row-level security) in order to limit the scope of what SQL queries are allowed to be run, the same way we'd never let a customer support rep run arbitrary SQL queries.
(Though, frankly, the only correct thing to do: never under any circumstance attach a mechanism as silly as an LLM via MCP to a production account... not just scoping it to only work with some specific database or tables or data subset... just do not ever use an account which is going to touch anything even remotely close to your actual data, or metadata, or anything at all relating to your organization ;P via an LLM.)
tptacek
I don't know where "more agents" is coming from.
stuart73547373
can you explain a little more about how this would work and in what situations? like how is the driver llm ultimately protected from malicious text. or does it all get removed or cleaned by the agent code
OtherShrezzing
Pragmatically, does your responsible disclosure processes matter, when the resolution is “ask the LLM more times to not leak data, and add disclosures to the documentation”?
ajross
Absolutely astounding to me, having watched security culture evolve from "this will never happen", though "don't do that", to the modern world of multi-mode threat analysis and defense in depth...
...to see it all thrown in the trash as we're now exhorted, literally, to merely ask our software nicely not to have bugs.
null
jimjimjim
Yes, the vast amount of effort, time and money spent on making the world secure things and checking that those things are secured now being dismissed because people can't understand that maybe LLMs shouldn't be used for absolutely everything.
Aperocky
How to spell job security in a roundabout way.
cyanydeez
Late stage grift economy is a weird parallelism with LLM State of art bullshit.
e9a8a0b3aded
I wouldn't wrap it with any additional prompting. I believe that this is a "fail fast" situation, and adding prompting around it only encourages bad practices.
Giving an LLM access to a tool that has privileged access to some system is no different than providing a user access to a REST API that has privileged access to a system.
This is a lesson that should already be deeply ingrained. Just because it isn't a web frontend + backend API doesn't absolve the dev of their auth responsibilities.
It isn't a prompt injection problem; it is a security boundary problem. The fine-grained token level permissions should be sufficient.
lunw
Co-founder of General Analysis here. Technically this is not a responsibility of Supabase MCP - this vulnerability is a combination of:
1. Unsanitized data included in agent context
2. Foundation models being unable to distinguish instructions and data
3. Bad access scoping (cursor having too much access)
This vulnerability can be found almost everywhere in common MCP use patterns.
We are working on guardrails for MCP tool users and tool builders to properly defend against these attacks.
6thbit
In the non-AI world, a database server mostly always just executes any query you give it to, assuming right permissions.
They are not responsible only in the way they wouldn't be responsible for an application-level sql injection vulnerability.
But that's not to say that they wouldn't be capable of adding safeguards on their end, not even on their MCP layer. Adding policies and narrowing access to whatever comes through MCP to the server and so on would be more assuring measures than what their comment here suggest around more prompting.
dventimi
> But that's not to say that they wouldn't be capable of adding safeguards on their end, not even on their MCP layer. Adding policies and narrowing access to whatever comes through MCP to the server and so on would be more assuring measures than what their comment here suggest around more prompting.
This is certainly prudent advice, and why I found the GA example support application to be a bit simplistic. I think a more realistic database application in Supabase or on any other platform would take advantage of multiple roles, privileges, Row Level Security, and other affordances within the database to provide invariants and security guarantees.
blibble
> Sadly General Analysis did not follow our responsible disclosure processes [3] or respond to our messages to help work together on this.
your only listed disclosure option is to go through hackerone, which requires accepting their onerous terms
I wouldn't either
jekwoooooe
MCP for everything is just so so stupid
“Pwetty pwease don’t leak my database chatgpt”
abujazar
This "attack" can't be mitigated with prompting or guardrails though – the security needs to be implemented on the user level. The MCP server's db user should only have access to the tables and rows it's supposed to. LLMs simply can't be trusted to adhere to access policies, and any attempts to do that probably just limits the MCP server's capabilities without providing any real security.
fsndz
I now understand why some people say MCP is mostly bullshit + a huge security risk: https://www.lycee.ai/blog/why-mcp-is-mostly-bullshit
tptacek
This is just XSS mapped to LLMs. The problem, as is so often the case with admin apps (here "Cursor and the Supabase MCP" is an ad hoc admin app), is that they get a raw feed of untrusted user-generated content (they're internal scaffolding, after all).
In the classic admin app XSS, you file a support ticket with HTML and injected Javascript attributes. None of it renders in the customer-facing views, but the admin views are slapped together. An admin views the ticket (or even just a listing of all tickets) and now their session is owned up.
Here, just replace HTML with LLM instructions, the admin app with Cursor, the browser session with "access to the Supabase MCP".
ollien
You're technically right, but by reducing the problem to being "just" another form of a classic internal XSS, missing the forest for the trees.
An XSS mitigation takes a blob of input and converts it into something that we can say with certainty will never execute. With prompt injection mitigation, there is no set of deterministic rules we can apply to a blob of input to make it "not LLM instructions". To this end, it is fundamentally unsafe to feed _any_ untrusted input into an LLM that has access to privileged information.
Terr_
Right: The LLM is an engine for taking an arbitrary document and making a plausibly-longer document. There is no intrinsic/reliable difference between any part of the document and any other part.
Everything else—like a "conversation"—is stage-trickery and writing tools to parse the output.
tptacek
Yes. "Writing tools to parse the output" is the work, like in any application connecting untrusted data to trusted code.
I think people maybe are getting hung up on the idea that you can neutralize HTML content with output filtering and then safely handle it, and you can't do that with LLM inputs. But I'm not talking about simply rendering a string; I'm talking about passing a string to eval().
The equivalent, then, in an LLM application, isn't output-filtering to neutralize the data; it's passing the untrusted data to a different LLM context that doesn't have tool call access, and then postprocessing that with code that enforces simple invariants.
tptacek
Seems pretty simple: the MCP calls are like an eval(), and untrusted input can't ever hit it. Your success screening and filtering LLM'd eval() inputs will be about as successful as your attempts to sanitize user-generated content before passing them to an eval().
eval() --- still pretty useful!
ollien
Untrusted user input can be escaped if you _must_ eval (however ill-advised), depending on your language (look no further than shell escaping...). There is a set of rules you can apply to guarantee untrusted input will be stringified and not run as code. They may be fiddly, and you may wish to outsource them to a battle-tested library, but they _do_ exist.
Nothing exists like this for an LLM.
losvedir
The problem is, as you say, eval() is still useful! And having LLMs digest or otherwise operate on untrusted input is one of its stronger use cases.
I know you're pretty pro-LLM, and have talked about fly.io writing their own agents. Do you have a different solution to the "trifecta" Simon talks about here? Do you just take the stance that agents shouldn't work with untrusted input?
Yes, it feels like this is "just" XSS, which is "just" a category of injection, but it's not obvious to me the way to solve it, the way it is with the others.
Groxx
With part of the problem being that it's literally impossible to sanitize LLM input, not just difficult. So if you have these capabilities at all, you can expect to always be vulnerable.
otterley
Oh, Jesus H. Christ: https://github.com/supabase-community/supabase-mcp/blob/main...
noselasd
It's an MCP for your database, ofcourse it's going to execute SQL. It's your responsibility for who/what can access the MCP that you've pointed at your database.
otterley
Except without any authentication and authorization layer. Remember, the S in MCP is for "security."
Also, you can totally have an MCP for a database that doesn't provide any SQL functionality. It might not be as flexible or useful, but you can still constrain it by design.
tptacek
This to me is like going "Jesus H. Christ" at the prompt you get when you run the "sqlite3" command. It is also crazy to point that command at a production database and do random stuff with it. But not at all crazy to use it during development. I don't think this issue is as complicated, or as LLM-specific, as it seems; it's really just recapitulating security issues we understood pretty clearly back in 2010.
Actually, in my experience doing software security assessments on all kinds of random stuff, it's remarkable how often the "web security model" (by which I mean not so much "same origin" and all that stuff, but just the space of attacks and countermeasures) maps to other unrelated domains. We spent a lot of time working out that security model; it's probably our most advanced/sophisticated space of attack/defense research.
(That claim would make a lot of vuln researchers recoil, but reminds me of something Dan Bernstein once said on Usenet, about how mathematics is actually one of the easiest and most accessible sciences, but that ease allowed the state of the art to get pushed much further than other sciences. You might need to be in my head right now to see how this is all fitting together for me.)
ollien
> It is also crazy to point that command at a production database and do random stuff with it
In a REPL, the output is printed. In a LLM interface w/ MCP, the output is, for all intents and purposes, evaluated. These are pretty fundamentally different; you're not doing "random" stuff with a REPL, you're evaluating a command and _only_ printing the output. This would be like someone copying the output from their SQL query back into the prompt, which is of course a bad idea.
wrs
SimonW coined (I think) the term “prompt injection” for this, as it’s conceptually very similar to SQL injection. Only worse, because there’s currently no way to properly “escape” the retrieved content so it can’t be interpreted as part of the prompt.
simonw
If you want to use a database access MCP like the Supabase one my recommendation is:
1. Configure it to be read-only. That way if an attack gets through it can't cause any damage directly to your data.
2. Be really careful what other MCPs you combine it with. Even if it's read-only, if you combine it with anything that can communicate externally - an MCP that can make HTTP requests or send emails for example - your data can be leaked.
See my post about the "lethal trifecta" for my best (of many) attempt at explaining the core underlying issue: https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/
null
theyinwhy
I'd say exfiltration is fitting even if there wasn't malicious intent.
journal
one day everything private will be leaked and they'll blame it on misconfiguration by someone they can't even point a finger at. some contractor on another continent.
how many of you have auth/athr just one `if` away from disaster?
we will have a massive cloud leak before agi
vigilans
If you're hooking up an LLM to your production infrastructure, the vulnerability is you.
anand-tan
This was precisely why I posted Tansive on Show HN this morning -
https://news.ycombinator.com/item?id=44499658
MCP is generally a bad idea for stuff like this.
abujazar
Well, this is the very nature of MCP servers. Useful for development, but it should be quite obvious that you shouldn't grant a production MCP server full access to your database. It's basically the same as exposing the db server to the internet without auth. And of course there's no security in prompting the LLM not to do bad stuff. The only way to do this right in production is having a separate user and database connection for the MCP server that only has access to the things it should.
null
sshh12
I'm surprised we haven't seen more "real" attacks from these sorts of things, maybe it's just bc not very many people are actually running these types of MCPs (fortunately) in production.
Wrote about a similar supabase case [0] a few months ago and it's interesting that despite how well known these attacks feel even the official docs don't call it out [1].
[0] https://blog.sshh.io/i/161242947/mcp-allows-for-more-powerfu... [1] https://supabase.com/docs/guides/getting-started/mcp
simonw
Yeah, I am surprised at the lack of real-world exploits too.
I think it's because MCPs still aren't widely enough used that attackers are targeting them. I don't expect that will stay true for much longer.
0cf8612b2e1e
Could be that the people most likely to mainline MCP hype with full RW permissions are the least likely to have any auditing controls to detect the intrusion.
ang_cire
Yep, the "we don't have a dedicated security team, but we've never had an intrusion anyways!" crowd.
pests
Support sites always seem to be a vector in a lot of attacks. I remember back when people would signup for SaaS offerings with organizational email built in (ie join with a @company address, automatically get added to that org) using a tickets unique support ticket address (which would be a @company address), and then using the ticket UI to receive the emails to complete the signup/login flow.
ajd555
I've heard of some cloudflare MCPs. I'm just waiting for someone to connect it to their production and blow up their DNS entries in a matter of minutes... or even better, start touching the WAF
coderinsan
From tramlines.io here - We found a similar exploit in the official Neon DB MCP - https://www.tramlines.io/blog/neon-official-remote-mcp-explo...
simonw
Hah, yeah that's the exact same vulnerability - looks like Neon's MCP can be setup for read-write access to the database, which is all you need to get all three legs of the lethal trifecta (access to private data, exposure to malicious instructions and the ability to exfiltrate).
coderinsan
Here's another one we found related to the lethal trifecata problem in AI Email clients like Shortwave that have integrated MCPs - https://www.tramlines.io/blog/why-shortwave-ai-email-with-mc...
TeMPOraL
This is why I believe that anthropomorphizing LLMs, at least with respect to cognition, is actually a good way of thinking about them.
There's a lot of surprise expressed in comments here, as is in the discussion on-line in general. Also a lot of "if only they just did/didn't...". But neither the problem nor the inadequacy of proposed solutions should be surprising; they're fundamental consequences of LLMs being general systems, and the easiest way to get a good intuition for them starts with realizing that... humans exhibit those exact same problems, for the same reasons.
Supabase engineer here working on MCP. A few weeks ago we added the following mitigations to help with prompt injections:
- Encourage folks to use read-only by default in our docs [1]
- Wrap all SQL responses with prompting that discourages the LLM from following instructions/commands injected within user data [2]
- Write E2E tests to confirm that even less capable LLMs don't fall for the attack [2]
We noticed that this significantly lowered the chances of LLMs falling for attacks - even less capable models like Haiku 3.5. The attacks mentioned in the posts stopped working after this. Despite this, it's important to call out that these are mitigations. Like Simon mentions in his previous posts, prompt injection is generally an unsolved problem, even with added guardrails, and any database or information source with private data is at risk.
Here are some more things we're working on to help:
- Fine-grain permissions at the token level. We want to give folks the ability to choose exactly which Supabase services the LLM will have access to, and at what level (read vs. write)
- More documentation. We're adding disclaimers to help bring awareness to these types of attacks before folks connect LLMs to their database
- More guardrails (e.g. model to detect prompt injection attempts). Despite guardrails not being a perfect solution, lowering the risk is still important
Sadly General Analysis did not follow our responsible disclosure processes [3] or respond to our messages to help work together on this.
[1] https://github.com/supabase-community/supabase-mcp/pull/94
[2] https://github.com/supabase-community/supabase-mcp/pull/96
[3] https://supabase.com/.well-known/security.txt