Skip to content
← Bounty Board

Marine HQ · Registry · 2026

ClickScope

Sounding Line

฿ 260,000,000

Read-only ClickHouse access for an AI agent, enforced three separate ways — statement policy, server-side caps, database grants — so a hijacked prompt still can't drop a table or scan a billion rows it was only meant to query. The layer most MCP database servers skip entirely.

securitymcpclickhousepython
PythonDossier sealed — source is private

The moment you give an agent a database connection, you've made a decision that most people don't think hard enough about. You've handed something that reasons over untrusted text — logs, tickets, scraped pages, whatever else is in its context — the ability to act on your data. If that text ever contains an instruction disguised as content, the agent doesn't know the difference between "the user asked this" and "the log line said this." That's the whole prompt injection problem, and a database credential turns it from an annoyance into an incident.

I built ClickScope because "just don't let it write" isn't a security control, it's a hope. So the read-only guarantee doesn't live in one place — it's enforced three separate times, on purpose:

  1. Statement policy — before anything touches the network, the SQL has to open with SELECT, WITH…SELECT, SHOW, DESCRIBE, or EXPLAIN. DDL and DML keywords, INTO OUTFILE, inline SETTINGS overrides — rejected before a connection is even opened.
  2. Server-enforced caps — every query still runs with readonly=1, plus hard limits on execution time, rows read, bytes read, memory, and threads. These bind inside ClickHouse itself, so they hold even if something upstream of them is wrong.
  3. Database grants — the connection is a user with SELECT only. This is the layer I actually trust, because it's the one an application bug can't argue with.

Three layers because I don't want the safety of a production database resting on me having gotten one regex right.

The other half of the problem is cost, not just safety. A billion-row ClickHouse table doesn't care that your query was well-intentioned — a filter that misses the sorting key turns into a full scan, and an agent that can't tell the difference will happily hand you one. So before anything runs, estimate_query_cost runs ClickHouse's own planner against it and tells you, in advance: how many parts, how many rows, what fraction of the table. verdict=full_scan before a single byte moves is the entire point — the agent gets to correct itself instead of you finding out from a Slack alert.

Credentials get the same treatment as the queries. A literal password in a config file is rejected outright, with an error pointing at the fix — not a warning, because people commit past warnings. Every secret is a reference — to an env var, a file, or the output of a command you already trust — resolved once at startup and never re-expanded, so a secret that happens to look like another reference can't be used to read it. And a multi-cluster profiles file that's writable by anyone but its owner is refused for the same reason ssh refuses a world-readable private key: a ${cmd:...} reference means the file decides what gets executed, so its integrity matters as much as its contents.

None of this is exotic. It's the same access-control discipline you'd expect from any production database client — least privilege, defense in depth, no secrets in plaintext. The only thing that's new is who's holding the connection.