ClickHouse access governance — every statement reviewed before it runs
Last updated
AccessFlow governs ClickHouse as a full query proxy: your team connects to AccessFlow, not to the database, and every statement is parsed, checked against a schema allow-list, risk-scored, routed to an approver, and only then executed — under column masking, row-level security and a row cap. The pipeline itself — parse, risk-score, route, approve, execute, audit — is the same for every relational engine and is described in full on the database access governance page. What follows is what is specific to ClickHouse.
- Family
- Relational
- Query language
- SQL
- Connects via
- Database driver
- Default port
8123- Default SSL mode
REQUIRE- Install
- One click — checksum-verified download
The driver and the connection
ClickHouse is a built-in catalog connector that uses the generic SQL dialect rather than one of the five with a dialect of their own. Its driver, com.clickhouse:clickhouse-jdbc, is pinned in the catalog and installed in one click, and it is loaded on its own — so the ClickHouse driver version you pick cannot disturb any other datasource.
That generic dialect is the same path any other JDBC-compatible engine can use: upload a driver JAR with its SHA-256, and AccessFlow will govern it the same way. ClickHouse is simply a driver that ships pre-declared in the catalog so you do not have to.
The datasource takes host, port (8123 by default for the HTTP interface), database, username and password, AES-256-GCM encrypted at rest. SSL defaults to DISABLE, reflecting the common in-cluster deployment — set it to REQUIRE or a verifying mode for anything crossing a network boundary.
AccessFlow builds the connection string from the datasource fields using the template the connector manifest declares, so what actually reaches the driver is predictable and reviewable:
jdbc:ch://{host}:{port}/{database_name}
com.clickhouse.jdbc.ClickHouseDriver
The driver is fetched the first time a datasource of this type is created or tested, and every driver AccessFlow loads is kept separate from every other one. Two datasources on different driver versions — even two ClickHouse datasources — never share a copy and cannot interfere with each other.
What AccessFlow parses
Statements are parsed before anything else happens, and the schema allow-list is enforced against the tables the statement really references. Because ClickHouse runs on the generic dialect, the parse uses the standard SQL grammar rather than a ClickHouse-aware one — a statement using syntax outside that grammar is refused with HTTP 422 rather than passed through unparsed, which is the fail-closed direction.
What it refuses
Multi-statement input is refused, as is anything the parser cannot read. The rejection happens before AccessFlow reaches the database at all.
Row-level security and masking
A policy is spliced into the WHERE clause as a bound predicate, and shapes that cannot be provably narrowed are refused. Masking is applied to the fetched result by the same masker every engine shares.
What the rewrite actually does
A policy restricting events to the caller's tenant narrows the read before ClickHouse receives it:
-- submitted SELECT event_type, count() FROM events WHERE day >= '2026-08-01' GROUP BY event_type; -- executed (policy: events.tenant = the caller's tenant) SELECT event_type, count() FROM events WHERE (day >= '2026-08-01') AND events.tenant = ? GROUP BY event_type;
The predicate is added before the GROUP BY, so the aggregate is computed over the rows the caller is allowed to see. Filtering afterwards would produce a different — and wrong — number, which is why the rewriter works on the parsed statement rather than on its text.
Introspection, dry-run and cost estimates
ClickHouse has no dialect-specific plan reader, so a dry-run reports itself unsupported with a localized reason rather than guessing at a plan. Everything else in the pre-flight path still runs — the statement is parsed, the allow-list checked, row security resolved, and the automatic submission estimate persisted with that unsupported state recorded — so a reviewer always sees a definitive answer rather than a blank panel.