Jul 31, 2026·5 min read·39 visits
The @dynatrace-oss/dynatrace-mcp-server before v2.0.0 allowed attackers to perform template injection into Dynatrace Workflow templates. This allows arbitrary Jinja2 template execution that persists on the tenant even after the session ends.
A template injection vulnerability in @dynatrace-oss/dynatrace-mcp-server allows untrusted input to be interpolated directly into Dynatrace Workflows using Jinja2 syntax, leading to persistent data exposure and exfiltration.
The Model Context Protocol (MCP) server for Dynatrace, distributed via the npm registry as @dynatrace-oss/dynatrace-mcp-server, acts as a bridge enabling Large Language Models (LLMs) to interact with Dynatrace APIs and perform automated operations.
One of the capabilities exposed by this MCP server is the create_workflow_for_notification tool. This tool allows users or automated agents to generate alert routing workflows inside a Dynatrace tenant, specifying details such as the target team, the class of problem, and the communication channel.
In versions of the server prior to 2.0.0, the tool accepts parameter inputs without proper validation or escaping, and interpolates them directly into a Dynatrace Workflow definition template. This workflow template utilizes the Jinja2 rendering engine at runtime, introducing a template injection surface (CWE-1336) that exposes the underlying Dynatrace tenant data to exfiltration.
The fundamental flaw resides within the handler implementation found at src/capabilities/create-workflow-for-problem-notification.ts. The handler accepts user-controlled properties, including teamName, problemType, and channel, via an incoming MCP JSON-RPC call.
These variables are processed as raw string variables and concatenated directly into the properties of a WorkflowCreate structure. Specifically, the channel parameter is wrapped in a Jinja2 execution syntax block: {{ "${channel}" }}. Because the implementation relies on simple ES6 template literals rather than structured, parameter-aware builders, characters like quotes and brackets remain intact.
Furthermore, the validation schema declared in src/index.ts relies on basic Zod string constraints (z.string().optional()) that enforce zero pattern verification or character blocklists. Consequently, any string containing template tags ({{ and }}) is parsed without validation, allowing the injection payload to reach the platform configuration unchanged.
An inspection of the vulnerable source code highlights how the payloads were constructed:
// Vulnerable: src/capabilities/create-workflow-for-problem-notification.ts
let notificationWorkflow: WorkflowCreate = {
title: `[MCP POC] Notify team ${teamName} on problem of type ${problemType}`,
description: `Automatically created workflow to notify team ${teamName}...`,
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}" }}`,
message: `🚨 Alert for Team ${teamName}\n*Problem Type*: ${problemType}\n` +
`*Problem ID*: {{ event()["display_id"] }}\n` +
`*Status*: {{ event()["event.status"] }}\n`
},
active: true,
},
},
};The logical execution flow of this exploit moves from the user/agent interface to the internal templating engine as shown below:
The channel context is particularly vulnerable. By injecting #channel" }} into the parameter, an attacker breaks out of the string boundary defined in the generated template structure, giving them direct access to the outer Jinja2 processing context.
To execute this attack, a malicious actor must trigger the create_workflow_for_notification tool. This can occur either through direct administrative access to the MCP client interface or via an indirect prompt injection attack, where an LLM is manipulated into executing the tool on behalf of an operator.
A verified proof-of-concept payload submits the following arguments during the tool execution request:
{
"teamName": "{{ event() }}",
"problemType": "ERROR",
"channel": "#mcp-sec-poc",
"isPrivate": true
}When this workflow is saved to the Dynatrace platform and subsequently triggered by an incident event, the underlying Jinja2 execution layer evaluates the payload. Instead of outputting the literal string {{ event() }}, the execution layer interprets the function call, which dumps the entire current event dictionary—including active operational data, status markers, and system metadata—into the outgoing communication block.
The core risk of this vulnerability is the persistent execution state of Dynatrace workflows. Unlike typical web-application vulnerabilities where the exploitation lifecycle is ephemeral, a workflow template injection creates a long-lived configuration object within the tenant.
Once the workflow is saved, it continues to run within the Dynatrace platform infrastructure. It remains active even if the operator closes their session, has their personal access credentials revoked, or completely uninstalls the MCP server. This establishes a highly reliable and silent persistence channel.
Furthermore, because the templates run within the security context of the Dynatrace automation execution environment, they can access platform utility functions like environment(). An attacker can use this access to map out internal tenant URLs, architecture settings, and directory metadata, then exfiltrate this information through configured notification actions.
Rather than attempting to build sanitization routines to sanitize complex nested Jinja2 brackets, the development team decided to completely remove the vulnerable code path to eliminate the attack surface.
In version 2.0.0, both the create_workflow_for_notification and make_workflow_public tools were removed. Users needing workflow creation capabilities are instructed to use official channels, such as the dtctl command-line utility, the native Dynatrace User Interface, or the raw Dynatrace Automation API directly.
To remediate this vulnerability, operators must upgrade @dynatrace-oss/dynatrace-mcp-server to version 2.0.0 or higher. Note that upgrading the server does not purge previously generated workflows; an incident response audit must be executed inside the Dynatrace console to discover and manually delete any workflows created under the prefix [MCP POC].
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
@dynatrace-oss/dynatrace-mcp-server Dynatrace | < 2.0.0 | 2.0.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1336 |
| Attack Vector | Network |
| CVSS v3.1 | 4.2 |
| Exploit Status | poc |
| KEV Status | Not Listed |
| Weakness Name | Improper Neutralization of Special Elements Used in a Template Engine |
The product parses a template without properly neutralizing special elements, allowing attackers to execute arbitrary template code.
An authorization bypass and credential oracle vulnerability in pyload-ng allows authenticated users with minimal or no privileges to brute-force the administrator password. This is achieved through sensitive API methods exposed globally combined with a non-constant-time password hash comparison algorithm.
An authentication bypass vulnerability in pyLoad allows unauthenticated remote attackers to gain administrative API access. The vulnerability is caused by a logical flaw in the API key cache validation lookup, where authentication states are cached using only the public key identifier, skipping cryptographic token verification on cache hits.
An authorization bypass vulnerability in Strawberry GraphQL between versions 0.217.0 and 0.326.1 occurs when a synchronous permission handler returns an awaitable object (such as an unawaited coroutine). Due to Python's truthiness rules, the unawaited coroutine is evaluated as True, leading to an immediate bypass of security policies.
An uncontrolled resource consumption vulnerability in Strawberry GraphQL allows unauthenticated remote attackers to trigger a Denial of Service on persistent WebSocket connections using the legacy graphql-ws protocol. When the server enforces max_subscriptions_per_connection, naturally terminating subscriptions are not cleared from memory registries, leading to exhaustion of connection slots.
A high-severity type-confusion vulnerability exists in NearForm fast-jwt prior to version 6.3.0. The vulnerability allows attackers to bypass crucial claim validation steps (such as expiration, issuer, audience, and subject validations) by presenting a validly signed JSON Web Token structured as a JSON array instead of a JSON object. This occurs because the library's decoder fails to reject JSON arrays during type evaluation.
NearForm fast-jwt prior to version 6.3.0 is vulnerable to an input validation flaw where configuring verifier properties (such as clockTolerance, clockTimestamp, and cacheTTL) with non-finite values like Infinity or NaN allows attackers to bypass temporal claim validations, including expiration (exp) and activation (nbf) boundaries. This validation bypass can result in unauthorized session persistence and cache poisoning.