Skip to content
GHSA W5pf Xwjh Vr5v

GHSA W5pf Xwjh Vr5v

github.com September 18, 2026

The service_name and span_name fields of a trace-funnel step are interpolated directly into single-quoted ClickHouse string literals in pkg/modules/tracefunnel/clickhouse_queries.go with no escaping. A single quote terminates the literal and the remainder of the value is parsed as SQL, letting a caller read data across the ClickHouse instance beyond what the funnel API exposes, with results returned in the HTTP response.

The funnel analytics query builders interpolate step fields straight into SQL:

ServiceName and SpanName are plain string fields on FunnelStep ( pkg/types/tracefunneltypes/tracefunnel.go ), tagged only json:"service_name" / json:"span_name" , and reach the builder verbatim from the request body via PostableFunnel . Nothing on the path validates or escapes them. sanitizeClause ( pkg/modules/tracefunnel/query.go ) only prefixes a clause with AND despite its name; it escapes nothing. Line 100's latency_pointer_t%d interpolates LatencyPointer the same way. The handlers return the result with aH.Respond(w, results) , so an injected subquery's output comes back in the response, and no blind technique is needed. start_time and end_time reach toDateTime64(%d/1e9, 9) unclamped, so the caller chooses the aggregation window.

The generated query reads signoz_traces.distributed_signoz_index_v3 ; breaking out of the funnel's WITH clause lets the caller reach other databases and tables that signoz's ClickHouse user can read. How far it reaches depends on that user's grants and on whether the driver permits more than one statement per request.

The weakness was introduced with the analytics query builders in da1b6d1ed0 ( #8129 , first released v0.88.0), and the same unescaped-interpolation pattern was present in the earlier two-step form.

Verified on a SigNoz v0.141.0. Each request below submits a funnel definition to an analytics/* endpoint; the service_name field flows into the query builder unescaped. The payload is deliberately benign: it calls version() through throwIf() , disclosing only the ClickHouse version and touching no application data.

Seed data (two services, so a 2-step funnel matches):

Each endpoint is shown twice: a control (well-formed names) and the injection, which sets the second step's service_name to

so the generated SQL becomes

The quote closes the literal, injects the scalar subquery, and -- out the template tail. ClickHouse evaluates throwIf(...) and aborts with the marker and version() (here ch=25.12.5.44 , the server's ClickHouse version) in the error, which shows the injected SQL ran. The overview and steps/overview controls return 500 on an unrelated NaN serialization quirk of the analytics on this small seed set; their injection is still shown by the marker in the ClickHouse error.

POST /api/v1/trace-funnels/analytics/validate → BuildFunnelValidationQuery

POST /api/v1/trace-funnels/analytics/overview → BuildFunnelOverviewQuery

POST /api/v1/trace-funnels/analytics/steps → BuildFunnelCountQuery

POST /api/v1/trace-funnels/analytics/steps/overview → BuildFunnelStepOverviewQuery

POST /api/v1/trace-funnels/analytics/slow-traces → BuildFunnelTopSlowTracesQuery

POST /api/v1/trace-funnels/analytics/error-traces → BuildFunnelTopSlowErrorTracesQuery

The injection lands in a query that reads signoz_traces.distributed_signoz_index_v3 , and the result is returned to the caller. At a minimum it reads data that signoz's ClickHouse user can read, beyond what the funnel API is meant to expose (traces, logs, metrics, and any secrets or PII they carry). Depending on that user's grants and enabled table functions, reporters demonstrated database/table enumeration, SSRF via url() , and local file disclosure via file() ; unbounded aggregations are an availability and cost exposure. Any caller who can submit a funnel definition can trigger it. Operators of self-hosted deployments are most exposed.

Upgrade to v0.142.1 or later, which closes all six endpoints.

Upgrade to v0.142.1 or block POST /api/v1/trace-funnels/analytics/* at the ingress or reverse proxy, and restrict signoz's ClickHouse user to least privilege with the url , file , remote and s3 table functions disabled. There is no configuration flag that disables these routes.

pkg/modules/tracefunnel/clickhouse_queries.go : the interpolation sinks

pkg/modules/tracefunnel/query.go : sanitizeClause , the two-step builder callers

Partial fix: 8e00c0405697 , released v0.142.0

Residual two-step fix: 8286e787b296 , released v0.142.1

Introduced: da1b6d1ed0 , first released v0.88.0

Independently reported, in order of report date:

@morimori-dev (additionally identified the url() SSRF vector)

@newugly (additionally identified the LatencyPointer sink)

@thaidn (reported by Calif.io in collaboration with Anthropic, reference ANT-2026-QNXG9GKJ)