Summary
Two shipping policy plugins — sql_sanitizer and resource_filter — decide over a string
(flat regex / urlparse().netloc) while the downstream consumer parses the same string with a
real grammar (a SQL engine; an RFC-3986/WHATWG URL parser). The two parses disagree, so a plugin
allows an argument the downstream then executes as the exact forbidden operation the plugin exists
to block (a monitor-vs-executor parse gap). Impact: the SQL sanitizer can be bypassed to run
privilege-escalating UPDATE, DROP TABLE, and unscoped mass DELETE; the resource filter can be
bypassed for SSRF to a blocked domain. Confirmed on main @ 7b17f124bec1c46a94ab0d619c9727493236dffa
(2026-07-16).
Details
SQL Sanitizer — plugins/sql_sanitizer/sql_sanitizer.py. There is no real SQL parser (import re
only, line ~21). _find_issues() (line ~138) strips comments with
_BLOCK_COMMENT_RE = re.compile(r"/\*.*?\*/", re.DOTALL) (line ~55, applied line ~119),
keyword-blocklists DROP/TRUNCATE/… via regex, and flags DELETE/UPDATE "without WHERE" via
_WHERE_RE = re.compile(r"\bWHERE\b", re.IGNORECASE) (line ~58; checks at lines ~158 and ~162).
Because these are flat-text operations over the raw query string, several parse divergences with a real
SQL engine slip past the checks (see PoC).
resource_filter — plugins/resource_filter/resource_filter.py. The domain filter computes
parsed = urlparse(payload.uri) then domain_lower = parsed.netloc.lower() (line ~112) and blocks by
equality / endswith("." + d) (line ~114). netloc includes any userinfo@, so it differs from
the host a URL library actually connects to (urlparse(uri).hostname), enabling SSRF (see PoC).
PoC
SQL Sanitizer (shipping defaults block_update_without_where, block_delete_without_where,
strip_comments). Each returns no issue (allow); a SQL engine executes the destructive operation
(verified with the real _find_issues and stdlib sqlite3):
- Privilege escalation —
WHERE inside a string literal satisfies the "UPDATE without WHERE" check:
UPDATE users SET is_admin=1, note='flagged WHERE approved'
- DDL bypass —
DROP hidden between string literals is removed by the DOTALL comment stripper
before the blocklist runs:
SELECT '/*' AS x; DROP TABLE users; SELECT '*/' AS y
- Unscoped DELETE — statement-scope confusion (the
WHERE belongs to the trailing SELECT):
DELETE FROM users; SELECT 1 WHERE 1=1
resource_filter (blocked_domains=["evil.com"]):
http://good.com@evil.com/x → netloc = "good.com@evil.com", which neither equals evil.com nor ends
with .evil.com → not blocked. The true host — per Python urlparse(...).hostname and Node
new URL(...).hostname — is evil.com → SSRF past the filter.
(Engine-specific variant to review: MySQL/MariaDB executable comments /*! … */, executed by the
engine but treated as a comment by the stripper/parser; not tested against a live MySQL.)
Impact
The SQL sanitizer is deployed to stop dangerous SQL reaching a database MCP server → the bypasses yield
full-table privilege escalation, table drops, and mass deletion (integrity/confidentiality/availability
of the DB). The resource filter is deployed to block egress to disallowed hosts → the bypass yields
SSRF to a blocked domain. Any deployment relying on these plugins as a security control is affected.
Root cause is a monitor-vs-executor interpretation conflict: the plugin's string/regex view diverges
from the executor's grammar. Remediation: decide over a real parse — sqlglot typed statements for
SQL (per-statement kind + a genuine WHERE node, not a keyword inside a literal), and
urlparse(uri).hostname (+ IDN/alt-IP normalization) for URLs — binding the policy to the same
canonical parse the downstream uses. Reproduction scripts available; we propose a 90-day coordinated-
disclosure embargo (part of an academic study).
Summary
Two shipping policy plugins —
sql_sanitizerandresource_filter— decide over a string(flat regex /
urlparse().netloc) while the downstream consumer parses the same string with areal grammar (a SQL engine; an RFC-3986/WHATWG URL parser). The two parses disagree, so a plugin
allows an argument the downstream then executes as the exact forbidden operation the plugin exists
to block (a monitor-vs-executor parse gap). Impact: the SQL sanitizer can be bypassed to run
privilege-escalating
UPDATE,DROP TABLE, and unscoped massDELETE; the resource filter can bebypassed for SSRF to a blocked domain. Confirmed on
main@7b17f124bec1c46a94ab0d619c9727493236dffa(2026-07-16).
Details
SQL Sanitizer —
plugins/sql_sanitizer/sql_sanitizer.py. There is no real SQL parser (import reonly, line ~21).
_find_issues()(line ~138) strips comments with_BLOCK_COMMENT_RE = re.compile(r"/\*.*?\*/", re.DOTALL)(line ~55, applied line ~119),keyword-blocklists
DROP/TRUNCATE/…via regex, and flagsDELETE/UPDATE"without WHERE" via_WHERE_RE = re.compile(r"\bWHERE\b", re.IGNORECASE)(line ~58; checks at lines ~158 and ~162).Because these are flat-text operations over the raw query string, several parse divergences with a real
SQL engine slip past the checks (see PoC).
resource_filter —
plugins/resource_filter/resource_filter.py. The domain filter computesparsed = urlparse(payload.uri)thendomain_lower = parsed.netloc.lower()(line ~112) and blocks byequality /
endswith("." + d)(line ~114).netlocincludes anyuserinfo@, so it differs fromthe host a URL library actually connects to (
urlparse(uri).hostname), enabling SSRF (see PoC).PoC
SQL Sanitizer (shipping defaults
block_update_without_where,block_delete_without_where,strip_comments). Each returns no issue (allow); a SQL engine executes the destructive operation(verified with the real
_find_issuesand stdlibsqlite3):WHEREinside a string literal satisfies the "UPDATE without WHERE" check:UPDATE users SET is_admin=1, note='flagged WHERE approved'DROPhidden between string literals is removed by the DOTALL comment stripperbefore the blocklist runs:
SELECT '/*' AS x; DROP TABLE users; SELECT '*/' AS yWHEREbelongs to the trailingSELECT):DELETE FROM users; SELECT 1 WHERE 1=1resource_filter (
blocked_domains=["evil.com"]):http://good.com@evil.com/x→netloc = "good.com@evil.com", which neither equalsevil.comnor endswith
.evil.com→ not blocked. The true host — per Pythonurlparse(...).hostnameand Nodenew URL(...).hostname— isevil.com→ SSRF past the filter.(Engine-specific variant to review: MySQL/MariaDB executable comments
/*! … */, executed by theengine but treated as a comment by the stripper/parser; not tested against a live MySQL.)
Impact
The SQL sanitizer is deployed to stop dangerous SQL reaching a database MCP server → the bypasses yield
full-table privilege escalation, table drops, and mass deletion (integrity/confidentiality/availability
of the DB). The resource filter is deployed to block egress to disallowed hosts → the bypass yields
SSRF to a blocked domain. Any deployment relying on these plugins as a security control is affected.
Root cause is a monitor-vs-executor interpretation conflict: the plugin's string/regex view diverges
from the executor's grammar. Remediation: decide over a real parse —
sqlglottyped statements forSQL (per-statement kind + a genuine
WHEREnode, not a keyword inside a literal), andurlparse(uri).hostname(+ IDN/alt-IP normalization) for URLs — binding the policy to the samecanonical parse the downstream uses. Reproduction scripts available; we propose a 90-day coordinated-
disclosure embargo (part of an academic study).