# mcp-contextforge-gateway has Server-Side Template Injection (SSTI) leading to Remote Code Execution in `PromptService._render_template` via unsandboxed Jinja2 Environment

PromptService rendered stored prompt templates in an unsandboxed Jinja2 Environment, letting any authenticated prompt-writer execute arbitrary commands on the gateway host.

- Payout: Research
- Severity: high
- Program: mcp-contextforge-gateway
- Found by: YuvalElbar6
- Disclosed: 2026-08-25
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-vwf3-4xxj-qg6h
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-vwf3-4xxj-qg6h
- Tags: area:web domain:api domain:web impact:internal-access impact:rce kind:research platform:ghsa sev:high tech:python technique:source-review vuln:rce vuln:ssti

**Target:** PromptService._render_template in mcpgateway/services/prompt_service.py, reachable via POST /prompts and PUT /prompts/{id}
**Root cause:** PromptService builds a plain jinja2.Environment instead of SandboxedEnvironment, so stored user-supplied templates can traverse attributes and call builtins at render time. The str.format() fallback in _render_template also re-opens attribute access when the sandbox rejects an expression.
**Steps:**
1. pip install mcp-contextforge-gateway==0.9.0
2. Instantiate PromptService and confirm `type(service._jinja_env).__name__` is `Environment`
3. Render payload `{{ self.__init__.__globals__.__builtins__.__import__('os').popen('echo MCPGATEWAY_RCE > /abs/path/MCPGATEWAY_RCE_PROOF.txt').read() }}` via `service._render_template(payload, {})`
4. Observe the proof file created on disk
5. End-to-end: submit the same payload as the `template` field to `POST /prompts` (or `PUT /prompts/{id}`), then trigger rendering through the MCP `prompts/get` flow
**How found:** Code review of the prompt rendering path: noticed _render_template used jinja2.Environment rather than SandboxedEnvironment, then confirmed the template field is persisted from authenticated API write endpoints and rendered later in get_prompt.
**Exploitation:** An authenticated principal with prompts.create/prompts.update stores a malicious template; every subsequent render of that prompt executes the embedded os.popen chain in the gateway process, yielding command execution, filesystem and env-var/secret read, and outbound network access.
**Escalation:** Multi-tenant deployments: one tenant registering a prompt compromises the whole gateway and indirectly all other tenants; persistence via additional malicious prompts or disk writes.
**Insight:** Grep for `jinja2.Environment(` in services that render DB- or API-sourced strings; also check exception fallbacks — a SecurityError falling through to str.format() re-enables attribute traversal even after sandboxing.

