Summary
/v1/pipes/{name} runs its SQL through the pipe cache + singleflight with no mutation guard, even though the shared executor deliberately supports mutation-verb SQL. A pipe that writes returns a cached [] on repeat calls and never re-executes.
Detail
internal/api/clickhouse_exec.go:26-31 dispatches mutation-verb SQL through conn.Exec, so an INSERT/ALTER pipe does execute the first time.
internal/api/pipes.go:176-221 caches the result under queryCacheKey(sql, params) with cache.QueryTimeToTTL (floor 10s, up to 1h) and wraps execution in singleflight — with no isMutation(sql) check on either the Put (store) or execute path.
Scenario: an admin authors INSERT INTO audit_log VALUES ({{msg}}, now()) with allowed_roles: ["writer"]. First call inserts and caches []. Every identical call within the TTL returns 200 + X-Cache: HIT and never reaches ClickHouse; concurrent identical calls are collapsed by singleflight to a single insert.
Impact
Silent data loss on mutation pipes. Docs frame pipes as the cached read path but never forbid mutation SQL, and the exec layer explicitly supports it.
Fix direction
Either reject mutation SQL at pipes.Store.Put, or bypass cache + singleflight when isMutation(sql).
Found in a repo-wide audit; verified by code trace. Distinct from #321/#365.
Summary
/v1/pipes/{name}runs its SQL through the pipe cache + singleflight with no mutation guard, even though the shared executor deliberately supports mutation-verb SQL. A pipe that writes returns a cached[]on repeat calls and never re-executes.Detail
internal/api/clickhouse_exec.go:26-31dispatches mutation-verb SQL throughconn.Exec, so an INSERT/ALTER pipe does execute the first time.internal/api/pipes.go:176-221caches the result underqueryCacheKey(sql, params)withcache.QueryTimeToTTL(floor 10s, up to 1h) and wraps execution in singleflight — with noisMutation(sql)check on either thePut(store) or execute path.Scenario: an admin authors
INSERT INTO audit_log VALUES ({{msg}}, now())withallowed_roles: ["writer"]. First call inserts and caches[]. Every identical call within the TTL returns200+X-Cache: HITand never reaches ClickHouse; concurrent identical calls are collapsed by singleflight to a single insert.Impact
Silent data loss on mutation pipes. Docs frame pipes as the cached read path but never forbid mutation SQL, and the exec layer explicitly supports it.
Fix direction
Either reject mutation SQL at
pipes.Store.Put, or bypass cache + singleflight whenisMutation(sql).Found in a repo-wide audit; verified by code trace. Distinct from #321/#365.