Information Disclosure via Logs
Description
The commit implements a real vulnerability fix by correcting how logging is performed across InfluxDB Flux/FSQL/InfluxQL components. Previously, debug logs could bypass the configured Grafana log level (GF_LOG_LEVEL) due to the use of a package-level or context-insensitive logger, potentially leaking sensitive information or flooding logs. The fix threads a logger through calls, replaces package-level log usage with context-aware loggers, and propagates per-request loggers (and dedicated subs loggers) through Flux/FSQL/InfluxQL pathways, health checks, and DS query handling. This ensures logs respect GF_LOG_LEVEL and reduces the risk of Information Disclosure via debug logs. Affected areas include flux/executor paths, flux/flux.go, fsql, influxql, fsql/fsql.go, health checks, and the main InfluxDB TSDB service wiring to pass logger instances properly.
Proof of Concept
Proof-of-Concept (PoC) to demonstrate pre-fix debug-log leakage and the post-fix protection provided by the commit.
Background assumptions for PoC:
- Grafana is running with an InfluxDB datasource (Flux/FSQL/InfluxQL) configured.
- GF_LOG_LEVEL is set to a non-debug level (e.g., INFO) to suppress verbose logs.
- The vulnerability arises when a logger is created in a package (e.g., a global package-level logger) and used to emit Debug logs that include sensitive data or query payloads, effectively bypassing GF_LOG_LEVEL due to the logger’s design.
Pre-fix (vulnerable behavior) reproduction scenario (conceptual, not tied to exact production code):
- A Flux/FSQL/InfluxQL codepath uses a package-level logger (glog) or derives a context logger from a global logger that is not strictly governed by the per-request GF_LOG_LEVEL.
- A debug log line includes sensitive data (e.g., the raw query string, tokens, or internal state) and is emitted via logger.Debug regardless of the configured log level.
Code sketch illustrating the vulnerability (simplified, representative of the pre-fix path):
// Pseudo-representation of pre-fix behavior
package vulnerable
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/backend"
)
var glog = backend.NewLoggerWith("logger", "tsdb.influx_flux")
func LogQueryPreFix(ctx context.Context, query string) {
// bug: uses a package-level logger to derive a contextual logger
logger := glog.FromContext(ctx)
// Debug logs may be emitted even if GF_LOG_LEVEL=info
logger.Debug("Received Query", "query", query)
}
How to exploit under pre-fix conditions:
1) Set environment GF_LOG_LEVEL=info to suppress debug logs.
2) Trigger a Flux/FSQL/InfluxQL query that causes LogQueryPreFix to be called with a sensitive query payload.
3) Observe Grafana logs containing the sensitive query due to the package-level logger emitting debug logs regardless of GF_LOG_LEVEL.
Post-fix (secure behavior) after this commit:
- The code paths have been refactored to accept an explicit logger parameter and/or pass a per-request logger that is derived from a context-propagated logger attached to the request.
- All internal logging calls now use the per-request logger, and debug logs are emitted only if the corresponding logger’s level allows it.
- Sensitive data in query payloads will not be logged unless the log level is configured to DEBUG.
Code sketch illustrating the post-fix approach (representative):
package fixed
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)
func LogQueryPostFix(ctx context.Context, query string, logger log.Logger) {
// Use the per-request logger, derived from the provided context
logger = logger.FromContext(ctx)
// Debug logs will respect the log level set for this logger
logger.Debug("Received Query", "query", query)
}
How to test post-fix protection using a real Grafana setup:
1) Set GF_LOG_LEVEL=info (or higher) and restart Grafana.
2) Configure an InfluxDB datasource and run a query that would previously log sensitive payloads.
3) Inspect Grafana logs and confirm there are no debug entries containing the raw query or sensitive data.
4) For verification, temporarily set the per-request logger to DEBUG and ensure that, in that scenario, the debug log appears; this confirms the logger respects level settings when configured.
Expected outcome:
- Before the fix, sensitive debug logs could appear at INFO level due to the use of a global/context-insensitive logger.
- After the fix, debug logs are emitted only when the logger’s level permits, mitigating Information Disclosure risk.
Commit Details
Author: Ryan Morris
Date: 2026-07-09 16:51 UTC
Message:
InfluxDB: Fix debug logs bypassing GF_LOG_LEVEL (#128079)
* InfluxDB: Fix debug logs bypassing GF_LOG_LEVEL
* InfluxDB: Fix log level in flux/fsql/influxql packages
Triage Assessment
Vulnerability Type: Information Disclosure
Confidence: HIGH
Reasoning:
Commit description and code changes show refactoring of logging to ensure log messages respect GF_LOG_LEVEL across Flux/InertiaDB components. Previously, debug logs could bypass the configured log level, potentially leaking sensitive information or causing excessive log noise. By threading a logger through calls and using context-based loggers, the fix enforces proper log level handling.
Verification Assessment
Vulnerability Type: Information Disclosure via Logs
Confidence: HIGH
Affected Versions: < 12.4.0
Code Diff
diff --git a/pkg/tsdb/influxdb/flux/executor_test.go b/pkg/tsdb/influxdb/flux/executor_test.go
index 595a193f9e952..4377369e2fef8 100644
--- a/pkg/tsdb/influxdb/flux/executor_test.go
+++ b/pkg/tsdb/influxdb/flux/executor_test.go
@@ -12,6 +12,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana-plugin-sdk-go/backend"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/experimental"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
@@ -64,7 +65,7 @@ func executeMockedQuery(t *testing.T, name string, query queryModel) *backend.Da
query.MaxSeries = 50
}
- dr := executeQuery(context.Background(), glog, query, runner, query.MaxSeries)
+ dr := executeQuery(context.Background(), log.NewNullLogger(), query, runner, query.MaxSeries)
return &dr
}
@@ -229,7 +230,7 @@ func TestRealQuery(t *testing.T) {
runner, err := runnerFromDataSource(dsInfo)
require.NoError(t, err)
- dr := executeQuery(context.Background(), glog, queryModel{
+ dr := executeQuery(context.Background(), log.NewNullLogger(), queryModel{
MaxDataPoints: 100,
RawQuery: "buckets()",
}, runner, 50)
diff --git a/pkg/tsdb/influxdb/flux/flux.go b/pkg/tsdb/influxdb/flux/flux.go
index 852efe4c59e78..8dfd671d0620f 100644
--- a/pkg/tsdb/influxdb/flux/flux.go
+++ b/pkg/tsdb/influxdb/flux/flux.go
@@ -5,19 +5,16 @@ import (
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/backend"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/log"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/influxdata/influxdb-client-go/v2/api"
"github.com/grafana/grafana/pkg/tsdb/influxdb/models"
)
-var (
- glog = backend.NewLoggerWith("logger", "tsdb.influx_flux")
-)
-
// Query builds flux queries, executes them, and returns the results.
-func Query(ctx context.Context, dsInfo *models.DatasourceInfo, tsdbQuery backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
- logger := glog.FromContext(ctx)
+func Query(ctx context.Context, dsInfo *models.DatasourceInfo, tsdbQuery backend.QueryDataRequest, logger log.Logger) (*backend.QueryDataResponse, error) {
+ logger = logger.FromContext(ctx)
tRes := backend.NewQueryDataResponse()
logger.Debug("Received a query", "query", tsdbQuery)
r, err := runnerFromDataSource(dsInfo)
diff --git a/pkg/tsdb/influxdb/fsql/client.go b/pkg/tsdb/influxdb/fsql/client.go
index 0935fa8815a0b..cd3b49e8cfd7c 100644
--- a/pkg/tsdb/influxdb/fsql/client.go
+++ b/pkg/tsdb/influxdb/fsql/client.go
@@ -12,6 +12,7 @@ import (
"github.com/apache/arrow-go/v18/arrow/ipc"
"github.com/apache/arrow-go/v18/arrow/memory"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@@ -29,8 +30,8 @@ func (c *client) FlightClient() flight.Client {
return c.Client.Client
}
-func newFlightSQLClient(addr string, metadata metadata.MD, secure bool, tlsConfig *httpclient.TLSOptions, proxyClient proxy.Client) (*client, error) {
- dialOptions, err := grpcDialOptions(secure, tlsConfig, proxyClient)
+func newFlightSQLClient(addr string, metadata metadata.MD, secure bool, tlsConfig *httpclient.TLSOptions, proxyClient proxy.Client, logger log.Logger) (*client, error) {
+ dialOptions, err := grpcDialOptions(secure, tlsConfig, proxyClient, logger)
if err != nil {
return nil, fmt.Errorf("grpc dial options: %s", err)
}
@@ -47,7 +48,7 @@ func newFlightSQLClient(addr string, metadata metadata.MD, secure bool, tlsConfi
return &client{Client: fsqlClient, md: metadata}, nil
}
-func grpcDialOptions(secure bool, tlsConfig *httpclient.TLSOptions, proxyClient proxy.Client) ([]grpc.DialOption, error) {
+func grpcDialOptions(secure bool, tlsConfig *httpclient.TLSOptions, proxyClient proxy.Client, logger log.Logger) ([]grpc.DialOption, error) {
dialOptions := []grpc.DialOption{}
secureDialOpt := grpc.WithTransportCredentials(insecure.NewCredentials())
@@ -70,7 +71,7 @@ func grpcDialOptions(secure bool, tlsConfig *httpclient.TLSOptions, proxyClient
}
dialOptions = append(dialOptions, grpc.WithContextDialer(func(ctx context.Context, host string) (net.Conn, error) {
- logger := glog.FromContext(ctx)
+ logger := logger.FromContext(ctx)
logger.Debug("Dialing secure socks proxy", "host", host)
conn, err := dialer.Dial("tcp", host)
if err != nil {
diff --git a/pkg/tsdb/influxdb/fsql/fsql.go b/pkg/tsdb/influxdb/fsql/fsql.go
index b71db1e9eccd1..ceb00831cef28 100644
--- a/pkg/tsdb/influxdb/fsql/fsql.go
+++ b/pkg/tsdb/influxdb/fsql/fsql.go
@@ -7,6 +7,7 @@ import (
"net/url"
"github.com/grafana/grafana-plugin-sdk-go/backend"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/log"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
@@ -14,21 +15,17 @@ import (
"github.com/grafana/grafana/pkg/tsdb/influxdb/models"
)
-var (
- glog = backend.NewLoggerWith("logger", "tsdb.influx_flightsql")
-)
-
type SQLOptions struct {
Addr string `json:"host"`
Metadata []map[string]string `json:"metadata"`
Token string `json:"token"`
}
-func Query(ctx context.Context, dsInfo *models.DatasourceInfo, req backend.QueryDataRequest) (
+func Query(ctx context.Context, dsInfo *models.DatasourceInfo, req backend.QueryDataRequest, logger log.Logger) (
*backend.QueryDataResponse, error) {
- logger := glog.FromContext(ctx)
+ logger = logger.FromContext(ctx)
tRes := backend.NewQueryDataResponse()
- r, err := runnerFromDataSource(dsInfo)
+ r, err := runnerFromDataSource(dsInfo, logger)
if err != nil {
return tRes, err
}
@@ -126,7 +123,7 @@ func ParseURL(endpoint string) (string, error) {
}
// runnerFromDataSource creates a runner from the datasource model (the datasource instance's configuration).
-func runnerFromDataSource(dsInfo *models.DatasourceInfo) (*runner, error) {
+func runnerFromDataSource(dsInfo *models.DatasourceInfo, logger log.Logger) (*runner, error) {
if dsInfo.URL == "" {
return nil, fmt.Errorf("missing URL from datasource configuration")
}
@@ -144,7 +141,7 @@ func runnerFromDataSource(dsInfo *models.DatasourceInfo) (*runner, error) {
md.Set("Authorization", fmt.Sprintf("Bearer %s", dsInfo.Token))
}
- fsqlClient, err := newFlightSQLClient(u, md, !dsInfo.InsecureGrpc, dsInfo.TLSConfig, dsInfo.ProxyClient)
+ fsqlClient, err := newFlightSQLClient(u, md, !dsInfo.InsecureGrpc, dsInfo.TLSConfig, dsInfo.ProxyClient, logger)
if err != nil {
return nil, err
}
diff --git a/pkg/tsdb/influxdb/fsql/fsql_test.go b/pkg/tsdb/influxdb/fsql/fsql_test.go
index 6d53e868131da..acff306a6fc36 100644
--- a/pkg/tsdb/influxdb/fsql/fsql_test.go
+++ b/pkg/tsdb/influxdb/fsql/fsql_test.go
@@ -13,6 +13,7 @@ import (
"github.com/apache/arrow-go/v18/arrow/flight/flightsql/example"
"github.com/apache/arrow-go/v18/arrow/memory"
"github.com/grafana/grafana-plugin-sdk-go/backend"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
@@ -93,6 +94,7 @@ func (suite *FSQLTestSuite) TestIntegration_QueryData() {
},
},
},
+ log.NewNullLogger(),
)
require.NoError(suite.T(), err)
@@ -159,6 +161,7 @@ func TestInvalidSchema(t *testing.T) {
},
},
},
+ log.NewNullLogger(),
)
require.Equal(t, backend.ErrorSourceDownstream, resp.Responses["A"].ErrorSource)
}
diff --git a/pkg/tsdb/influxdb/healthcheck.go b/pkg/tsdb/influxdb/healthcheck.go
index f0c4d52309184..5d69a0520f912 100644
--- a/pkg/tsdb/influxdb/healthcheck.go
+++ b/pkg/tsdb/influxdb/healthcheck.go
@@ -23,7 +23,7 @@ const (
func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult,
error) {
- logger := logger.FromContext(ctx)
+ logger := s.logger.FromContext(ctx)
dsInfo, err := s.getDSInfo(ctx, req.PluginContext)
if err != nil {
return getHealthCheckMessage(logger, "error getting datasource info", err)
@@ -35,20 +35,20 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
switch dsInfo.Version {
case influxVersionFlux:
- return CheckFluxHealth(ctx, dsInfo, req)
+ return CheckFluxHealth(ctx, dsInfo, req, s.fluxLogger)
case influxVersionInfluxQL:
- return CheckInfluxQLHealth(ctx, dsInfo, req)
+ return CheckInfluxQLHealth(ctx, dsInfo, req, s.influxqlLogger)
case influxVersionSQL:
- return CheckSQLHealth(ctx, dsInfo, req)
+ return CheckSQLHealth(ctx, dsInfo, req, s.fsqlLogger)
default:
return getHealthCheckMessage(logger, "", errors.New("unknown influx version"))
}
}
func CheckFluxHealth(ctx context.Context, dsInfo *models.DatasourceInfo,
- req *backend.CheckHealthRequest) (*backend.CheckHealthResult,
+ req *backend.CheckHealthRequest, logger log.Logger) (*backend.CheckHealthResult,
error) {
- logger := logger.FromContext(ctx)
+ logger = logger.FromContext(ctx)
ds, err := flux.Query(ctx, dsInfo, backend.QueryDataRequest{
PluginContext: req.PluginContext,
Queries: []backend.DataQuery{
@@ -63,7 +63,7 @@ func CheckFluxHealth(ctx context.Context, dsInfo *models.DatasourceInfo,
},
},
},
- })
+ }, logger)
if err != nil {
return getHealthCheckMessage(logger, "error performing flux query", err)
@@ -80,8 +80,8 @@ func CheckFluxHealth(ctx context.Context, dsInfo *models.DatasourceInfo,
return getHealthCheckMessage(logger, "", errors.New("error getting flux query buckets"))
}
-func CheckInfluxQLHealth(ctx context.Context, dsInfo *models.DatasourceInfo, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
- logger := logger.FromContext(ctx)
+func CheckInfluxQLHealth(ctx context.Context, dsInfo *models.DatasourceInfo, req *backend.CheckHealthRequest, logger log.Logger) (*backend.CheckHealthResult, error) {
+ logger = logger.FromContext(ctx)
tracer := tracing.DefaultTracer()
resp, err := influxql.Query(ctx, tracer, dsInfo, &backend.QueryDataRequest{
PluginContext: req.PluginContext,
@@ -93,7 +93,7 @@ func CheckInfluxQLHealth(ctx context.Context, dsInfo *models.DatasourceInfo, req
JSON: []byte(`{"query": "SHOW measurements", "rawQuery": true}`),
},
},
- })
+ }, logger)
if err != nil {
return getHealthCheckMessage(logger, "error performing influxQL query", err)
}
@@ -115,7 +115,8 @@ func CheckInfluxQLHealth(ctx context.Context, dsInfo *models.DatasourceInfo, req
return getHealthCheckMessage(logger, "", errors.New("error connecting influxDB influxQL"))
}
-func CheckSQLHealth(ctx context.Context, dsInfo *models.DatasourceInfo, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
+func CheckSQLHealth(ctx context.Context, dsInfo *models.DatasourceInfo, req *backend.CheckHealthRequest, logger log.Logger) (*backend.CheckHealthResult, error) {
+ logger = logger.FromContext(ctx)
ds, err := fsql.Query(ctx, dsInfo, backend.QueryDataRequest{
PluginContext: req.PluginContext,
Queries: []backend.DataQuery{
@@ -130,7 +131,7 @@ func CheckSQLHealth(ctx context.Context, dsInfo *models.DatasourceInfo, req *bac
},
},
},
- })
+ }, logger)
if err != nil {
return getHealthCheckMessage(logger, "error performing sql query", err)
diff --git a/pkg/tsdb/influxdb/influxdb.go b/pkg/tsdb/influxdb/influxdb.go
index 20a6b43f9010a..6e5103e9825d3 100644
--- a/pkg/tsdb/influxdb/influxdb.go
+++ b/pkg/tsdb/influxdb/influxdb.go
@@ -20,19 +20,28 @@ import (
"github.com/grafana/grafana/pkg/tsdb/influxdb/models"
)
-var logger log.Logger = backend.NewLoggerWith("logger", "tsdb.influxdb")
-
type Service struct {
- im instancemgmt.InstanceManager
+ im instancemgmt.InstanceManager
+ logger log.Logger
+ fluxLogger log.Logger
+ influxqlLogger log.Logger
+ fsqlLogger log.Logger
}
func ProvideService(httpClient *httpclient.Provider) *Service {
+ // Constructed here (not as a package-level var) so it picks up Grafana's
+ // in-process logger override installed during coreplugin init.
+ logger := backend.NewLoggerWith("logger", "tsdb.influxdb")
return &Service{
- im: datasource.NewInstanceManager(NewInstanceSettings(httpClient)),
+ im: datasource.NewInstanceManager(NewInstanceSettings(httpClient, logger)),
+ logger: logger,
+ fluxLogger: backend.NewLoggerWith("logger", "tsdb.influx_flux"),
+ influxqlLogger: backend.NewLoggerWith("logger", "tsdb.influx_influxql"),
+ fsqlLogger: backend.NewLoggerWith("logger", "tsdb.influx_flightsql"),
}
}
-func NewInstanceSettings(httpClientProvider *httpclient.Provider) datasource.InstanceFactoryFunc {
+func NewInstanceSettings(httpClientProvider *httpclient.Provider, logger log.Logger) datasource.InstanceFactoryFunc {
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
opts, err := settings.HTTPClientOptions(ctx)
if err != nil {
@@ -97,7 +106,7 @@ func NewInstanceSettings(httpClientProvider *httpclient.Provider) datasource.Ins
}
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
- logger := logger.FromContext(ctx)
+ logger := s.logger.FromContext(ctx)
logger.Debug("Received a query request", "numQueries", len(req.Queries))
tracer := tracing.DefaultTracer()
@@ -111,11 +120,11 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
switch dsInfo.Version {
case influxVersionFlux:
- return flux.Query(ctx, dsInfo, *req)
+ return flux.Query(ctx, dsInfo, *req, s.fluxLogger)
case influxVersionInfluxQL:
- return influxql.Query(ctx, tracer, dsInfo, req)
+ return influxql.Query(ctx, tracer, dsInfo, req, s.influxqlLogger)
case influxVersionSQL:
- return fsql.Query(ctx, dsInfo, *req)
+ return fsql.Query(ctx, dsInfo, *req, s.fsqlLogger)
default:
return nil, fmt.Errorf("unknown influxdb version")
}
diff --git a/pkg/tsdb/influxdb/influxql/influxql.go b/pkg/tsdb/influxdb/influxql/influxql.go
index 75cf6fd307517..e51f2ffc6b4b0 100644
--- a/pkg/tsdb/influxdb/influxql/influxql.go
+++ b/pkg/tsdb/influxdb/influxql/influxql.go
@@ -30,11 +30,10 @@ const (
var (
ErrInvalidHttpMode = errors.New("'httpMode' should be either 'GET' or 'POST'")
ErrInvalidUrl = errors.New("URL must contain scheme and host")
- glog = backend.NewLoggerWith("logger", "tsdb.influx_influxql")
)
-func Query(ctx context.Context, tracer trace.Tracer, dsInfo *models.DatasourceInfo, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
- logger := glog.FromContext(ctx)
+func Query(ctx context.Context, tracer trace.Tracer, dsInfo *models.DatasourceInfo, req *backend.QueryDataRequest, logger
... [truncated]