⌁AI·CYBER·BRIEF▌
Vulnerabilities8 min read

Two Unsloth flaws let a model's config file run code — one needs no trust_remote_code

A Pillar Security researcher found two ways a Hugging Face model's config.json could run code on machines using Unsloth. Both are fixed; only one got a CVE.

Within two days, Unsloth — one of the most widely used open-source toolkits for fine-tuning large language models — was shown to have two separate ways that a model's configuration file could run code on the machine loading it. One received CVE-2026-93348 (CVSS 8.6) and is fixed in unsloth-zoo 2026.8.14 / unsloth 2026.8.20; the other never received a CVE at all and was quietly fixed back in June. If you fine-tune or quantize models with Unsloth, upgrade to at least unsloth 2026.8.20 and treat every model you pull from a public hub as untrusted code, not data.

What happened, in plain English

Unsloth is a Python library that makes fine-tuning and quantizing LLMs (large language models) dramatically cheaper in memory and time. It is popular — the main repository carries about 76,900 GitHub stars — and it is the sort of tool that runs on expensive GPU machines holding training data, cloud credentials and Hugging Face access tokens.

To use it, you point it at a model, usually one hosted on Hugging Face, a public hub where anyone can publish. Every model there ships with a small text file called config.json that describes the model: how many layers, what architecture family it belongs to, and so on. The reasonable assumption is that this file is data — a description to be read, not instructions to be obeyed.

Ariel Fogel, a researcher at Pillar Security, found two places where Unsloth broke that assumption.

The first was in Unsloth Studio, the browser interface bundled with the library. Merely selecting a model in the UI caused the backend to read its config.json. If that file contained an auto_map entry pointing at custom Python files in the model repository, those files were executed on the spot — during capability detection, before a single weight was downloaded. Looking at a model was enough to run its code.

The second, CVE-2026-93348, is subtler and in some ways worse. Unsloth built a Python import statement by pasting the model_type string straight out of the config into generated source code, then ran it. Put a newline character into that string and you are no longer naming a model family — you are writing new lines of program. Think of a form that asks for your job title and then reads your answer aloud as a command to the room: put a full stop and a new sentence in the box, and the room follows your sentence.

Are you affected? What to do now

You are affected if you have unsloth or unsloth-zoo installed and have loaded models from any source you do not fully control. Self-hosted and cloud notebook environments (Colab, Kaggle, SageMaker, internal GPU clusters) are equally in scope — this is a library flaw, not a service flaw.

Check what you have:

  • Run pip show unsloth unsloth-zoo and read the Version: lines. Unsloth uses date-style version numbers, so 2026.8.19 is older than 2026.8.20.
  • For CVE-2026-93348, you are vulnerable if unsloth-zoo is 2025.9.9 or newer but below 2026.8.14, or unsloth is 2025.9.9 or newer but below 2026.8.20.
  • For the Studio flaw, you are vulnerable if unsloth is 2026.5.10 or earlier. If you are on a current release you are already past it.
  • Check pinned versions in requirements.txt, pyproject.toml, lockfiles, Dockerfiles and any prebuilt training images — pinned dependencies are the usual reason a fixed library never actually lands.

Fix it, in this order:

  • Upgrade both packages together: pip install -U "unsloth-zoo>=2026.8.14" "unsloth>=2026.8.20". Upgrading only one leaves the vulnerable code path in place.
  • Rebuild and redeploy any container images that bake Unsloth in, then confirm the version inside the running image rather than in the build file.
  • Audit your own code for trust_remote_code=True. This is a Hugging Face transformers setting that permits a model repository to ship and execute its own Python. Set it explicitly to False unless you have read the model's code yourself.
  • If an Unsloth Studio instance on an older version was reachable by anyone beyond its operator, or if untrusted models were loaded on a vulnerable version, rotate what that process could see: Hugging Face tokens, cloud provider credentials, SSH keys and any API keys present in the environment.
  • Prefer models from accounts you can identify, and pin models by revision hash rather than by branch name, so a repository cannot change under you after review.

On detection: no official source has published indicators of compromise for either issue, and there is nothing to hunt for by hash or domain. Dark Reading reported on September 29, 2026 that no real-world exploitation and no malicious model repositories targeting this mechanism have been observed. For most readers the honest answer is that upgrading is the whole job — and that if you do not run Unsloth anywhere, you have nothing to do here at all.

The expert view

Both bugs are the same class: a confusion between describing a model and instantiating one. The root cause in CVE-2026-93348 is textbook CWE-94, improper control of generation of code. get_transformers_model_type() in hf_utils.py gathered model_type values from nested model configurations, applied some normalisation but no character allowlist, and the result was interpolated into source that unsloth_compile_transformers() handed to exec(). Injection into a code generator is not meaningfully different from injection into a SQL statement; the sink was simply a Python compiler instead of a query planner.

What makes this one interesting is that it sits outside the mechanism everyone has been trained to worry about. The standard advice for model supply chain risk is "don't enable trust_remote_code", and for the Studio flaw that advice is exactly right. But CVE-2026-93348 lives in Unsloth's own compilation path: the advisory and the two fix commits describe a string from the config being built into an import and executed by Unsloth itself, not custom model code being fetched through transformers. A shop that had diligently disabled remote code would still have been exposed. That is the same pattern this publication covered with MLflow, where two flaws let a crafted model run code even with pickle loading off — the hardening switch is real, and it is not the only door.

The remediation is worth reading, because it is better than the minimum. Pull request #1083 constrains model_type to [a-z0-9_]+ and, more importantly, replaces the exec()/eval() construction with importlib.import_module(), removing the sink rather than filtering the input. Its authors report validating 5,783 model types across transformers 4.57.6 to 5.15.1 with no legitimate values rejected, which is the sort of evidence that makes an allowlist deployable instead of a support burden. Pull request #1108 then goes after the class: an allowlist lookup against transformers' own MODEL_*_MAPPING_NAMES registries in place of a raw getattr, eval() removed from peft_utils.py and tokenizer_utils.py, and a static linter that fails the build when interpolated values reach exec, eval or compile. That last item is the durable fix.

On severity: CVSS 8.6 under version 4.0 (8.1 under 3.1) reads about right rather than inflated. The vector requires user interaction — somebody has to load the malicious model — and exploitation yields the privileges of the loading user, not root. But the context multiplies it. These processes routinely sit on machines with cloud credentials, proprietary datasets and write access to model artifacts, so code execution there means credential theft and the ability to poison training outputs — a quieter and longer-lived problem than a compromised host.

The disclosure handling is the loose thread. CVE-2026-93348 was assigned by VulnCheck, a third-party CVE Numbering Authority, and we could not find a GitHub Security Advisory from the project itself. For the Studio flaw, Pillar states that the maintainers declined to publish an advisory on the grounds that Studio was in beta — while noting that the vulnerable code shipped in the production PyPI package — and disputed the severity assessment, arguing that Hugging Face's malware scanning was an adequate control. Reasonable people disagree about severity, but a hub-side scanner is a different control from a client-side trust boundary, and it does not inspect a config.json for a newline in the wrong field. The practical consequence for defenders is mundane and annoying: without a project advisory, neither fix reaches you through the channel you normally watch, and you find out from a researcher's blog three months later.

What is still unknown: whether either flaw was ever exploited, how many pinned deployments remain on affected versions, and whether other fine-tuning and quantization toolkits build code from config fields the same way. Two independent instances in one library make that last question the one worth someone's time.

Official sources

Get the daily brief

AI + security signal by email: headlines, a two-line summary, a link. No noise, no spam.

How often