DEV Community

Cover image for CVE-2026-85620: When Your MCP Server's Allowlist Parser Isn't the One Actually Parsing SQL
Cor E
Cor E

Posted on

CVE-2026-85620: When Your MCP Server's Allowlist Parser Isn't the One Actually Parsing SQL

Postgres MCP Pro ships a feature called Safe Mode. The pitch is simple: let an AI agent talk to your database, but only let it run read-only queries. No DROP, no DELETE, no ALTER, no writes of any kind. It does this by parsing every incoming SQL statement and checking it against an allowlist before the statement ever touches Postgres.

CVE-2026-85620 says the parser has a flaw that lets crafted SQL slip past that allowlist entirely. An agent (or whoever's steering it, directly or via prompt injection) can construct a statement that the validator reads as safe but Postgres executes as something very much not safe.

Let's talk about why this class of bug is basically inevitable, and where the actual fix needs to live.

How Safe Mode Was Supposed to Work

The design is a classic gatekeeper pattern: intercept the query text, parse it, walk the resulting structure, check it against a set of permitted operations, then either forward it to Postgres or reject it. It's the same idea as a WAF rule that blocks UNION SELECT, or an input sanitizer that strips <script> tags.

The problem with gatekeeper patterns built on parsing is that they only work if your parser and the downstream engine agree, 100% of the time, on what a given string of text means. Postgres has its own SQL parser, with its own quirks, its own dialect extensions, its own handling of comments, whitespace, casing, quoting, and multi-statement bodies. If Postgres MCP Pro's allowlist parser is even slightly out of sync with libpq's actual grammar, you get a gap. CVE-2026-85620 is that gap.

We don't have the specific grammar trick used here (it's not in the disclosure summary), but this is structurally the same failure mode as SQL injection filters that get bypassed by comment obfuscation, encoding tricks, or statement chaining the filter didn't anticipate. Two parsers, one ground truth. Whichever one is wrong loses.

What Existing Defenses Missed, and Why

Safe Mode's validator is a single point of failure sitting in front of the database. Once a query gets past it, there's no second opinion. Postgres itself has no idea the query arrived via an "allowlisted" path versus a raw connection — it just executes valid SQL. There's no layer asking "does this look like something an AI agent's tool call should actually be doing right now."

That's the structural miss. A parser-based allowlist validates syntax. It doesn't validate intent, and it definitely doesn't validate context — like whether this particular tool call is part of a sequence that looks like an agent being walked, statement by statement, toward a destructive action it was never supposed to reach.

This is also exactly the kind of bug that's invisible until someone finds it. Nothing about Safe Mode looks broken in testing with well-formed queries. It only breaks under adversarial construction, which is precisely the input class an AI agent (fed attacker-controlled text somewhere upstream) can be manipulated into producing.

Where Sentinel Would Have Caught This

Sentinel doesn't try to out-parse Postgres. It sits on the tool-call path in the agentic proxy and applies agentic_tool_abuse pattern detection to what the tool is actually being asked to do, independent of whether some other component's SQL validator thinks the statement is allowed.

The fast-path regex library includes signatures for tool/function abuse patterns — this is exactly the layer built for "the agent is being instructed to call a database/file/shell tool in a way that doesn't match benign use." A crafted statement engineered to evade an allowlist parser but still execute a destructive operation is going to correlate with the kind of query construction that trips those signatures, because the underlying intent (drop a table, alter permissions, exfiltrate rows) doesn't change just because the syntax was massaged to dodge one specific validator.

Two things matter here:

  1. Sentinel scans the tool call itself, not just the tool's eventual result. If the agent is about to send a SQL statement to the Postgres MCP server, that outbound call is content Sentinel can scrub before it leaves the session.
  2. The deep-path vector similarity layer doesn't care about SQL grammar. It's comparing semantic intent against a library of attack signature embeddings. A statement engineered to look read-only to a naive parser but semantically represents "grant this role superuser" or "truncate this table" isn't fooling a similarity check the same way it fools a syntax allowlist, because the embedding isn't reasoning about grammar validity, it's reasoning about what the statement is trying to do.

Put plainly: Postgres MCP Pro's Safe Mode failed because it asked "is this syntactically permitted." Sentinel's tool-abuse detection asks a different question: "does this tool call look like the kind of thing that shows up right before something bad happens." Those are different failure surfaces, and a parser bug in one doesn't automatically compromise the other.

Illustrative Example

The following is a constructed example showing the shape of a Sentinel response for a blocked tool call — it is not the actual bypass payload from CVE-2026-85620, since that detail isn't public in the source summary. It's here to show what the detection looks like mechanically.

{
  "request_id": "f7e3a9c1d4b2",
  "security": {
    "action_taken": "blocked",
    "threat_score": 0.89,
    "flags": []
  },
  "safe_payload": "[SENTINEL BLOCKED]: Tool call withheld — fast-path tool/function abuse pattern detected. Matched signature class: destructive-operation-via-permitted-syntax."
}
Enter fullscreen mode Exit fullscreen mode

And a config-side illustration of wiring the agentic proxy in front of a Postgres MCP tool-call flow (also illustrative, showing intended usage, not a verified fix):

import anthropic

client = anthropic.Anthropic(
    api_key="sk_live_...",
    base_url="https://api.sentinelaifirewall.com/v1",
)

# Tool calls the agent makes against Postgres MCP Pro flow through this client.
# agentic_tool_abuse pattern matching runs on the outbound tool call before
# it ever reaches the MCP server's own Safe Mode validator.
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": user_message}],
    tools=[postgres_mcp_tool_definition],
)
Enter fullscreen mode Exit fullscreen mode

The point isn't that this specific snippet stops CVE-2026-85620 (we don't have the payload to test against). The point is architectural: putting a second, independent detection layer in front of a tool call means a bypass in the tool's own validator doesn't leave you with zero coverage. It leaves you with one fewer layer, not none.

The Takeaway

If you're running an MCP server that gates access with an internal allowlist, parser, or validator, don't treat that validator as your only line of defense; it's one component with one failure mode, and CVE-2026-85620 shows exactly what happens when that specific component is wrong. Put a detection layer in front of the tool-call path itself, one that reasons about intent and pattern rather than re-implementing whatever grammar the downstream system uses. Two independent checks that fail differently is a much better position than one clever parser that has to be perfect forever.

If you're running MCP servers or agentic tool integrations in production, check out Sentinel-Proxy — it's built to catch exactly this class of gap between "the validator said yes" and "the operation was actually safe."

Sources


AI-assisted draft or imaging, human-curated, reviewed and edited.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.