Skip to content

security: enforce application-layer validation for DDL/DML in read-only mode - #151

Closed
shuvajyotikar13 wants to merge 2 commits into
ClickHouse:mainfrom
shuvajyotikar13:patch-2
Closed

shuvajyotikar13 wants to merge 2 commits into
ClickHouse:mainfrom
shuvajyotikar13:patch-2

Conversation

@shuvajyotikar13

@shuvajyotikar13 shuvajyotikar13 commented Mar 22, 2026

Copy link
Copy Markdown

Description

The Problem:

Authorization Bypass in Default Configuration

The current implementation of _validate_query_for_destructive_ops contains a logic flaw that creates a "fail-open" security state when the server is deployed in its default read-only mode (allow_write_access = False).

Specifically, the function returns early without inspecting the SQL string if write access is disabled, delegating all security responsibility to the clickhouse-connect driver's readonly=1 flag. However, ClickHouse evaluates permissions server-side; if the authenticated database user possesses elevated privileges, the server silently overrides the driver's read-only request. This allows an autonomous agent or a malicious actor (via prompt injection) to execute destructive DDL commands like DROP TABLE despite the server being configured for safety.

The Fix: Application-Layer Regex Firewall

This PR introduces a strict validation gate that executes regardless of the allow_write_access setting.

Comment Stripping: The patch strips SQL comments (-- and /* */) before evaluation to prevent attackers from "hiding" destructive commands from the regex.

Prefix Allow-listing: When in read-only mode, the server now enforces a strict allow-list of safe SQL prefixes: SELECT, WITH, SHOW, DESCRIBE, EXPLAIN, and EXISTS.

Protocol Compliance: Ensures that any violation results in a standard ToolError, which is handled gracefully by the FastMCP framework.

Verification & Testing

I have added a new test suite, tests/test_clickhouse_security.py, which mocks the server configuration to prove the bypass exists in the unpatched version.

Test 1 (Read-Only Logic Bypass): Confirms that a DROP TABLE command is ignored by the current validation logic when allow_write_access is False. (Fails on main, Passes with this PR).

Test 2 (Intent Validation): Confirms that the existing regex logic is still respected when writes are enabled but drops are restricted.

Enterprise Impact

In an enterprise agentic workflow, this vulnerability represents a critical Privilege Escalation vector. By enforcing validation at the application layer, we ensure that even if a database user is over-privileged, the MCP server acts as a definitive guardrail against accidental or malicious data destruction.

…ly mode

Refactor _validate_query_for_destructive_ops to enhance security checks for read-only access and clarify documentation.
@joe-clickhouse

Copy link
Copy Markdown
Collaborator

Hey there @shuvajyotikar13 sorry for the delay here and thanks for the contribution. I just checked this with clickhouse-connect 1.8.0 against recent server versions (e.g. 26.3 and 26.8). The older server did have SET readonly=DEFAULT and nested SETTINGS constraint bypasses which are now fixed. Specifically in ClickHouse/ClickHouse#115334 and ClickHouse/ClickHouse#115397. The direct mcp-clickhouse bug was already fixed by #93 before this PR opened. Consequently, this allowlist wouldn't cover the server bypasses because nested SELECT/WITH will pass it, as does valid WITH ... INSERT, while some valid read-only statements are rejected. The separate write-mode guard was later enhanced in #220. Therefore, I'm closing this as already fixed but thanks again!

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.

2 participants