mcp-grafana
- Type
- MCP server
- Scanned
- Oct 29, 2026
- Version
- 0f5eebe984a0
- Files read
- 600
This official server lets AI assistants query your Grafana data sources, manage alerts and incidents, and view or modify dashboards. The biggest risks are that attackers or rogue prompts can modify your Grafana setup by default, bypass read-only protections using query injection and URL manipulation flaws, or connect without a password when using the default Docker setup.
What it can reach
- Network
- Connects to your Grafana server and its data sources, fetches documentation and plugin lists from grafana.com, sends anonymous usage data to stats.grafana.org by default, and can open a local or network port to listen for requests.
- Secrets
- Reads Grafana login credentials, API keys, service account tokens, and security certificates from environment variables, command-line flags, local files, or incoming web requests.
- Files
- Reads user-specified certificate and token files, and its Claude plugin installer downloads and saves the program executable to the local disk.
- Shell commands
- The Claude plugin helper script runs system extraction tools (tar or PowerShell) to unpack and launch the downloaded program.
Findings
-
HighURL path traversal and arbitrary GET response leak in list_loki_label_values
The list_loki_label_values tool interpolates the caller-controlled labelName argument directly into the request path without url.PathEscape or path validation, and makeRequest subsequently parses the concatenated string with url.Parse. A prompt-injected agent can supply relative path segments (../) and a '#' character (which turns the trailing '/values' into a URL fragment that is never sent over HTTP) to traverse to arbitrary Grafana or datasource proxy GET endpoints. When the response JSON does not have status='success', fetchData includes the raw response body (up to 10 MB) in the returned error message, bypassing --disable-query, --disable-api, and --loki-enforced-matchers.
Fix: Escape labelName with url.PathEscape(labelName) and reject values containing path separators or '..' segments before building the URL.
-
HighUnescaped template variable substitution in run_panel_query allows query/SQL injection in read-only mode
While raw query tools (query_sql and query_influxdb) are disabled under --disable-write because raw queries can modify or delete data, run_panel_query is marked read-only and remains enabled. However, when a dashboard panel query contains template variables without an explicit ':sqlstring' formatter (such as $var or ${var}), run_panel_query substitutes caller-supplied variable overrides verbatim into the SQL, InfluxQL, Flux, or Cypher query string. Even when ':sqlstring' is used, formatSQLStringVariable does not escape backslashes, which allows escaping single quotes in MySQL and ClickHouse.
Fix: Sanitize or parameterize variable overrides for SQL and InfluxDB queries, escape backslashes in formatSQLStringVariable, or gate SQL/InfluxDB execution in run_panel_query behind --disable-write.
-
MediumUnescaped projectName parameter in Cloud Monitoring metric descriptor URL
tools/prom_backend_cloudmonitoring.go:302
In fetchMetricDescriptors, the projectName tool argument (passed from list_prometheus_metric_names, list_prometheus_metric_metadata, list_prometheus_label_names, and list_prometheus_label_values) is formatted directly into the URL path without url.PathEscape or character validation. A crafted projectName containing '../', '?', or '#' can alter the target path or query string sent to the Grafana datasource proxy.
Fix: Validate that the project name matches a valid GCP project ID pattern and escape it with url.PathEscape.
-
MediumArbitrary request headers in grafana_api_request can bypass organization scoping
The grafana_api_request tool (enabled by default, including its GET-only variant in read-only mode) allows the caller to set arbitrary HTTP headers on the outbound Grafana request. When GRAFANA_ORG_ID is unset (0) and --dynamic-multi-org is disabled, OrgIDRoundTripper does not overwrite X-Grafana-Org-Id, allowing a prompt-injected agent to pass 'X-Grafana-Org-Id' in the headers map and access other organizations accessible to the credential despite --dynamic-multi-org being off.
Fix: Reject or strip sensitive headers such as X-Grafana-Org-Id, Authorization, X-Access-Token, X-Grafana-Id, and Cookie from the user-supplied headers map in doAPIRequest.
-
MediumDocker image binds to 0.0.0.0:8000 without requiring caller authentication
The default container entrypoint starts the server in SSE mode bound to 0.0.0.0:8000. If the operator does not set MCP_GRAFANA_SERVER_TOKEN or --server-auth-token, the server logs a security error but still accepts unauthenticated connections. Although DNS rebinding middleware checks that the Host header is a loopback variant (such as localhost:8000), any non-browser client on the network can set 'Host: localhost:8000' and call tools using the container's Grafana credentials.
Fix: Require --server-auth-token / MCP_GRAFANA_SERVER_TOKEN when binding to a non-loopback address, or bind to 127.0.0.1:8000 by default.
-
MediumClaude plugin installer downloads and runs unpinned latest release at runtime
.claude-plugin/install-binary.mjs:71
When started as a Claude plugin, install-binary.mjs queries the GitHub API for releases/latest, downloads the latest archive and checksums file, and executes the binary. Because the version is not pinned and the cosign signature bundle is not verified, a compromised GitHub release or tag will automatically be downloaded and executed on the user's machine on the next run.
Fix: Pin the release version and expected SHA-256 hashes in install-binary.mjs, or verify the release's Sigstore/cosign signature before executing the binary.
-
MediumDocker image binds SSE server to 0.0.0.0:8000 without enforcing caller authentication
The Docker container entrypoint starts the MCP server in SSE mode bound to all network interfaces (0.0.0.0:8000). Because caller authentication (--server-auth-token or MCP_GRAFANA_SERVER_TOKEN) is optional and only logs an error when omitted, publishing port 8000 without setting a server token allows any attacker who can reach the port (and set an allowed Host header such as localhost:8000) to invoke MCP tools using the server's Grafana credentials.
Fix: Set MCP_GRAFANA_SERVER_TOKEN (or --server-auth-token) when running in SSE or streamable-http mode, bind to 127.0.0.1 when only local access is needed, or use stdio mode (-t stdio).
-
MediumWrite tools and raw Grafana API requests are enabled by default
By default, the server registers write-capable tools—including update_dashboard, alerting_rules_write, install_plugin, and grafana_api_request (which can send arbitrary POST, PUT, PATCH, and DELETE requests to Grafana). If the AI agent reads untrusted log lines, trace attributes, or issue text containing a prompt injection, it can be steered into modifying or deleting Grafana resources or installing plugins with the user's credentials.
Fix: Pass --disable-write and --disable-api when starting the server, and use a least-privilege read-only Grafana service account token unless write operations are required.
-
LowAnonymous usage telemetry is enabled by default
The server sends anonymous usage statistics (process ID, OS/architecture, server and Grafana versions, tool call and error counts, authentication method type, and active flag/environment variable names) to https://stats.grafana.org/mcp-grafana-usage-report every 4 hours and on shutdown unless explicitly disabled.
Fix: Set --usage-stats=disabled, GRAFANA_USAGE_STATS=disabled, or DO_NOT_TRACK=1 if you do not want usage reports sent externally.
-
InfoAnonymous usage telemetry is sent to Grafana Labs by default
The server periodically sends anonymous usage statistics (such as tool call counts, enabled tool categories, OS/architecture, and Grafana version) to https://stats.grafana.org/mcp-grafana-usage-report every 4 hours and on shutdown unless explicitly disabled.
Fix: Set --usage-stats=disabled, GRAFANA_USAGE_STATS=disabled, or DO_NOT_TRACK=1 if you do not want telemetry data leaving the machine.