ML CVEs
Binary code on a screen
Vulnerability Tracking

trust_remote_code and the ML Orchestration CVE Class

A second family of ML supply-chain CVEs has nothing to do with model weights and everything to do with the glue: transformers' trust_remote_code, langchain expression surfaces, and template injection in orchestration libraries.

By ML CVEs Editorial · · 8 min read

The deserialization CVE class gets most of the attention because “loading a model runs code” is a memorable headline. But a second family of ML supply-chain CVEs has nothing to do with model weights and everything to do with the glue between them: the orchestration libraries that load custom model code, evaluate expressions, and render prompt templates. These are CWE-94 (code injection) and CWE-1336 (template injection) flaws, and they are arguably more dangerous in production because they execute on attacker-influenced input at runtime, not just on a one-time model load.

transformers and trust_remote_code

Hugging Face transformers supports models whose architecture is defined by Python files shipped in the model repository. Loading such a model with trust_remote_code=True imports and runs that code in your process. The flag exists because some legitimate architectures need it, and the documentation is explicit that you should only enable it for repositories you trust and ideally pin to a specific revision.

In practice the flag is copied from tutorials and left on. The moment it is set against a repository you do not control, “download this model” becomes “execute this repository’s code.” There is frequently no separate CVE for this — it is documented intended behavior — which is exactly why it is dangerous as a tracking problem: a defender scanning a CVE feed will not see “you enabled trust_remote_code on an untrusted repo,” yet it is functionally equivalent to the deserialization RCEs that do get CVEs. Treat it as a standing exposure to audit in your own code, not something the feed will surface for you.

langchain and the expression/eval surface

Where there is a CVE, the langchain ecosystem is the canonical case study. CVE-2023-29374 covered code execution in LLMMathChain: the chain took LLM output and evaluated it as a Python expression, so prompt-influenced text reached an eval-like sink. CVE-2024-21513 covered code execution in langchain-experimental through a similar path. These are CWE-94: untrusted text — and LLM output driven by user input is untrusted text — flowing into a code-generation or evaluation primitive.

The structural lesson generalizes past langchain. Any orchestration component that (a) takes model output and (b) feeds it to eval, exec, a math parser, a code interpreter tool, or a shell, is one prompt-injection away from RCE. The CVE history of these libraries is not a story about one buggy function; it is a series of independent rediscoveries that “LLM output is an untrusted input channel” applied to whatever sink was nearest.

Template injection: the quieter variant

Prompt templates are rendered by template engines. If user-controlled data is interpolated into a template that is itself rendered — rather than passed as a bound variable — you have CWE-1336 server-side template injection, and depending on the engine that ranges from data disclosure to code execution. This appears in ML stacks wherever prompt construction concatenates user input into a Jinja-style template instead of parameterizing it. It rarely produces a dramatic ML CVE, which is precisely why it survives in codebases: it looks like string formatting, not a sink.

Reading these CVEs correctly

For this class the triage question is different from deserialization. It is not “did I produce these bytes” but: does untrusted runtime input — user prompts, retrieved documents, tool output — reach a code, expression, or template sink? A langchain code-execution CVE is critical for an app that pipes user chat into an agent with a Python tool, and close to irrelevant for an app that uses the library only for static, server-authored chains with no eval-capable tools. Same CVE, opposite exposure, and the CVSS vector — typically scored for the worst case — will not distinguish them. Your data-flow does.

What actually mitigates it

  • Default trust_remote_code to off and pin revisions. If a model genuinely requires it, vendor and review the model code, then load it pinned to a reviewed commit — never a moving branch.
  • Keep model output away from code sinks. Do not eval LLM output. Replace math/code chains with sandboxed, allow-listed evaluators, or remove the capability if the use case does not need it.
  • Parameterize prompts; never render user input as a template. Pass user data as bound variables to a template that is authored entirely by you.
  • Treat orchestration libraries as a tracked CVE surface in their own right. langchain, langchain-experimental, transformers, llama-index and similar move fast and have a recurring code-execution history. Pin versions, watch their advisories specifically, and re-evaluate exposure when your agent’s tool list changes — not only when a CVE drops.

Weight-loading CVEs and orchestration CVEs are two halves of the same supply chain. The first asks whether you trust the bytes; the second asks whether you trust the inputs flowing through the glue at runtime. A tracker that only watches the first half is reporting on half the attack surface.

Sources

  1. transformers: trust_remote_code documentation and warning
  2. CVE-2023-29374: LangChain LLMMathChain code execution — NVD
  3. CVE-2024-21513: langchain-experimental code execution — NVD
  4. CWE-94: Improper Control of Generation of Code (Code Injection) — MITRE
  5. CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine — MITRE
Subscribe

ML CVEs — in your inbox

CVEs in ML libraries, frameworks, and the AI/ML supply chain. — delivered when there's something worth your inbox.

No spam. Unsubscribe anytime.

Related

Comments