- Exploited
- No known exploitation
- Fix
- Fixed in 2.0.0
- Who is exposed
- Not reported
Do first: Upgrade to a fixed version: 2.0.0.
- Last modified
- A 0-100 score for how much a CVE matters across the industry: exploitation, exposure, how widely the software is used, severity and whether a fix exists.What do these mean? →
- 14/100 · provisional
- The share of the registry's downloads of each affected package in the window that were versions inside the affected ranges. Downloads, not users or installs: CI runs and mirrors inflate the counts.
- 41% of @dynatrace-oss/dynatrace-mcp-server downloads last week were affected versions (11.9K).npm, 28.9K downloads in the last 7 days, read Nov 9, 2026
- The rating the maker of the software gives it. Shown when it differs from the CVSS level.What do these mean? →
- Medium (ghsa)
- Whether working attack code is public (Exploit-DB, Metasploit, Nuclei or GitHub). Public code makes attacks easier.What do these mean? →
- None on Exploit-DB, Metasploit or GitHub
Summary
The @dynatrace-oss/dynatrace-mcp-server package has a template injection flaw in its create_workflow_for_notification tool. An attacker can embed Jinja2 expressions in the teamName, problemType, or channel values, which the Dynatrace workflow engine evaluates at runtime to send event data to destinations the attacker chooses, and the workflow stays in the tenant after the session ends.
Written by anthropic/claude-haiku-5.5 from the sources below. Verify against them.
Read the original description
A 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.
Details
The 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, {{ ... }} 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.
The vulnerable code is in src/capabilities/create-workflow-for-problem-notification.ts, lines 82-99:
let notificationWorkflow: WorkflowCreate = {
title: `[MCP POC] Notify team ${teamName} on problem of type ${problemType}`,
description: `Automatically created workflow to notify team ${teamName} on problems of type ${problemType} - ...`,
isPrivate: isPrivate,
type: 'SIMPLE',
tasks: {
send_notification: {
name: 'Send notification',
action: 'dynatrace.slack:slack-send-message',
description: 'Sends a notification to a Slack channel',
input: {
connectionId: 'slack-connection-id',
channel: `{{ \"${channel}\" }}`, // <-- channel sits inside {{ }}
message: `🚨 Alert for Team ${teamName}\n*Problem Type*: ${problemType}\n` +
`*Problem ID*: {{ event()["display_id"] }}\n*Status*: {{ event()["event.status"] }}\n` +
`<{{ environment().url }}/ui/apps/.../problem/{{ event()["event.id"] }}|Click here>`,
},
active: true,
},
},
};The 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.
The 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.
The approval prompt at src/index.ts:1069-1072:
const approved = await requestHumanApproval(
`Create a workflow for notifying team ${teamName} via ${channel} about ${problemType} problems`,
);Renders {{ event() }} literally to the operator with no indication that it will be templated, and does not surface the workflow's visibility.
The 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.
PoC
Tested 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:
{
"teamName": "{{ event() }}",
"problemType": "ERROR",
"channel": "#mcp-sec-poc",
"isPrivate": true
}The 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:
title: "[MCP POC] Notify team {{ event() }} on problem of type ERROR"
message: "🚨 Alert for Team {{ event() }}\n*Problem Type*: ERROR\n*Problem ID*: {{ event()[\"display_id\"] }}\n..."
channel: '{{ "#mcp-sec-poc" }}'
action: "dynatrace.slack:slack-send-message"The 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:
channel: #mcp-sec-poc
message: Alert for Team
{'display_id': 'P-123',
'event.status': 'OPEN',
'event.id': 'abc-123'}
*Problem Type*: ERROR
*Problem ID*: P-123
*Status*: OPEN
<https://<tenant>.apps.dynatrace.com/ui/apps/.../problem/abc-123|Click here for details>The 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() }}.
This is end-to-end confirmation of the Jinja-evaluation chain.
Impact
A workflow created with a malicious template payload acts as a persistent exfiltration channel:
- It fires on every matching problem indefinitely.
- 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). - The resolved output is delivered to a destination the attacker controls (the
channelparameter is also templated, and is even more dangerous because it is interpolated inside an existing{{ "..." }}expression context). - The workflow persists in the tenant after the MCP session, operator credentials, and MCP server are gone.
- With
isPrivate=false(the default), the workflow is visible to all tenant users.
Exploitation 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.
cve.now Index
- Exploitation?/40Unknown. No CISA SSVC exploitation rating yet
- Exposure?/25Unknown. No advisory context yet
- Reach10/20Affects npm packages
- Severity4/10Rated medium by ghsa
- No patch0/5No signal
What would change the score
- Would score about 69 if added to CISA KEV
- Would score about 64 if CISA's SSVC rates exploitation active
- Would score about 36 if advisories confirm default setups are exposed
Impact
What to do
Upgrade to a fixed version: 2.0.0.
Timeline
- Advisory published
- Record last updated
Affected versions
fixed in 2.0.0
Fixed in 2.0.0 (released Jun 16, 2026, 45 days before disclosure).
The latest release, 2.1.2, is not affected.
Evidence 5
- Advisory
- Repository
- Repository
- Repository
- Repository
Corrections
None so far.
Something wrong on this page? Report an error. Every fix is listed here.
Questions about GHSA-XRMJ-5G4G-8987
Is GHSA-XRMJ-5G4G-8987 being exploited?
It is not in CISA's Known Exploited Vulnerabilities catalog, and CISA's SSVC assessment does not mark exploitation as active. That does not mean it has not been attacked: vendors and researchers can report attacks before CISA lists them. No public exploit code is tracked.Source: cve.now feed data
Which versions are affected by GHSA-XRMJ-5G4G-8987?
@dynatrace-oss/dynatrace-mcp-server (npm): before 2.0.0.Source: cve.now feed data
How do I fix GHSA-XRMJ-5G4G-8987?
Upgrade to a fixed version: 2.0.0.Source: cve.now feed data