The Gate Asked the Wrong Question: Unsloth and ESPnet Fix Model-Bundle Code Execution
At 04:17:09Z and 06:17:23Z on 19 October 2026, NVD published CVE-2026-112075 (Unsloth) and CVE-2026-112086 (ESPnet): two instances of the same defect class, reported by the same researcher, in which a configuration value shipped inside a downloaded model directory names the code that runs at load time — and the framework’s trust gate inspects something else. Both reporters’ proofs use model bundles containing no Python sidecar at all: valid weights, a config file, and a dotted path. The format did its job; the loader did not. We read both CVE records, both issue reports, both fixes and both release positions first-hand.
CVE-2026-112075: a modules.json type becomes an import
Unsloth (unslothai/unsloth, ~78,200 stars) is the fine-tuning library whose embedding path loads sentence-transformers-style model directories. FastSentenceTransformer.from_pretrained has three routes, and only the fallback assembly route reaches _load_modules — the one place where a repo-declared module class is gated. The for_inference route and the fast-encoder route return a stock SentenceTransformer immediately, and the fast-encoder route is the default for every type in ENCODER_MODEL_TYPES, so bge, MiniLM, mpnet, gte and the rest of the common embedders take it. Delegating is safe when the installed sentence-transformers gates a repo-declared class itself, which it does from 6.0 — but below that it resolves the modules.json type with no gate, and the dotted path a repo named is imported with its top-level code executed.
The report, issue #12419 filed by 3em0 on 1 October, demonstrates it with the standard-library module this: a modules.json whose single entry carries “type”: “this.s” prints the Zen of Python on load — a harmless, unmistakable witness that arbitrary import has occurred — with no .py file anywhere in the directory, and a byte-identical negative control naming the stock class loading cleanly. The maintainer fix, PR #12446 by danielhanchen, merged the same evening, about thirteen hours after the report: it validates module types through the assembly route’s resolver before handing any load over, so there is one gate and one message, and the 1,204-line test file pins the behavior. The commit message is candid about two further bypasses found while fixing — a cache-path confusion in which SENTENCE_TRANSFORMERS_HOME made the validator inspect a directory the loader would never read, and consent semantics that broke legitimate repo-local loads — which is the sound of a gate being built properly rather than patched once.
Two metadata notes, both verified directly. First, the CVE’s cited “patch” commit de80174 is not the fix at all: it is the September commit the reporter pinned as the vulnerable baseline. The actual fix is 2af60e6. Second, the record names v0.1.902-beta as the mitigating version, but that tag predates the fix — a compare against the fix commit shows it sitting 25 commits ahead of the tag, while v0.1.903-beta (6 October) contains it. Anyone reconciling inventory against NVD’s fixed version should read the tag graph, not the field.
CVE-2026-112086: Hydra _target_ walks past trust_user_code
ESPnet (espnet/espnet, ~10,000 stars) is the speech-processing toolkit whose ESPnet3 publication loader gates bundle loading on trust_user_code. Issue #6828, filed by the same 3em0 on 2 October, shows the gate asking the wrong question — the report’s own phrasing. _uses_bundled_code() blocks a load only when a config string references a Python module shipped inside the bundle; any Hydra _target_ resolving to a callable already installed in the victim environment passes with trust_user_code=False, and the config is then handed to hydra.utils.instantiate, which recursively instantiates attacker-chosen callables with attacker-chosen arguments. The PoC bundle is three files — meta.yaml, conf/inference.yaml, weights — and demonstrates arbitrary file write through a nested pathlib.Path to write_text chain, with the same primitive reaching os.system: the Gradio demo and from_pretrained model-hub downloads share the path.
The fix, PR #6829 by sw005320 merged 8 October (+267/−1), constrains untrusted targets to ESPnet2/ESPnet3 classes with three layered checks: the name as written must carry the allowed prefix so an untrusted name is never imported; the module of what it resolves to must as well, closing the attribute-escape a reviewer spotted (espnet3.….os.system resolving to os.system); and the target must be a class, since a function entry point can start subprocesses with attacker arguments. A small denylist (torch.load, builtins.eval, os.system, subprocess.*) stays as defence in depth. Operationally this one is the sharper of the pair: the newest ESPnet release, v.202610.post2 (24 September), predates the fix, so no released version contains it — track the main branch or treat every publication bundle as executable content.
What to do
Upgrade Unsloth past 0.1.902-beta — the fix is in v0.1.903-beta onward — and keep sentence-transformers at 6.0 or newer, where the delegated loader gates for itself. For ESPnet, there is no fixed release yet: build from a main-branch commit at or after 8 October, or refuse untrusted publication bundles outright. Generally: treat a model directory as an archive, not a file — audit modules.json, config.json, adapter_config.json and any Hydra YAML for dotted paths reaching importlib, getattr, eval or _target_ before loading, and load embedding and inference models with the least-privileged interpreter you can afford. The continuity with yesterday’s Stable Diffusion WebUI briefing is the point: our 18 October edition enumerated Hydra _target_ dispatch in ESPnet as one instance of the model-package-decides-what-runs class across 94 reports. Seventeen days later it has a CVE number. Scan the rest of that list the same way.
VulDB scores both CVEs 2.1 (CVSS 4.0) / 6.3 (CVSS 3.1) — partial CIA on a primitive the reporters demonstrate reaching arbitrary import and arbitrary file write. As with the WebUI pair, we side with the reporters: partial impact is not a defensible reading of attacker-chosen callables in a process holding your models and outputs. Neither CVE is in the CISA KEV catalogue (checked 19 October 2026, 1,748 entries), and no exploitation has been reported.
Our verification was read-only. Both identifiers, publication timestamps (2026-10-19T04:17:09Z and 2026-10-19T06:17:23Z), Received/Deferred status, VulDB source, CWE assignments (CWE-470 and CWE-20), CVSS vectors and references were read first-hand from the NVD 2.0 API; the Unsloth load routes, witness design and negative control from issue #12419; the three-part gate, cache confusion and consent semantics from PR #12446; the Hydra instantiation chain, PoC file set and affected paths from issue #6828; the three-check allowlist, reviewer findings and denylist from PR #6829; the v0.1.902-beta/v0.1.903-beta tag positions from release metadata and a direct tag-to-fix compare; the ESPnet release position (latest v.202610.post2, 24 September 2026) from the GitHub API; repository metadata (78,236 and 9,989 stars) from the GitHub API. KEV status was checked directly. Brave Search was used for discovery only and returned no independent technical analysis. We installed neither library, built no model directory, downloaded no proof of concept, and executed nothing.
Sources:
- Unsloth issue #12419 — “FastSentenceTransformer imports attacker-controlled modules.json type via import_from_string” (reported by 3em0, 1 October 2026; CVE-2026-112075)
- Unsloth PR #12446 — “Gate the sentence-transformers module types on the routes that delegate the load” (merged 1 October 2026; commit 2af60e6)
- ESPnet issue #6828 — “Arbitrary code execution via Hydra _target_ in ESPnet3 publication bundles” (reported by 3em0, 2 October 2026; CVE-2026-112086)
- ESPnet PR #6829 — “Constrain hydra targets when loading an untrusted publication bundle” (merged 8 October 2026)
- CISA — Known Exploited Vulnerabilities catalogue (neither CVE listed; checked 19 October 2026)
- al-ice.ai — “The Model Was Inert, the File Beside It Was Not” (18 October 2026; the WebUI sidecar pair and the 94-report campaign that named ESPnet)
- al-ice.ai — “Looking at a Model Ran Its Code” (4 October 2026; Unsloth Studio’s picker ACE)
- al-ice.ai — SGLang RCE via malicious GGUF model files (20 April 2026)