RAGFlow Prompt Generator Server-Side Template Injection Leads to Remote Code Execution
Unsandboxed Jinja2 Environment in citation_prompt() lets any authenticated user pop a shell via a DuckDuckGo→LLM Canvas workflow
RAGFlow, an open-source Retrieval-Augmented Generation engine from infiniflow, contains a critical server-side template injection in versions 0.24.0 and earlier. The prompt generator (rag/prompts/generator.py) renders user-supplied citation guidelines through an unsandboxed Jinja2 environment. Any authenticated user — including a self-registered, low-privilege account — can build a Canvas workflow chaining a DuckDuckGo tool into an LLM component, embed a Jinja2 payload in the prompt, and execute arbitrary OS commands on the RAGFlow server with the service's own privileges. Fixed in v0.25.0 by switching to jinja2.sandbox.SandboxedEnvironment.
Technical Breakdown
RAGFlow Prompt Generator Server-Side Template Injection Leads to Remote Code Execution
rag/prompts/generator.py instantiates a module-level PROMPT_JINJA_ENV = jinja2.Environment(autoescape=False, trim_blocks=True, lstrip_blocks=True) — the plain, unsandboxed Jinja2 environment that exposes full Python object introspection via __globals__/__builtins__. Its citation_prompt() function renders attacker-controlled text pulled from <CITATION_GUIDELINES> tags inside an LLM component's sys_prompt — set via the Canvas DSL — directly through that environment as a template rather than as inert string data, so Jinja2 control syntax executes server-side instead of being displayed.
disable open self-registration / restrict Canvas creation to trusted users
Root Cause
rag/prompts/generator.py builds a module-level PROMPT_JINJA_ENV = jinja2.Environment(autoescape=False, trim_blocks=True, lstrip_blocks=True) — the plain, unsandboxed Jinja2 environment that exposes full Python object introspection (__globals__, __builtins__, etc.). Its citation_prompt() function renders user-controlled text — specifically content pulled from <CITATION_GUIDELINES> tags inside an LLM component's sys_prompt, set via the Canvas DSL — directly through that environment as a template rather than as inert string data, so attacker-supplied Jinja2 syntax executes server-side instead of being displayed.
Vulnerable Pattern
Any time a string that originated from user input reaches a template engine's .from_string()/.render() path instead of a plain string-formatting call, the engine's full expression language — attribute access, method calls, object graph traversal — becomes available to the attacker. Jinja2's default Environment has no barrier between a rendered template and Python's object model; only jinja2.sandbox.SandboxedEnvironment restricts attribute/method access. RAGFlow used the former for a field that was never meant to hold template syntax.
Why AI Services Are High-Value
RAG platforms are deliberately configured to hold an organization's most sensitive internal knowledge — contracts, source code, credentials-adjacent documentation — indexed for retrieval. The same container also holds live LLM provider API keys and vector-store credentials needed to serve queries. An RCE here doesn't just compromise a web app; it compromises the knowledge base and the keys protecting it in a single step, with the added twist that the attack surface (a "prompt" field) looks like ordinary product functionality rather than an obvious injection point.
The Fix
v0.25.0 replaces the unsandboxed environment with jinja2.sandbox.SandboxedEnvironment, which blocks access to dunder attributes and unsafe built-ins during rendering. No workaround was published by the vendor pre-patch; the closest mitigation is preventing untrusted accounts from reaching the vulnerable code path at all — disable open self-registration or restrict who can create/execute Canvas workflows until you upgrade.
Attack Chain
Recon / account acquisition
Attacker discovers a reachable RAGFlow instance (default self-registration enabled) and creates a normal user account; no special role or admin access needed.
Defensive note: disable open self-registration or gate it behind invite/SSO; alert on new-account-to-first-Canvas-execution intervals under a few minutes.
Malicious model registration
Attacker registers a fake OpenAI-compatible LLM model pointing at an attacker-controlled endpoint (not strictly required if using an existing configured model, but commonly used to control response/timing).
Defensive note: audit/restrict who can add custom model providers; log outbound model-provider URL configuration changes.
Canvas construction with payload
Attacker builds a Canvas workflow chaining a DuckDuckGo search tool into an LLM component, and embeds a Jinja2 SSTI payload (e.g. {% set g = cycler.__init__.__globals__ %}...os.popen(...)) inside <CITATION_GUIDELINES> tags in the LLM component's sys_prompt.
Defensive note: monitor Canvas DSL payloads/API calls for Jinja2 control syntax ({%, {{, __globals__, __builtins__) in prompt/system-prompt fields — a strong, low-false-positive signal since normal users never legitimately type template syntax there.
Trigger / server-side render
Attacker executes the Canvas; the DuckDuckGo component populates retrieved chunks, which flows into citation generation, causing citation_prompt() to render the attacker's string through the unsandboxed PROMPT_JINJA_ENV.from_string()/render path.
Defensive note: runtime egress/process monitoring on the RAGFlow app container — unexpected child processes (os.popen, subprocess) spawned from the Python web-worker process are the tell.
Command execution / impact
Arbitrary OS commands run with the privileges of the RAGFlow server process (container), giving the attacker code execution, filesystem access to mounted volumes (documents, vector-store credentials, LLM API keys), and a foothold for lateral movement into whatever network the RAGFlow container reaches.
Defensive note: least-privilege container user (non-root), network egress restrictions from the RAGFlow pod/container, and secrets stored outside the container filesystem (vault/KMS) so RCE doesn't equal instant credential theft.
Impact
RAG platforms sit at the intersection of an organization's most sensitive internal knowledge and its LLM infrastructure — a single template-injection RCE here compromises both the indexed documents and the credentials protecting them.
Remote Code Execution / Host Compromise
- Arbitrary OS command execution as the RAGFlow service account inside the container
- Full read/write of any files mounted into the container (uploaded documents, configs, .env)
- Potential container escape or pivot if the container has excess capabilities or a mounted Docker socket
- Persistence via cron/webshell drop if writable paths exist
Credential & Secret Theft
- Access to LLM provider API keys (OpenAI-compatible endpoints, embedding providers) stored in RAGFlow config/DB
- Access to vector database (Elasticsearch/Infinity) connection details present in the app's environment
- MySQL/Redis connection strings typically present in the app's environment
- Ability to read session/auth tokens of the RAGFlow application itself for further privilege escalation
Data Confidentiality & Integrity (RAG corpus)
- Exfiltration of ingested/indexed documents — often sensitive internal knowledge bases by design of a RAG tool
- Tampering with retrieved chunks/citations to poison downstream LLM answers (indirect prompt injection at scale)
- Deletion or corruption of the knowledge base via filesystem/DB access gained post-RCE
- Loss of integrity guarantees for any downstream system consuming RAGFlow's citations or generated answers
Defensive Tutorial
Patch to v0.25.0 or later
ImmediateRe-pull the infiniflow/ragflow image tagged v0.25.0+ and redeploy; confirm the fix by checking that rag/prompts/generator.py instantiates jinja2.sandbox.SandboxedEnvironment, not jinja2.Environment.
If you cannot patch immediately, disable self-registration
ImmediateSet whatever env/config flag controls open signup (RAGFlow's REGISTER_ENABLED/equivalent in docker/.env or admin settings) to prevent low-trust accounts from being created at all.
Grep logs for exploitation indicators
ImmediateSearch application/API logs for Canvas save/execute requests containing template syntax: patterns like {{, {%, __globals__, __builtins__, __import__, cycler, specifically in CITATION_GUIDELINES payloads with embedded braces.
Rotate secrets reachable from the RAGFlow container
ImmediateAny LLM API keys, DB credentials, or vector-store tokens present in the RAGFlow environment/config should be rotated now if the instance was internet-reachable and self-registration was ever enabled pre-patch.
Restrict Canvas creation/execution via RBAC
ImportantIf RAGFlow's role model allows it, limit who can build/run Canvas workflows (especially DuckDuckGo + LLM chains) to a trusted admin group rather than all authenticated users.
Egress-restrict the RAGFlow container
ImportantApply a network policy (Kubernetes NetworkPolicy / Docker network rules) so the RAGFlow app pod cannot make arbitrary outbound connections.
Run the RAGFlow service as non-root with a read-only root filesystem
ImportantWhere feasible, drop unnecessary Linux capabilities in the container spec in addition to the non-root/read-only posture.
Add template-injection detection to CI/SAST for internal forks
ImportantFlag any jinja2.Environment(...) (non-sandboxed) instantiation that renders user-controlled strings; this exact pattern recurred twice in RAGFlow (this CVE and a separate StringTransform/Message-component SSTI, GHSA-vvwj-fvwh-4whx).
Track this component in your SBOM/vuln-management pipeline
ImportantPin infiniflow/ragflow >= 0.25.0 and subscribe to github.com/infiniflow/ragflow/security/advisories — this codebase has shown a pattern of unsandboxed-template issues across multiple components.
References
- GitHub Security Advisory GHSA-wpg4-h5g2-jxm6 — vendor advisory with patch details and timeline.
- NVD Entry CVE-2026-45312 — CVSS 9.9, CWE-1336, affected version range.
- Vendor Fix infiniflow/ragflow PR #14068 — the SandboxedEnvironment fix, authored by Lyutoon.
- CWE Reference CWE-1336 — Improper Neutralization of Special Elements Used in a Template Engine