A GGUF model file is not just weights. It carries metadata, and one of those metadata
keys — tokenizer.chat_template — is a Jinja2 template string. If a server renders that
template with an unsandboxed Jinja environment, a model file you downloaded can run code on
the machine that loads it. That is the shape of two real advisories:
-
CVE-2024-34359 (llama-cpp-python, fixed 0.2.72): the chat template was rendered with a
plain
jinja2.Environment, so a crafted template reached Python builtins — server-side template injection from a model file. -
CVE-2026-5760 (SGLang
/v1/rerank, fixed 0.5.11, CVSS 9.8): the same class, driven by the chat template inside a GGUF file, over an unsandboxedjinja2.Environment.
The payload lives in the file, at rest. That means you can check for it before anything
renders it — no loading, no execution, no network. So I added gguf_chat_template_scan to the
open-source keras-model-scan toolkit: it reads a GGUF file's metadata, finds the
tokenizer.chat_template string, and pattern-matches the tokens that SSTI payloads need.
What it looks for
The template is treated as text and scanned for the building blocks of a Jinja SSTI payload:
python gguf_chat_template_scan.py suspicious-model.gguf
The rules:
-
CT001 (HIGH) — Python dunder attribute access (the
__class__/__mro__/__subclasses__walk used to climb from a harmless object to a dangerous one). -
CT002 (HIGH) — a Jinja SSTI builtin global (
cycler,lipsum,joiner,namespaceand friends that leak a path to builtins). -
CT003 (HIGH) — an OS / process / dynamic-import call surfacing in the template
(
os,subprocess,eval,exec,import). -
CT004 (MEDIUM) — dynamic attribute-access bypass (
attr,getattr,|attr(...)). -
CT005 (MEDIUM) — a web-framework SSTI global (
request,config,self). -
CT006 (MEDIUM) — a template
import/include/extendstag. - GCT000 (LOW) — the GGUF header/metadata could not be read (truncated or malformed).
It never renders the template. It parses the GGUF container, reads the metadata value, and
runs string checks. Clean templates — the ordinary chat formatting most models ship — produce
no findings.
Honest limits
This is a static token-matcher, not a Jinja parser or a sandbox. It flags suspicious templates;
it does not prove a given template is exploitable against a given server, and whether it is
exploitable depends on how that server renders it (a properly sandboxed environment blocks the
whole class). A payload that avoids the known tokens — heavy obfuscation, unusual encodings — can
slip past, as with any signature approach. It covers the GGUF carrier; the JSON
tokenizer_config.json and .jinja sidecar carriers are handled by a sibling scanner,
chat_template_scan, in the same repo. Use it as one layer: a fast pre-download / pre-serve
triage, not a guarantee.
Where it is
Code, rule reference and examples:
- Toolkit: https://github.com/munimtechnologies/keras-model-scan
- This scanner: https://github.com/munimtechnologies/keras-model-scan/blob/main/GGUF_CHAT_TEMPLATE_SCAN.md
It is Apache-2.0. This work was made with AI agents working in my name, and I say so plainly.
— MV2, Munim, Inc.
Top comments (0)