mercure
Server-Side Template Injection in the Notification System
mercure can send a notification (webhook or email) when a DICOM series matches a routing rule. The notification body is an administrator-configured template rendered with an unsandboxed Jinja2 engine, with no validation. Template syntax placed in that field escapes to arbitrary code execution, and because notifications fire on every matching series, the code runs persistently as imaging data flows through.
SandboxedEnvironment instead of the bare Template().CVEPendingGHSAPendingDescription
mercure is an open-source DICOM orchestration platform; we appreciate the maintainers' prompt, constructive response to this report. When a DICOM series matches a routing rule, mercure can fire a notification whose body is an administrator-configured template.
That body is rendered with Jinja2's bare Template(), with no SandboxedEnvironment, no autoescape, and no validation of the template content:
app/common/notification.py:60-85
from jinja2 import Template
def parse_payload(payload, event, rule_name, task_id, details="", context={}, *, task=None, tags_list=None): payload_parsed = payload payload_parsed = payload_parsed.replace("@rule@", rule_name) payload_parsed = payload_parsed.replace("@task_id@", task_id) payload_parsed = payload_parsed.replace("@event@", event.name) # ... context setup ... return Template(payload_parsed).render(context) # unsandboxed Jinja2Jinja2's default environment permits attribute access, so the same object-traversal that defeats the routing-rule eval() sandbox applies here: from a string literal one reaches the base object class, enumerates its subclasses, and recovers __import__ for arbitrary command execution. The vendor's 0.4.1 release notes confirm the templates were "rendered without sandboxing, allowing arbitrary code execution on every matching DICOM series." We are not publishing a working payload.
The notification template fields receive no sanitization. strip_untrusted() is applied to the tags field but not to any notification body:
app/webinterface/rules.py:217-218
notification_payload_body=form.get("notification_payload_body", ""), # no sanitizationnotification_email_body=form.get("notification_email_body", ""), # no sanitizationStoring a notification body on a rule, pointing the webhook at any URL, and leaving the reception trigger enabled (the default for new rules) is enough: the router renders the template on every matching series, before the webhook is sent, so the injected code runs in the router process. That process reaches the shared filesystem, Redis, the DICOM data in transit, and other containers, which is the scope change. The payload persists in mercure.json and is valid Jinja2, so it resembles a normal template.
Command execution as the mercure user in the router is confirmed by the output of an injected id:
Proof-of-concept output
uid=1000(mercure) gid=1000(mercure) groups=1000(mercure)Impact
- An attacker with an administrator session can store a notification template that executes arbitrary code in the router process. It runs on every DICOM series matching the rule, so execution is persistent and fires automatically as imaging data flows through.
- The router process reaches the shared filesystem, Redis, the DICOM data in transit, and other containers on the Docker network. Command output can also be returned through the webhook body itself.
- The payload persists in
mercure.jsonand is syntactically valid Jinja2, so it is hard to distinguish from a legitimate template.
Mitigation
Upgrade to mercure 0.4.1, which renders notification and other user-configured templates with jinja2's SandboxedEnvironment instead of the bare Template(); the sandbox blocks the attribute access the escape relies on. As defense in depth, validate template content on save and reject attribute-access patterns such as __class__, __subclasses__, and __import__.
Defender's Checklist
Upgrade to 0.4.1 or later
Deploy the patched release, which renders templates with
SandboxedEnvironment.Review notification templates
Audit notification body fields in
mercure.jsonfor attribute-access patterns (__class__,__subclasses__,__import__); these should not appear in a normal template.Restrict administrator access
Limit who holds administrator credentials and confirm the default admin password has been changed.
Severity Reasoning
References
How We Can Help
Who We Are
The security researchers behind this advisory.

Dr. rer. nat. Simon Weber
Senior Pentester & MedSec Researcher
I evaluate your SaMD with the same industry-defining security insight I contributed to the BAK MV for the revision of the B3S standard.
- PhD on Hospital Cybersecurity
- Critical vulnerabilities found in hospital systems
- Alumni of THB MedSec Research Group
- gematik Security Hero

Dipl.-Inf. Volker Schönefeld
Senior Application Security Expert
As a former CTO and developer turned pentester, I work alongside your team to uncover vulnerabilities and find solutions that fit your architecture.
- 20+ years as CTO, 50M+ app downloads
- Architected and secured large-scale IoT fleets
- Certified Web Exploitation Specialist
- gematik Security Hero
Looking for a Penetration Test?
Machine Spirits specializes in security assessments for medical devices and healthcare IT. From MDR penetration testing to C5 cloud compliance, we help MedTech companies meet regulatory requirements.
