2026-02-24 verified 2026-02-22 avoid
Theory Delta Connecting an agent to a database is framed as a correctness problem. It is primarily a security problem -- every MCP database server reviewed uses startsWith('select') as its read-only guard, which is bypassable.
independently-confirmed sourced + first-party 2 source snippets, 1 GHSA, 2 GitHub issues, 5 repo links

Every MCP database server enforces read-only with a string check -- and it's bypassable

From Theory Delta | Methodology | Published 2026-02-24

What the docs say

MCP database servers advertise a "read-only mode" that restricts agents to SELECT queries, preventing data modification. The official Anthropic MCP SQLite reference server and community implementations all offer this as a safety feature for connecting agents to production databases.

What actually happens

Every MCP database server reviewed implements read-only enforcement as a string prefix check. None use SQL AST parsing. Here is the actual code:

executeautomation/mcp-database-server (source, security advisory GHSA-65hm-pwj5-73pw):

// index.ts:272-291
if (!query.trim().toLowerCase().startsWith("select")) {
  throw new Error("Only SELECT queries are allowed with read_query");
}

modelcontextprotocol/servers mcp-server-sqlite (issue #3314):

# mcp_server_sqlite/server.py:326
results = db._execute_query(
    f"PRAGMA table_info({arguments['table_name']})"
)

The table_name parameter is interpolated directly into the PRAGMA statement without sanitization. Input like users); DROP TABLE users; -- executes arbitrary SQL. The reference implementation that builders copy from has an open SQL injection.

This means:

The NL2SQL correctness ceiling compounds the problem: SOTA on enterprise schemas (Spider 2.0) is ~17% for o1-preview, ~10% for GPT-4o. Spider 1.0's commonly cited 86.6% uses toy schemas and is a misleading baseline.

What to do instead

  1. Add a SQL AST parser (sqlglot for Python, node-sql-parser for Node) that identifies statement type before execution. String prefix checks are not a security boundary.
  2. Use parameterized queries -- neither executeautomation nor haxzie/sequel-mcp separates parameters from query strings. Parameterized queries eliminate the SQL injection class entirely.
  3. Sandbox any code execution path -- if your NL2SQL stack generates visualization code or transformation scripts, run them in a subprocess with restricted imports or a container with no network access. Input filtering will not stop LLM role-switching attacks.
  4. Do not trust the Anthropic reference server as a secure starting point. Issue #3314 is open. Audit describe_table and any PRAGMA-using path before deploying.
  5. Test schema discovery tools against your actual database -- MySQL 8.0 RDS returns 748 null entries from list_tables in executeautomation (issue #23, open). Agents silently fall back to raw information_schema queries, bypassing the tool abstraction.

Environments tested

Tool Version Result
executeautomation/mcp-database-server latest (Feb 2026) startsWith('select') prefix check confirmed
modelcontextprotocol/servers mcp-server-sqlite v2025.4.25 PRAGMA injection via f-string confirmed
haxzie/sequel-mcp latest (Feb 2026) SELECT INTO OUTFILE passes prefix check
vanna-ai/vanna v0.7.x NL2SQL pipeline reviewed
Canner/WrenAI latest (Feb 2026) NL2SQL pipeline reviewed
Snowflake-Labs/ReFoRCE latest (Feb 2026) Benchmark reviewed
xlang-ai/Spider2 latest (Feb 2026) Enterprise accuracy benchmarks reviewed

Confidence and gaps

Confidence: empirical -- source code reviewed and vulnerability confirmed across 3 MCP database servers, benchmarks reviewed across 4 additional tools. Security advisory GHSA-65hm-pwj5-73pw independently confirms the finding.

Open questions: Does any MCP database server implement AST-based read-only enforcement? Are there production deployments using parameterized queries through MCP tool calls? Has anyone shipped a sandboxed execution path for NL2SQL visualization code?

Seen different? Contribute your evidence -- theory delta is what makes this knowledge base work.