Skip to content

fix(sqlite): prevent SQL injection in describe_table and query tools#3663

Open
xr843 wants to merge 1 commit intomodelcontextprotocol:mainfrom
xr843:fix/sqlite-sql-injection
Open

fix(sqlite): prevent SQL injection in describe_table and query tools#3663
xr843 wants to merge 1 commit intomodelcontextprotocol:mainfrom
xr843:fix/sqlite-sql-injection

Conversation

@xr843
Copy link

@xr843 xr843 commented Mar 21, 2026

Summary

  • Fixes a critical SQL injection vulnerability in the SQLite MCP server's describe_table tool (OWASP A03:2021 — Injection)
  • The table_name parameter was interpolated directly into a PRAGMA query via f-string without any sanitization
  • Adds defense-in-depth validation: regex identifier check + whitelist validation against sqlite_master + bracket quoting

Fixes #3314

Vulnerability

The describe_table tool at src/sqlite/src/mcp_server_sqlite/server.py constructed SQL using unsanitized string interpolation:

# BEFORE (vulnerable)
results = db._execute_query(
    f"PRAGMA table_info({arguments['table_name']})"
)

An attacker could provide a crafted table_name like users); DROP TABLE users; -- to execute arbitrary SQL.

Fix — Defense-in-Depth

1. Regex identifier validation (_validate_identifier)

Rejects any identifier containing characters other than [a-zA-Z0-9_.], blocking SQL metacharacters (;, ), ', --).

2. Whitelist validation against sqlite_master (_validate_table_name)

Uses a parameterized query to verify the table actually exists before using it:

results = self._execute_query(
    "SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = :name",
    {"name": table_name},
)

3. Bracket-quoted identifier

The validated table name is wrapped in SQLite bracket quoting ([table_name]) as a final safeguard:

# AFTER (safe)
table_name = db._validate_table_name(arguments["table_name"])
results = db._execute_query(f"PRAGMA table_info([{table_name}])")

Note on repo structure

The SQLite MCP server was previously archived to servers-archived (which is now read-only and cannot accept PRs). This PR re-introduces the sqlite source with the security fix applied. Maintainers may prefer to apply this patch to the archived repo directly or handle it differently.

Test plan

  • Verify describe_table works correctly with valid table names
  • Verify injection payloads like users); DROP TABLE users; -- are rejected with a clear error
  • Verify non-existent table names return a "Table not found" error
  • Verify names with special characters (;, ', ", -) are rejected by the regex validator

The describe_table tool constructed SQL using f-string interpolation
without sanitizing the table_name parameter, allowing injection attacks
like: `users); DROP TABLE users; --`

Fix applies defense-in-depth with two layers:
1. Regex validation rejects identifiers with non-alphanumeric chars
2. Whitelist validation checks table_name against sqlite_master using
   a parameterized query before use

Also wraps the table name in SQLite bracket-quoting as an extra guard.

Note: The sqlite server was moved to servers-archived (which is now
read-only), so this PR re-introduces the fixed source files. The
maintainers may prefer to apply this patch differently.

Fixes modelcontextprotocol#3314
Ref: OWASP A03:2021 (Injection)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security Audit: SQL injection vulnerability in mcp-server-sqlite

1 participant