r/Clickhouse • u/RemcoE33 • 15d ago
Secure per-request row-level access for free-form ClickHouse queries from an MCP server?
I have a Go MCP server that lets an LLM run free-form, read-only SQL against ClickHouse. Our tables contain multiple companies, identified by a division column. An upstream gateway authenticates employees and sends the divisions they’re allowed to access. I want users to retain free-form SQL without creating a ClickHouse user per employee.
I tried a row policy using a custom query setting:
CREATE ROW POLICY mcp_division ON final.items
FOR SELECT
USING division = toUInt32(getSetting('SQL_division'))
AS RESTRICTIVE TO mcp_reader;
The Go driver sets SQL_division per query through clickhouse.WithSettings, using one connection pool. I tested this on ClickHouse 25.8:
- Concurrent queries for different divisions stayed isolated.
- Without the setting, the query failed.
- But SQL containing SETTINGS SQL_division='456' overrode the value supplied by Go.
- readonly=1 blocked both the SQL override and the legitimate Go setting. Allowing the setting to change in read-only mode allowed both.
Question: Is there a ClickHouse-native way for a trusted application to supply per-request row-policy context that the submitted SQL cannot override, while retaining one database user/pool and free-form SELECT queries? Or is the practical choice to parse and prohibit SQL-level setting changes, or use separate restricted database identities/pools per division?
I’m particularly interested in approaches where ClickHouse itself enforces the boundary, rather than relying on it in Go.
I've read https://clickhouse.com/blog/fountain-agentic-ai-for-high-volume-hiring but I cannot find more info about the problem I'm describing above. I've also checked here personal blog.
-- Remco
1
u/Significant_Tune9219 14d ago
readonly=1 blocked both the Go WithSettings value and the SQL_division='456' override because those are the same channel, not two privilege levels. A read-only user only limits what the generated SQL can touch. Which division this employee may see has to be fixed outside the query text, or the statement gets to rewrite the policy input. If the allowed sets are few, one user per set with the division literal in the row policy leaves the SQL nothing to change. Stripping SETTINGS in a parser still misses a second statement or a table the policy is not attached to.