{"id":"EUVD-2026-120442","revision":null,"records":[{"revision":"bf3b95ea3d584b7293a3272c23df988c","observedAt":"2026-11-10T21:21:24.605+00:00","statement":{"source":"cve-program","id":"CVE-2026-123809","aliases":[],"title":"Dynatrace MCP Server: Workflow template injection via create_workflow_for_notification","description":"Dynatrace MCP Server is an MCP server for Dynatrace Observability. Prior to 2.0.0, the create_workflow_for_notification tool inserts the caller-controlled teamName, problemType, and channel values into a Dynatrace workflow without neutralizing Jinja2 expressions. A caller can place template expressions such as event() in those values, and the workflow engine evaluates them when the persistent workflow runs. When the workflow has a valid Slack connector, the resulting Slack action can disclose event or tenant metadata to an attacker-selected channel; the shipped placeholder connection does not deliver messages by itself. The workflow remains in the tenant after the MCP session and credentials are gone. The isPrivate option defaults to false, making the created workflow tenant-visible unless the caller changes it. Exploitation requires either an MCP prompt-injection path or an operator approving a prompt that displays the template fragment literally. This issue is fixed in version 2.0.0.","state":0,"publishedAt":"2026-11-09T20:53:14.455+00:00","modifiedAt":"2026-11-10T19:07:49.797+00:00","affected":[{"ecosystem":"Vendor product","name":"dynatrace-mcp","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"dynatrace-oss\",\n                    \"product\": \"dynatrace-mcp\",\n                    \"versions\": [\n                        {\n                            \"version\": \"< 2.0.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N","score":4.2,"severity":"MEDIUM"}],"references":["https://github.com/dynatrace-oss/dynatrace-mcp/security/advisories/GHSA-xrmj-5g4g-8987","https://github.com/dynatrace-oss/dynatrace-mcp/commit/8f975d6f6713b6da803aa1b602c9f68c8a6ec02a","https://github.com/dynatrace-oss/dynatrace-mcp/releases/tag/v2.0.0"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-123809","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-123809","identifiers":["CVE-2026-123809"]},"retained":false,"historicalAffected":[]},{"revision":"1c1ffd8911104c3fbc589110579f752a","observedAt":"2026-11-13T23:06:05.53+00:00","statement":{"source":"euvd","id":"EUVD-2026-120442","aliases":["CVE-2026-123809"],"title":"Dynatrace MCP Server is an MCP server for Dynatrace Observability. Prior to 2.0.0, the create_workflow_for_notification tool inserts the caller-controlled teamName, problemType, and channel values into a Dynatrace workflow without neutraliz…","description":"Dynatrace MCP Server is an MCP server for Dynatrace Observability. Prior to 2.0.0, the create_workflow_for_notification tool inserts the caller-controlled teamName, problemType, and channel values into a Dynatrace workflow without neutralizing Jinja2 expressions. A caller can place template expressions such as event() in those values, and the workflow engine evaluates them when the persistent workflow runs. When the workflow has a valid Slack connector, the resulting Slack action can disclose event or tenant metadata to an attacker-selected channel; the shipped placeholder connection does not deliver messages by itself. The workflow remains in the tenant after the MCP session and credentials are gone. The isPrivate option defaults to false, making the created workflow tenant-visible unless the caller changes it. Exploitation requires either an MCP prompt-injection path or an operator approving a prompt that displays the template fragment literally. This issue is fixed in version 2.0.0.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N","score":4.2,"severity":null}],"references":["https://github.com/dynatrace-oss/dynatrace-mcp/security/advisories/GHSA-xrmj-5g4g-8987","https://github.com/dynatrace-oss/dynatrace-mcp/commit/8f975d6f6713b6da803aa1b602c9f68c8a6ec02a","https://github.com/dynatrace-oss/dynatrace-mcp/releases/tag/v2.0.0"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-120442","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Nov 9, 2026, 8:53:14 PM","dateUpdated":"Nov 10, 2026, 7:07:49 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-120442","identifiers":["EUVD-2026-120442","CVE-2026-123809"]},"retained":false,"historicalAffected":[]},{"revision":"e99f21139cb74cacb0301b68ac74637d","observedAt":"2026-11-13T22:59:46.198+00:00","statement":{"source":"nvd","id":"CVE-2026-123809","aliases":[],"title":"Dynatrace MCP Server is an MCP server for Dynatrace Observability. Prior to 2.0.0, the create_workflow_for_notification tool inserts the caller-controlled teamName, problemType, and channel values into a Dynatrace workflow without neutraliz…","description":"Dynatrace MCP Server is an MCP server for Dynatrace Observability. Prior to 2.0.0, the create_workflow_for_notification tool inserts the caller-controlled teamName, problemType, and channel values into a Dynatrace workflow without neutralizing Jinja2 expressions. A caller can place template expressions such as event() in those values, and the workflow engine evaluates them when the persistent workflow runs. When the workflow has a valid Slack connector, the resulting Slack action can disclose event or tenant metadata to an attacker-selected channel; the shipped placeholder connection does not deliver messages by itself. The workflow remains in the tenant after the MCP session and credentials are gone. The isPrivate option defaults to false, making the created workflow tenant-visible unless the caller changes it. Exploitation requires either an MCP prompt-injection path or an operator approving a prompt that displays the template fragment literally. This issue is fixed in version 2.0.0.","state":0,"publishedAt":"2026-11-09T22:18:36.983+00:00","modifiedAt":"2026-11-12T22:18:51.47+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N","score":4.2,"severity":"MEDIUM"}],"references":["https://github.com/dynatrace-oss/dynatrace-mcp/commit/8f975d6f6713b6da803aa1b602c9f68c8a6ec02a","https://github.com/dynatrace-oss/dynatrace-mcp/releases/tag/v2.0.0","https://github.com/dynatrace-oss/dynatrace-mcp/security/advisories/GHSA-xrmj-5g4g-8987"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-123809","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-1336"],"productCriteria":[],"sourceDates":{"published":"2026-11-09T22:18:36.983","lastModified":"2026-11-12T22:18:51.470"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-123809","identifiers":["CVE-2026-123809"]},"retained":false,"historicalAffected":[]},{"revision":"6c77e5c48eca4ca9b0e199054f38778d","observedAt":"2026-11-09T22:02:23.304+00:00","statement":{"source":"osv","id":"GHSA-xrmj-5g4g-8987","aliases":["CVE-2026-123809"],"title":"@dynatrace-oss/dynatrace-mcp-server has a workflow template injection via create_workflow_for_notification","description":"### Summary\nA template injection vulnerability in the `create_workflow_for_notification` tool lets a caller embed Jinja2 expressions that the Dynatrace workflow engine evaluates at runtime, exfiltrating event data to attacker-controlled destinations through a workflow that persists in the tenant after the MCP session ends.\n\n### Details\nThe `create_workflow_for_notification` tool interpolates three caller-supplied parameters (`teamName`, `problemType`, `channel`) directly into a Dynatrace Workflow definition. Dynatrace Workflows use Jinja2 templating: per the [official documentation](https://docs.dynatrace.com/docs/analyze-explore-automate/workflows/reference), `{{ ... }}` expressions in action inputs are evaluated at workflow runtime for every action except `Run Javascript` (which is carved out specifically to avoid code injection). A caller can therefore supply, for example, `teamName = \"{{ event() }}\"` and have the workflow engine evaluate that expression at runtime, serialising the full event object into the message body delivered to the Slack channel.\n\nThe vulnerable code is in `src/capabilities/create-workflow-for-problem-notification.ts`, lines 82-99:\n\n```typescript\nlet notificationWorkflow: WorkflowCreate = {\n  title: `[MCP POC] Notify team ${teamName} on problem of type ${problemType}`,\n  description: `Automatically created workflow to notify team ${teamName} on problems of type ${problemType} - ...`,\n  isPrivate: isPrivate,\n  type: 'SIMPLE',\n  tasks: {\n    send_notification: {\n      name: 'Send notification',\n      action: 'dynatrace.slack:slack-send-message',\n      description: 'Sends a notification to a Slack channel',\n      input: {\n        connectionId: 'slack-connection-id',\n        channel: `{{ \\\"${channel}\\\" }}`,        // <-- channel sits inside {{ }}\n        message: `\uD83D\uDEA8 Alert for Team ${teamName}\\n*Problem Type*: ${problemType}\\n` +\n                 `*Problem ID*: {{ event()[\"display_id\"] }}\\n*Status*: {{ event()[\"event.status\"] }}\\n` +\n                 `<{{ environment().url }}/ui/apps/.../problem/{{ event()[\"event.id\"] }}|Click here>`,\n      },\n      active: true,\n    },\n  },\n};\n```\n\nThe action used is `dynatrace.slack:slack-send-message`, which is not in the documented Jinja-expression exception list. Its inputs are evaluated at workflow runtime.\n\nThe schema in `src/index.ts:1052-1070` registers `teamName`, `problemType`, and `channel` as `z.string().optional()` with no pattern validation. The `isPrivate` parameter has `.default(false)`, so created workflows are visible tenant-wide unless the caller explicitly sets it.\n\nThe approval prompt at `src/index.ts:1069-1072`:\n\n```typescript\nconst approved = await requestHumanApproval(\n  `Create a workflow for notifying team ${teamName} via ${channel} about ${problemType} problems`,\n);\n```\n\nRenders `{{ event() }}` literally to the operator with no indication that it will be templated, and does not surface the workflow's visibility.\n\nThe workflow is persistent: it remains in the tenant after the MCP session ends, after the operator's MCP credentials are revoked, and after the MCP server is uninstalled. It fires on every matching problem until manually deleted from the Workflows app. The `channel` parameter is uniquely dangerous because it is interpolated inside an existing `{{ \"...\" }}` expression context - close the string with `\"` and you can run arbitrary Jinja expressions in the destination field itself.\n\n\n### PoC\n\nTested end-to-end against a real Dynatrace tenant. The MCP server was run in stdio mode with the operator's Platform Token. A `tools/call create_workflow_for_notification` was sent with:\n\n```json\n{\n  \"teamName\":    \"{{ event() }}\",\n  \"problemType\": \"ERROR\",\n  \"channel\":     \"#mcp-sec-poc\",\n  \"isPrivate\":   true\n}\n```\n\nThe operator approved the prompt (which read: `\"Create a workflow for notifying team {{ event() }} via #mcp-sec-poc about ERROR problems\"`). The MCP returned a workflow ID. Fetching the stored workflow body via the Dynatrace Automation API (`GET /platform/automation/v1/workflows/<id>`) showed the injected expression stored verbatim:\n\n```\ntitle:   \"[MCP POC] Notify team {{ event() }} on problem of type ERROR\"\nmessage: \"\uD83D\uDEA8 Alert for Team {{ event() }}\\n*Problem Type*: ERROR\\n*Problem ID*: {{ event()[\\\"display_id\\\"] }}\\n...\"\nchannel: '{{ \"#mcp-sec-poc\" }}'\naction:  \"dynatrace.slack:slack-send-message\"\n```\n\nThe workflow was then triggered manually via the \"Run workflow\" feature in the Dynatrace Workflows app, with a synthetic event payload `{\"display_id\":\"P-123\",\"event.status\":\"OPEN\",\"event.id\":\"abc-123\"}`. The execution log for the `send_notification` task shows the workflow engine evaluated the injected expressions at runtime. The \"Input\" tab for the executed action contains:\n\n```\nchannel: #mcp-sec-poc\nmessage: Alert for Team\n         {'display_id': 'P-123',\n          'event.status': 'OPEN',\n          'event.id': 'abc-123'}\n         *Problem Type*: ERROR\n         *Problem ID*: P-123\n         *Status*: OPEN\n         <https://<tenant>.apps.dynatrace.com/ui/apps/.../problem/abc-123|Click here for details>\n```\n\nThe injected `{{ event() }}` resolved to the actual event object before the Slack action was called. The Slack action then failed only because the workflow uses a hardcoded placeholder `connectionId: 'slack-connection-id'` that does not resolve to any real connection. If a real Slack connector had been configured, the message would have been delivered with the serialized event data in place of `{{ event() }}`.\n\nThis is end-to-end confirmation of the Jinja-evaluation chain.\n\n### Impact\nA workflow created with a malicious template payload acts as a persistent exfiltration channel:\n\n- It fires on every matching problem indefinitely.\n- The injected template is evaluated at runtime and resolves to whatever the Jinja function returns (`event()` gives the full event object, `environment()` gives tenant metadata, plus other available functions per the Dynatrace Workflows documentation).\n- The resolved output is delivered to a destination the attacker controls (the `channel` parameter is also templated, and is even more dangerous because it is interpolated inside an existing `{{ \"...\" }}` expression context).\n- The workflow persists in the tenant after the MCP session, operator credentials, and MCP server are gone.\n- With `isPrivate=false` (the default), the workflow is visible to all tenant users.\n\nExploitation requires either prompt injection (an LLM under the MCP server's control is reading attacker-controlled data and follows an injection that calls `create_workflow_for_notification` with the malicious arguments) or operator carelessness (the approval prompt shows the raw team name literal and the operator clicks Approve without recognising `{{ event() }}` as a template fragment). Both are realistic in practice.","state":0,"publishedAt":"2026-07-31T16:01:07+00:00","modifiedAt":"2026-11-09T21:00:04.0844361+00:00","affected":[{"ecosystem":"npm","name":"@dynatrace-oss/dynatrace-mcp-server","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0"},{"kind":"fixed","version":"2.0.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"MODERATE"}],"references":["https://github.com/dynatrace-oss/dynatrace-mcp/security/advisories/GHSA-xrmj-5g4g-8987","https://github.com/dynatrace-oss/dynatrace-mcp/pull/547","https://github.com/dynatrace-oss/dynatrace-mcp/commit/64dfcb1095823d0fb43013635446288a6ff60e29","https://github.com/dynatrace-oss/dynatrace-mcp","https://github.com/dynatrace-oss/dynatrace-mcp/releases/tag/v2.0.0"],"sourceUrl":"https://github.com/advisories/GHSA-xrmj-5g4g-8987","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-xrmj-5g4g-8987","identifiers":["GHSA-xrmj-5g4g-8987","CVE-2026-123809"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-14T10:57:17.8189526+00:00","complete":true,"records":1781,"lastAttemptAt":"2026-11-14T10:57:17.8189526+00:00","lastSuccessAt":"2026-11-14T10:57:18.028601+00:00","error":null,"lastVerifiedAt":"2026-11-14T10:57:18.0286015+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-f4305fb3b9417bd97547e9305731a80b9463cd5f49ef853b3cd62960cad53a57-170b1662553ee3a628a7edcd60e9c4640846434bdb7f43295fba3e4bb85afb6a","complete":true,"records":425715,"lastAttemptAt":"2026-11-14T10:57:17.3248872+00:00","lastSuccessAt":"2026-11-14T10:57:17.273575+00:00","error":null,"lastVerifiedAt":"2026-11-14T10:57:17.3248872+00:00","snapshotPublishedAt":"2026-11-14T10:27:50+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-21c7ffb0cd8d4e41935630d950039183","complete":true,"records":13821,"lastAttemptAt":"2026-11-14T10:58:29.960654+00:00","lastSuccessAt":"2026-11-14T09:58:00.482+00:00","error":null,"lastVerifiedAt":"2026-11-14T09:58:00.482+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13874,"expectedIdentifiers":13874,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-14T10:57:17.8189526+00:00","complete":true,"records":1123,"lastAttemptAt":"2026-11-14T10:57:17.8189526+00:00","lastSuccessAt":"2026-11-14T10:57:18.0293537+00:00","error":null,"lastVerifiedAt":"2026-11-14T10:57:18.0293539+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-1b63371cb47c47c5be86901813e9f81a","complete":false,"records":13821,"lastAttemptAt":"2026-11-14T10:58:29.1314442+00:00","lastSuccessAt":"2026-11-14T10:58:29.127+00:00","error":"170 identifier lookups failed; latest failures include HttpError (HTTP 503). Other identifiers were checked independently.","lastVerifiedAt":"2026-11-14T10:58:29.127+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13304,"expectedIdentifiers":13874,"failedIdentifiers":170},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-0b57fec07d57d893c006f1cfeb0af7ae73375ea99dba0cb68b1da97c5ec74185","complete":true,"records":9955,"lastAttemptAt":"2026-11-14T10:57:17.6710433+00:00","lastSuccessAt":"2026-11-13T23:59:01.475919+00:00","error":null,"lastVerifiedAt":"2026-11-14T10:57:17.6710433+00:00","snapshotPublishedAt":"2026-11-13T23:17:54+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-f4424d7d4861b1af1404bd15abcb83f295dcd003412a9340ca99dc8200aa148a","complete":true,"records":2014,"lastAttemptAt":"2026-11-14T10:57:17.391885+00:00","lastSuccessAt":"2026-11-13T19:57:08.2864435+00:00","error":null,"lastVerifiedAt":"2026-11-14T10:57:17.391885+00:00","snapshotPublishedAt":"2026-11-13T19:33:10+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-012aff9b2fb349d53e2c4bace6ca57956b6f5f9d82c1dc9fdc1c9b3c738f8977","complete":true,"records":3148,"lastAttemptAt":"2026-11-14T10:57:17.7769585+00:00","lastSuccessAt":"2026-11-14T04:57:03.8279646+00:00","error":null,"lastVerifiedAt":"2026-11-14T10:57:17.7769585+00:00","snapshotPublishedAt":"2026-11-14T03:02:50+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-fdee02376e78ad97a6afd9c64b7a511338faf1c7adbbe54e4431f98a9f790e0d","complete":true,"records":232471,"lastAttemptAt":"2026-11-14T10:57:17.5629251+00:00","lastSuccessAt":"2026-11-14T08:58:41.6192286+00:00","error":null,"lastVerifiedAt":"2026-11-14T10:57:17.5629251+00:00","snapshotPublishedAt":"2026-11-14T08:33:14+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}