QueryGate makes tenant isolation an executable preflight for AI-written SQL
The new MCP reference server injects caller scope after generation, re-parses the final query, and publishes the boundary its proof cannot cross.
A new open-source entrant is treating tenant isolation as a property of the executed SQL, not a sentence in an agent prompt. QueryGate, an MIT-licensed MCP reference server created on August 25, places a governed pipeline between an LLM and Postgres, DuckDB, BigQuery or Snowflake. The model may write the query, but the server qualifies names, clamps limits, injects caller scope, estimates cost and runs through a read-only path.
The useful idea is the second check
Predicate injection alone is not QueryGate’s main contribution. After rewriting every governed scope, the server parses the resulting SQL again and demands the expected tenant predicate on each governed table. Its published test matrix covers aliases, joins between two governed tables, governed/public joins, subqueries and CTE bodies. Separate rejection tests remove the filter, bind the wrong parameter and attach the right-looking predicate to OR 1 = 1.
That makes the safety claim inspectable. A deterministic demo sends the same business question through three static tokens: one for Acme, one for Globex and one allowed to see both. The repository reports $55,952.59 and $62,841.34 for the single-tenant callers, then $118,793.93 for the combined caller—the exact sum. The demo talks directly to MCP, so it tests the server’s isolation path without depending on whether a model cooperates.
The fail-closed behavior matters just as much. A caller with an empty scope is refused when the SQL touches a governed model; the same caller may still query a model explicitly marked public. PII masking is also enforced in the AST, and masked columns are barred from predicates to avoid turning WHERE customer_name = ... into a guessing oracle. QueryGate additionally keeps tenant scopes in cache keys, preventing identical SQL from becoming a cross-tenant cache hit.
The repository states its own limit
This is a reference implementation, not a mature product. At discovery it had no stars, forks or tagged release, and its README says teams should adapt the seams rather than deploy it unchanged. It also opens a fresh Postgres connection for cost estimation and another for execution, a known limit under concurrency.
More importantly, both injection and assertion interpret SQL through sqlglot. If that parser resolves a dialect construct differently from the warehouse, re-parsing with the same library may repeat the same mistake. QueryGate therefore recommends treating its inject-and-assert layer as defense in depth over native row policies, not as their replacement.
That caveat strengthens the project’s value as a template. The practical pattern is not “trust this parser.” It is: make authorization a deterministic transformation after generation, publish adversarial tests for the transformed query, fail closed when scope is absent, and document exactly where the proof ends.
sources
- QueryGate repository and READMEgithub.com
- QueryGate governance test matrixgithub.com
comments · 0