groundy
security

SGLang's CVE-2026-5760 Turns a GGUF Download Into RCE, Shifting the Trust Boundary to Hugging Face

CVE-2026-5760 lets poisoned GGUF files trigger Jinja2 SSTI through SGLang's unsandboxed template rendering, forcing teams to treat hub downloads as executable code.

9 min···12 sources ↓

On April 20, 2026, CISA-ADP scored CVE-2026-5760 CVSS 9.8 CRITICAL, disclosing that SGLang’s /v1/rerank endpoint executes arbitrary Python when it renders a model’s tokenizer.chat_template through an unsandboxed jinja2.Environment() (NVD CVE-2026-5760 Detail). A poisoned GGUF file from any source can embed a Jinja2 SSTI payload in that metadata field, which turns a routine model download into remote code execution on the inference host. That means self-hosted teams can no longer treat Hugging Face pulls as weights-only operations.

The April 20 Disclosure: What CVE-2026-5760 Actually Does

On April 20, 2026, CISA-ADP published CVE-2026-5760 with a CVSS 9.8 CRITICAL score (NVD CVE-2026-5760 Detail). The vulnerability affects SGLang’s /v1/rerank endpoint and stems from a specific implementation choice: the code in python/sglang/srt/entrypoints/openai/serving_rerank.py instantiates a plain jinja2.Environment() rather than jinja2.sandbox.ImmutableSandboxedEnvironment when rendering a model’s tokenizer.chat_template (SGLang serving_rerank.py source (main branch)).

Because the environment is unsandboxed, any Jinja2 template expressions in that metadata are evaluated with full Python access. At the time of disclosure CERT/CC, which coordinated it, stated explicitly that “no response or patch was obtained during the coordination process” and that it had not received a statement from the vendor (CERT/CC Vulnerability Note VU#915947).

[Updated June 2026] SGLang has since fixed the code. PR #23660 swapped the plain jinja2.Environment() in serving_rerank.py for jinja2.sandbox.ImmutableSandboxedEnvironment, opened the day after disclosure and merged April 29, 2026; the fix shipped in v0.5.11 on May 5, 2026 (SGLang PR #23660 (sandbox the rerank chat-template environment)). NVD now lists versions before 0.5.11 as affected and references the PR as the patch (NVD CVE-2026-5760 Detail). The hygiene complaint still stands: SGLang published no formal GitHub Security Advisory, the fix landed as an ordinary PR, and the CERT/CC note was last updated April 27 and never amended to record the patch (SGLang Security Advisories (showing none published)). Anyone reading the advisory chain alone would still believe the bug is unaddressed.

How the Attack Chain Works: From Poisoned GGUF to RCE

An attacker embeds a Jinja2 server-side template injection (SSTI) payload inside a GGUF model’s tokenizer.chat_template metadata (CERT/CC Vulnerability Note VU#915947). GGUF files carry more than quantized weights; they include tokenizer configuration, chat templates, and other metadata that inference runtimes parse during model load.

When a self-hosted SGLang instance loads that model and the /v1/rerank endpoint processes a request, the runtime passes the poisoned template through the unsandboxed jinja2.Environment() (SGLang serving_rerank.py source (main branch)). The SSTI payload executes arbitrary Python on the inference host at load time. This is not prompt injection; the malicious code runs during model deserialization, before any user query is processed.

The SSTI Gadget Chain: How a Template Becomes a Shell

Jinja2 SSTI works because templates can reach live Python objects through attribute access. A payload as short as {{ ''.__class__.__mro__[1].__subclasses__() }} walks from a string literal up to object, enumerates every loaded subclass, and indexes to something useful like a subprocess.Popen or an os-importing warning class. From there the attacker calls into the operating system. There is no import statement in the template text and nothing a naive substring scan for import os would catch. This is the textbook server-side template injection escape, documented for a decade against Flask and Django apps. The only novelty is the injection vector: a model file instead of a web form.

ImmutableSandboxedEnvironment blocks the common gadgets by intercepting attribute access to dunder names and forbidding mutation, which is why both llama-cpp-python and SGLang reached for it. It is not a kernel-grade boundary. The Jinja maintainers are explicit that the sandbox raises the cost of an escape rather than guaranteeing none, and earlier versions have fallen to format-string and str.format_map gadgets. Treat it as defense in depth, not a wall.

The timing detail matters for detection. On the rerank path the template is rendered when a request reaches /v1/rerank, not at process start, so a poisoned model can sit loaded and inert until the first request exercises that route. A team that loads a model, smoke-tests /v1/chat/completions, and never touches rerank could promote the vulnerable model to production without tripping anything. The trigger is one unauthenticated request to the route nobody tested.

Why Hugging Face’s Scanners Miss It: The Trust Boundary Gap

Hugging Face runs ClamAV malware scans and pickle import checks on every uploaded file (Hugging Face Hub Pickle Scanning Documentation). Those scanners are designed to catch known malware signatures and unsafe Python deserialization, not to inspect GGUF metadata or embedded Jinja2 templates for SSTI payloads (Hugging Face Hub Pickle Scanning Documentation).

The gap matters because most self-hosted inference pipelines treat a model hub download as a passive data transfer. Teams verify hashes for integrity, but rarely audit the internal metadata fields that the runtime will execute. CVE-2026-5760 forces those teams to move provenance verification upstream: every downloaded model file must be treated as untrusted code, not as inert weights.

The Repeating Pattern: llama-cpp-python’s CVE-2024-34359 and What SGLang Ignored

This vulnerability class is not new. In 2024, llama-cpp-python disclosed CVE-2024-34359, scored CVSS 9.6 by GitHub [Updated June 2026], for the exact same flaw: rendering tokenizer.chat_template through an unsandboxed jinja2.Environment() (llama-cpp-python GHSA-56xg-wfcc-g829 (CVE-2024-34359)). The project patched it in v0.2.72 by replacing the unsandboxed environment with ImmutableSandboxedEnvironment (llama-cpp-python GHSA-56xg-wfcc-g829 (CVE-2024-34359)), a change visible in commit b454f40a (llama-cpp-python commit b454f40a (ImmutableSandboxedEnvironment fix)).

The fix pattern was established in 2024, and SGLang’s eventual patch is a near-verbatim copy of it. That is the uncomfortable part: the remediation was a known, two-line constructor swap with two years of prior art, yet the flaw still shipped to production and stayed live until external coordination forced the issue. The same gadget keeps reappearing because every inference framework reimplements chat-template rendering, and the unsandboxed jinja2.Environment() is the path of least resistance every time.

Model Loading Is the Real Attack Surface

CVE-2026-5760 is one instance of a larger problem: model files are executable inputs, and the ecosystem keeps treating them as inert data. The classic case is Python pickle, the default serialization behind older PyTorch checkpoints, which calls __reduce__ on load and runs whatever the file dictates. Hugging Face’s pickle scanning and tools like picklescan exist precisely because a .bin checkpoint can be a payload, and that scanner has shipped its own CVSS 10.0 bypass, a reminder that static analysis of a serialization format is a losing race against a determined attacker.

The same trust failure appears in trust_remote_code=True, the Transformers flag that lets a model repo ship its own Python and have it imported on load. InstructLab’s CVE-2026-6859 hardcoded that flag to true, turning any model pull into code execution with no opt-out. GGUF was supposed to be the safer format: a single self-describing binary, no pickle, no remote code. CVE-2026-5760 shows the metadata itself became the vector once a runtime decided to interpret one of its fields as a template.

Safetensors is the counterexample that proves the point. It was designed as a pure tensor container with no executable hooks and no code path on load, and it has held up because there is nothing in the format to interpret. The lesson is structural: every field a loader interprets is attack surface, and the durable formats interpret as little as possible. Chat templates are the awkward exception because they are code by design, a templating language bundled with the weights, which is why the same CVE keeps recurring across llama-cpp-python, SGLang, and vLLM.

The pattern reaches past model files. LangChain’s CVE-2025-65106 covered template injection through attribute access in its prompt templates, and RAGFlow shipped an SSTI-to-RCE in an agent text-processing component. Same root cause, different host: a templating engine handed attacker-influenced input with too much ambient authority. For self-hosted inference teams the operational takeaway is to put the model loader, not just the network endpoint, inside the threat model, and to run a model-file scanner that understands GGUF, such as ModelScan, in the deploy path rather than trusting a hub badge.

What Teams Should Do Now: Mitigations and Deploy-Path Checks

[Updated June 2026] The first action is now simple: upgrade to SGLang v0.5.11 or later, which sandboxes the rerank template environment (SGLang PR #23660 (sandbox the rerank chat-template environment)). Anything before that is exploitable, and because the patch shipped without an advisory, version-pinned deployments may still be running vulnerable builds without any alert telling them to move.

The upgrade does not retire the underlying discipline. Teams should still audit every GGUF file before it reaches the inference host, inspecting tokenizer.chat_template for Jinja2 expressions that call into Python internals and treating any template that uses __import__, os, subprocess, or attribute-walking constructs like __class__ and __mro__ as malicious regardless of the model’s origin. The sandbox raises the bar; it does not make a hostile template harmless, and it covers only the rerank path that was patched.

For maintainers, the remediation is the same one llama-cpp-python applied in 2024 and SGLang copied in 2026: swap jinja2.Environment() for jinja2.sandbox.ImmutableSandboxedEnvironment anywhere tokenizer templates are rendered (llama-cpp-python commit b454f40a (ImmutableSandboxedEnvironment fix)). The root cause is systemic; while the disclosed trigger was the /v1/rerank endpoint, any code path that renders model-supplied templates without sandboxing carries the same risk, and a fix scoped to one endpoint does not prove the others are clean.

What Still Needs Fixing: Vendor Response and Scanner Coverage

[Updated June 2026] SGLang shipped the code fix in v0.5.11 but still has not published a security advisory or publicly acknowledged the vulnerability through its own channels (SGLang Security Advisories (showing none published)). The related vLLM entry the PoC author referenced, CVE-2025-61620, has since been published as GHSA-6fvq-23cw-5628. It is a denial-of-service through a malicious Jinja template in vLLM’s OpenAI-compatible server, not RCE, fixed in vLLM 0.11.0 (vLLM GHSA-6fvq-23cw-5628 (CVE-2025-61620 Jinja template DoS)). The same model-loading attack surface produced a separate vLLM RCE advisory, GHSA-8fr4-5q9j-m8gm, for code execution through a model’s config in get_config (vLLM GHSA-8fr4-5q9j-m8gm (RCE via model config in get_config)).

Model hub security scanning also remains incomplete. Hugging Face’s current pipeline does not gate GGUF metadata on executable Jinja2 (Hugging Face Hub Pickle Scanning Documentation), which leaves a window open for poisoned uploads that pass every existing check. Tooling is catching up unevenly: Hugging Face now publishes a dedicated analysis project for GGUF chat templates (huggingface/gguf-jinja-analysis (GGUF chat-template static analysis)), but it reads as a research and audit tool rather than a blocking gate on every upload. Closing the window for real will require either hub-side template static analysis run by default or runtime sandboxing that inference frameworks adopt as a baseline instead of after a CVE.

Frequently Asked Questions

Does this vulnerability affect SGLang endpoints other than /v1/rerank?

While CVE-2026-5760 was disclosed for the /v1/rerank endpoint, the root cause is unsandboxed Jinja2 template rendering during model loading. Any code path that renders model-supplied tokenizer.chat_template without sandboxing carries the same risk.

How is this different from prompt injection?

Prompt injection manipulates user-facing inputs to alter model behavior. CVE-2026-5760 is server-side template injection: the Jinja2 payload executes during model deserialization on the inference host, before any user query is processed. Firewalls inspecting user inputs will not detect it.

Is CVE-2026-5760 patched, and what should teams still do?

Yes. SGLang fixed it in v0.5.11 (May 5, 2026) via PR #23660, which sandboxes the rerank template environment, so the first step is to upgrade past 0.5.11. Because the fix shipped without a formal advisory, version-pinned deployments may still run vulnerable builds. Beyond upgrading, teams should audit every GGUF file before loading it, inspecting tokenizer.chat_template for Jinja2 expressions that call Python internals like import, os, or subprocess. Any template using such constructs should be treated as malicious regardless of the model’s origin.

Will Hugging Face’s security scanners catch a poisoned GGUF file?

No. Hugging Face’s current scanners run ClamAV and check for unsafe pickle imports, but they do not inspect GGUF metadata or embedded Jinja2 templates for SSTI payloads. A clean scan does not guarantee a GGUF file is safe to load.

sources · 12 cited

  1. NVD CVE-2026-5760 Detailnvd.nist.govprimaryaccessed 2026-04-23
  2. CERT/CC Vulnerability Note VU#915947kb.cert.orgprimaryaccessed 2026-04-23
  3. SGLang serving_rerank.py source (main branch)github.comprimaryaccessed 2026-04-23
  4. SGLang Security Advisories (showing none published)github.comprimaryaccessed 2026-04-23
  5. Hugging Face Hub Pickle Scanning Documentationhuggingface.covendoraccessed 2026-04-23
  6. Protect AI ModelScan (model-file scanner, GGUF support)github.comcommunityaccessed 2026-06-26