CVE-2026-92729: SigNoz trace-funnel analytics exposed without authentication

Viewer analytics without a login

SigNoz query-service allows unauthenticated callers to reach trace-funnel analytics through six endpoints that accept a funnel definition in the request body. The missing access checks let anyone with network access to the affected web/API service request analytics normally available to an authenticated viewer.

A trace records work performed while handling a request, and a span represents an individual operation within that trace. A trace funnel analyzes a sequence of steps identified by service and span names. Its analytics can show how activity moves through those steps, where errors occur, and which traces are slow.

The declared affected range is v0.88.0 through v0.141.0, with v0.141.1 identified as the fixed release. Runtime observations cover v0.140.0; source checks also covered v0.129.0 and v0.141.0. The full declared range was not tested at runtime.

How the request crosses the missing access boundary

The failure begins in RegisterTraceFunnelsRoutes(), in pkg/query-service/app/http_handler.go. It registers 12 POST analytics routes without am.ViewAccess(), the access-control wrapper used to enforce viewer access. Neighboring routes for creating, reading, updating, and deleting funnels use am.ViewAccess() or am.EditAccess(). The registration function is shared by the community and enterprise editions.

Six affected routes accept a complete funnel definition under /api/v1/trace-funnels/analytics/, with the suffixes validate, overview, steps, steps/overview, slow-traces, and error-traces. The other six use the saved-funnel form, /api/v1/trace-funnels/{funnel_id}/analytics/*. The saved-funnel variants also lack middleware role checks, but only the six payload routes were exercised at runtime.

The payload handlers provide no fallback authentication check. They decode a traceFunnels.PostableFunnel object and proceed without checking authentication claims or organization context. The attacker controls start_time and end_time, expressed as nanoseconds since the Unix epoch, and the steps array containing service_name and span_name. The steps/overview, slow-traces, and error-traces operations also use step_start and step_end. Only the validation handler rejects definitions with fewer than two steps; the other five payload handlers pass the steps onward without that check.

The handlers send these values to the trace-funnel query module. Builders such as tracefunnel.BuildFunnelValidationQuery turn them into SQL, and GetListResultV3(ctx, chq.Query) executes the resulting query against ClickHouse, the database holding trace data. Queried tables include signoz_traces.distributed_signoz_index_v3. Because both the route wrapper and a handler-level authentication check are absent, an unauthenticated request reaches database query execution.

No saved funnel, credentials, or user interaction is required. The demonstrated instance had not completed its initialization wizard and had no users. The route-registration flaw also indicates applicability after initialization, although initialized instances were not retested. Query execution still requires the ClickHouse trace schema and the queried tables to exist.

What the analytics can disclose

The exposed operations provide several forms of viewer-level information:

Operation Analytics returned
steps Per-step span and error counts
overview and steps/overview Time-series funnel analytics
slow-traces and error-traces Trace identifiers

With a populated trace dataset, these query capabilities can reveal trace identifiers, durations, span counts, service topology, error activity, and time-series analytics for attacker-selected services, spans, and time ranges.

The observed dataset contained no trace records: per-step counts were zero, and slow-trace and error-trace results were null. Access to the query capability was demonstrated; disclosure of populated telemetry follows from the endpoint and query behavior rather than an observed retrieval of populated trace data.

Additional SQL injection and server-side requests

Additional SQL injection and server-side request forgery were demonstrated on v0.140.0 through this request path. SQL injection lets input change database expressions instead of remaining data. Server-side request forgery, or SSRF, causes a server to fetch a resource selected by an attacker. These are distinct from the CVE’s missing authentication and authorization, and their affected version ranges have not been established.

The SQL construction defines named pairs of service and span values in a WITH clause, which makes those values available to the rest of the query. It uses fmt.Sprintf to insert step.ServiceName and step.SpanName into quoted SQL tuples without escaping them. Consequently, SQL delimiters in those fields can change the query’s structure. The observed technique coordinated delimiters and block comments across two steps, preserving valid tuples while introducing an attacker-controlled expression into a single SELECT query.

Raw database errors create a disclosure channel for the expression’s result. An injected expression can pass nonnumeric text into a UInt8 conversion, which expects an unsigned eight-bit integer. ClickHouse includes the text it cannot convert in its error. The handler passes that error through RespondError into an HTTP 500 response, allowing the caller to read the evaluated text.

ClickHouse’s url() table function can fetch HTTP resources for use in queries. Placing it inside the injected expression makes ClickHouse issue the request; passing the fetched text into the same failing conversion returns that text through the error channel. The demonstrated fetch retrieved the SigNoz service’s /api/v1/version JSON response.

This chain depends on the database account’s permissions, availability of url(), and HTTP destinations reachable from the ClickHouse server. The observed injection operated within one SELECT; multiple statements were rejected. Target code execution and target-side file or database writes were not demonstrated. Observed write restrictions depended on the tested ClickHouse version and configuration, so they do not establish that every deployment lacks a write-capable path.

Upgrade and interpret exposure checks correctly

Upgrade affected installations to SigNoz v0.141.1 or later. The reported fix, commit f78bd49 in PR #12817, adds am.ViewAccess() to all 12 trace-funnel analytics routes, restoring the missing access checks. The fix is supported by code and release information; rejection of equivalent unauthenticated requests on the fixed release is inferred from the wrapper and was not demonstrated at runtime.

That access-control change does not establish an independent fix for unsafe SQL interpolation, raw error disclosure, or url() behavior. The additional findings should therefore not be assumed resolved at their underlying query and error-handling layers solely because the routes gained authentication.

The public /api/v1/version endpoint exposes a version value that can be compared with the declared affected range. When interpreting endpoint behavior, use the correct method: these analytics routes are POST-only. A bare GET can return the single-page application’s HTML with status 200, which does not establish access to analytics data.

The observed authentication difference was an unauthenticated GET to /api/v1/trace-funnels/list returning 401 while unauthenticated POSTs to all six payload analytics routes reached their handlers. A successful analytics response or an error known to occur after query execution can demonstrate the missing boundary. An HTTP 500 alone is inconclusive: overview and steps/overview produced post-query serialization failures involving NaN, a “not a number” value the response serializer could not encode.

The demonstrated SQL injection error channel included error converting clickhouse results to list and conversion errors of the form Cannot parse string ... as UInt8, containing evaluated text. These messages can help identify use of that error channel when associated with the affected analytics requests, but a conversion error by itself does not prove injection.