GHSA-xrmj-5g4g-8987: Dynatrace MCP: Jinja injection creates persistent exfil

GHSA-xrmj-5g4g-8987 MEDIUM
Published July 31, 2026
CISO Take

The Dynatrace MCP server lets a caller inject Jinja2 template expressions into three unsanitized parameters of the create_workflow_for_notification tool, and because the resulting Dynatrace Workflow evaluates those expressions at runtime, an attacker can turn a routine notification workflow into a persistent exfiltration channel that ships event data and tenant metadata to a destination they control. The severity is only medium (CVSS 4.2, no EPSS/KEV data, no public exploit or scanner template), but the blast radius is disproportionate to the score: the malicious workflow survives after the MCP session ends, after the operator's credentials are revoked, and after the MCP server itself is uninstalled, and by default (isPrivate=false) it is visible to every user in the tenant. The human-approval gate that should catch this is itself defeated, since it renders the raw {{ event() }} fragment to the operator with no indication that it is executable template syntax. This is a textbook illustration of why AI agent tool-calling needs output/parameter sanitization, not just human-in-the-loop approval, since a non-expert reviewer has no reason to recognize a Jinja expression as dangerous. Action: any org running the Dynatrace MCP server should upgrade to @dynatrace-oss/dynatrace-mcp-server 2.0.0 immediately, and separately audit the tenant's Workflows app for any workflow whose title/description/message contains unexpected {{ }} expressions or was created with isPrivate=false by an MCP-driven actor.

Sources: GitHub Advisory ATLAS dynatrace.com

What is the risk?

Exploitability is non-trivial but realistic: it requires either an indirect prompt injection that steers an LLM-driven MCP client into calling create_workflow_for_notification with crafted arguments, or an operator approving a request without recognizing {{ event() }} as executable template syntax rather than literal text. There is no public exploit code, no Nuclei template, and it is not in CISA KEV, which keeps the near-term mass-exploitation probability low. However, impact and persistence push effective risk above what the 4.2 CVSS score suggests: the vulnerability creates a durable, tenant-wide backdoor that outlives the MCP session, the operator's Platform Token, and even the MCP server installation itself, and it can serialize arbitrary event/environment data to an attacker-controlled Slack channel or other destination. Because Dynatrace Workflows sit inside observability/AIOps tooling that typically has broad read access to infrastructure and application telemetry, a single successful injection can leak sensitive operational data continuously until someone manually finds and deletes the workflow.

How does the attack unfold?

Initial Access via Indirect Prompt Injection
Attacker-controlled content ingested by the AI agent (e.g., a poisoned ticket or log entry) contains instructions that steer the agent into calling create_workflow_for_notification with malicious parameters.
AML.T0051.001
Tool Invocation with Template Payload
The agent (or attacker directly) calls the MCP tool with teamName/channel values containing Jinja2 expressions like {{ event() }}, which are interpolated unsanitized into the workflow definition.
AML.T0053
Persistence via Tenant Workflow
The operator approves a deceptive prompt that displays the raw template fragment as plain text; the malicious workflow is created and persists in the Dynatrace tenant independent of the MCP session or credentials.
AML.T0110
Runtime Exfiltration
On every matching problem, the Dynatrace Workflow engine evaluates the injected Jinja expressions and delivers serialized event/environment data to the attacker-controlled channel indefinitely.
AML.T0086

What systems are affected?

Package Ecosystem Vulnerable Range Patched
@dynatrace-oss/dynatrace-mcp-server npm < 2.0.0 2.0.0

Do you use @dynatrace-oss/dynatrace-mcp-server? You're affected.

How severe is it?

CVSS 3.1
4.2 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Moderate

What is the attack surface?

AV AC PR UI S C I A
AV Network
AC High
PR None
UI Required
S Unchanged
C Low
I Low
A None

What should I do?

1 step
  1. Upgrade @dynatrace-oss/dynatrace-mcp-server to 2.0.0 or later, which patches the injection per the linked commit/PR. As an immediate compensating control, audit existing Dynatrace tenants for workflows created via MCP: query the Automation API (GET /platform/automation/v1/workflows) for entries with '[MCP' in the title or with {{ }} sequences embedded in title/description/message/channel fields, and manually delete any that were not intentionally created. Review workflow visibility settings and flag any MCP-created workflow with isPrivate=false for re-evaluation. Going forward, treat any AI agent tool that accepts free-text parameters destined for a templating engine (Jinja2, Handlebars, EJS, etc.) as requiring strict allow-list or escaping validation before interpolation, and do not rely solely on a human-approval prompt as a security boundary — approval text must be rendered in a way that surfaces raw template syntax rather than displaying it as if it were plain text. Monitor for unexpected outbound Slack/webhook traffic originating from Workflows as a detection signal.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 14 - Human Oversight
NIST AI RMF
MANAGE-4.1 - Post-deployment monitoring for AI system risks
OWASP LLM Top 10
LLM01 - Prompt Injection LLM06 - Excessive Agency

Frequently Asked Questions

What is GHSA-xrmj-5g4g-8987?

The Dynatrace MCP server lets a caller inject Jinja2 template expressions into three unsanitized parameters of the create_workflow_for_notification tool, and because the resulting Dynatrace Workflow evaluates those expressions at runtime, an attacker can turn a routine notification workflow into a persistent exfiltration channel that ships event data and tenant metadata to a destination they control. The severity is only medium (CVSS 4.2, no EPSS/KEV data, no public exploit or scanner template), but the blast radius is disproportionate to the score: the malicious workflow survives after the MCP session ends, after the operator's credentials are revoked, and after the MCP server itself is uninstalled, and by default (isPrivate=false) it is visible to every user in the tenant. The human-approval gate that should catch this is itself defeated, since it renders the raw {{ event() }} fragment to the operator with no indication that it is executable template syntax. This is a textbook illustration of why AI agent tool-calling needs output/parameter sanitization, not just human-in-the-loop approval, since a non-expert reviewer has no reason to recognize a Jinja expression as dangerous. Action: any org running the Dynatrace MCP server should upgrade to @dynatrace-oss/dynatrace-mcp-server 2.0.0 immediately, and separately audit the tenant's Workflows app for any workflow whose title/description/message contains unexpected {{ }} expressions or was created with isPrivate=false by an MCP-driven actor.

Is GHSA-xrmj-5g4g-8987 actively exploited?

No confirmed active exploitation of GHSA-xrmj-5g4g-8987 has been reported, but organizations should still patch proactively.

How to fix GHSA-xrmj-5g4g-8987?

Upgrade @dynatrace-oss/dynatrace-mcp-server to 2.0.0 or later, which patches the injection per the linked commit/PR. As an immediate compensating control, audit existing Dynatrace tenants for workflows created via MCP: query the Automation API (GET /platform/automation/v1/workflows) for entries with '[MCP' in the title or with {{ }} sequences embedded in title/description/message/channel fields, and manually delete any that were not intentionally created. Review workflow visibility settings and flag any MCP-created workflow with isPrivate=false for re-evaluation. Going forward, treat any AI agent tool that accepts free-text parameters destined for a templating engine (Jinja2, Handlebars, EJS, etc.) as requiring strict allow-list or escaping validation before interpolation, and do not rely solely on a human-approval prompt as a security boundary — approval text must be rendered in a way that surfaces raw template syntax rather than displaying it as if it were plain text. Monitor for unexpected outbound Slack/webhook traffic originating from Workflows as a detection signal.

What systems are affected by GHSA-xrmj-5g4g-8987?

This vulnerability affects the following AI/ML architecture patterns: agent frameworks, MCP servers / tool integrations, workflow / SOAR automation platforms.

What is the CVSS score for GHSA-xrmj-5g4g-8987?

GHSA-xrmj-5g4g-8987 has a CVSS v3.1 base score of 4.2 (MEDIUM).

What is the AI security impact?

Affected AI Architectures

agent frameworksMCP servers / tool integrationsworkflow / SOAR automation platforms

MITRE ATLAS Techniques

AML.T0051.001 Indirect
AML.T0053 AI Agent Tool Invocation
AML.T0086 Exfiltration via AI Agent Tool Invocation

Compliance Controls Affected

EU AI Act: Article 14
NIST AI RMF: MANAGE-4.1
OWASP LLM Top 10: LLM01, LLM06

What are the technical details?

Original Advisory

### Summary 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](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. The vulnerable code is in `src/capabilities/create-workflow-for-problem-notification.ts`, lines 82-99: ```typescript 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`: ```typescript 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: ```json { "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 `channel` parameter 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.

Exploitation Scenario

An organization deploys an AIOps assistant that uses the Dynatrace MCP server to let an LLM agent triage problems and set up team notifications on the operator's behalf. An attacker plants an indirect prompt injection in content the agent ingests during triage (e.g., a poisoned incident ticket, log line, or webhook payload) instructing the agent to call create_workflow_for_notification with teamName set to "{{ event() }}" and channel set to a string that closes the existing template context and adds an attacker-controlled Jinja expression. The operator sees an approval prompt reading something like 'Create a workflow for notifying team {{ event() }} via #some-channel about ERROR problems' and approves it, not recognizing the double curly braces as executable syntax. The workflow is created and persists in the tenant; every subsequent matching problem causes the Dynatrace Workflow engine to evaluate the injected expression at runtime, serializing live event data (and, with other Jinja functions, tenant environment metadata) into the message body delivered to a channel or connection the attacker controls — continuing indefinitely even after the MCP session, the operator's token, and the MCP server itself are gone.

Weaknesses (CWE)

CWE-1336 — Improper Neutralization of Special Elements Used in a Template Engine: The product uses a template engine to insert or process externally-influenced input, but it does not neutralize or incorrectly neutralizes special elements or syntax that can be interpreted as template expressions or other code directives when processed by the engine.

  • [Architecture and Design] Choose a template engine that offers a sandbox or restricted mode, or at least limits the power of any available expressions, function calls, or commands.
  • [Implementation] Use the template engine's sandbox or restricted mode, if available.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N

Timeline

Published
July 31, 2026
Last Modified
July 31, 2026
First Seen
July 31, 2026

Related Vulnerabilities