Skip to content

Commit 5ba735c

Browse files
committed
fix(clickhouse): enforce server-side readonly=1 on the query operation
1 parent 30bdf8a commit 5ba735c

1 file changed

Lines changed: 11 additions & 2 deletions

File tree

  • apps/sim/app/api/tools/clickhouse

apps/sim/app/api/tools/clickhouse/utils.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ export interface ClickHouseIntrospectionResult {
6363
*/
6464
async function clickhouseRequest(
6565
config: ClickHouseConnectionConfig,
66-
statement: string
66+
statement: string,
67+
options: { readOnly?: boolean } = {}
6768
): Promise<ClickHouseHttpResult> {
6869
const hostValidation = await validateDatabaseHost(config.host, 'host')
6970
if (!hostValidation.isValid) {
@@ -73,6 +74,12 @@ async function clickhouseRequest(
7374
const protocol = config.secure ? 'https' : 'http'
7475
const url = new URL(`${protocol}://${config.host}:${config.port}/`)
7576
url.searchParams.set('database', config.database)
77+
if (options.readOnly) {
78+
// Server-enforced read-only: ClickHouse rejects any write/DDL and forbids the
79+
// query from re-enabling writes via `SET readonly=0`. This is the real boundary
80+
// for the query operation; the SQL-shape checks below are defense-in-depth.
81+
url.searchParams.set('readonly', '1')
82+
}
7683

7784
const controller = new AbortController()
7885
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS)
@@ -293,7 +300,9 @@ export async function executeClickHouseQuery(
293300
)
294301
}
295302
}
296-
const result = await clickhouseRequest(config, ensureJsonFormat(query))
303+
const result = await clickhouseRequest(config, ensureJsonFormat(query), {
304+
readOnly: options.enforceReadOnly,
305+
})
297306
return parseRowsResult(result)
298307
}
299308

0 commit comments

Comments
 (0)