GHSA-j7w6-vpvq-j3gm: diffusers: silent RCE via None.py trust_remote_code bypass
GHSA-j7w6-vpvq-j3gm HIGHA logic flaw in HuggingFace Diffusers (< 0.38.0) allows a malicious HuggingFace Hub model repository to achieve arbitrary code execution on any machine that simply calls DiffusionPipeline.from_pretrained() — no suspicious arguments required, no warnings issued, and the returned pipeline is fully functional. The root cause is a Python string-formatting quirk where f"{None}.py" evaluates to "None.py", causing diffusers to silently load and execute any file by that name in a Hub repo while completely skipping the trust_remote_code security gate. With 385 downstream dependents and a working PoC included in the disclosure, exploitation requires only that a victim load a convincingly packaged model from Hub — a routine action in nearly every diffusion model workflow. Upgrade to diffusers 0.38.0 immediately and scan ~/.cache/huggingface/ for any file named None.py, which is a strong indicator of prior compromise.
What is the risk?
HIGH. CVSS 8.8 reflects network-reachable RCE requiring only user interaction (loading a Hub model). The severity is compounded by the silent nature of exploitation: no exception, no warning, fully functional output — victims have no signal that anything went wrong. The attack bypasses an explicit security mechanism (trust_remote_code) that users reasonably rely on as a safety gate. The 385 downstream dependents and the library's central role in diffusion model workflows create wide blast radius across research, MLOps, and production inference environments. No EPSS data yet, but a clear PoC in the advisory lowers the barrier to exploitation to near-zero.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| Diffusers | pip | < 0.38.0 | 0.38.0 |
Do you use Diffusers? You're affected.
How severe is it?
What is the attack surface?
What should I do?
6 steps-
Patch: upgrade immediately to diffusers >= 0.38.0 (pip install --upgrade 'diffusers>=0.38.0').
-
Audit caches: find ~/.cache/huggingface -name 'None.py' — any hit is a strong indicator of prior compromise requiring incident response.
-
Pre-load inspection: before calling from_pretrained on any Hub repo, check for unexpected *.py files at the repo root and in component subdirectories (unet/, scheduler/, etc.).
-
Pin model commits: use commit-hash pinning for all Hub models in production to prevent silent swaps to malicious versions.
-
Restrict process egress: in production inference environments, prevent model-loading processes from making arbitrary outbound network connections.
-
SBOM/dependency tracking: verify no internal tooling depends on diffusers < 0.38.0 via pip-audit or equivalent.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is GHSA-j7w6-vpvq-j3gm?
A logic flaw in HuggingFace Diffusers (< 0.38.0) allows a malicious HuggingFace Hub model repository to achieve arbitrary code execution on any machine that simply calls DiffusionPipeline.from_pretrained() — no suspicious arguments required, no warnings issued, and the returned pipeline is fully functional. The root cause is a Python string-formatting quirk where f"{None}.py" evaluates to "None.py", causing diffusers to silently load and execute any file by that name in a Hub repo while completely skipping the trust_remote_code security gate. With 385 downstream dependents and a working PoC included in the disclosure, exploitation requires only that a victim load a convincingly packaged model from Hub — a routine action in nearly every diffusion model workflow. Upgrade to diffusers 0.38.0 immediately and scan ~/.cache/huggingface/ for any file named None.py, which is a strong indicator of prior compromise.
Is GHSA-j7w6-vpvq-j3gm actively exploited?
No confirmed active exploitation of GHSA-j7w6-vpvq-j3gm has been reported, but organizations should still patch proactively.
How to fix GHSA-j7w6-vpvq-j3gm?
1. Patch: upgrade immediately to diffusers >= 0.38.0 (pip install --upgrade 'diffusers>=0.38.0'). 2. Audit caches: find ~/.cache/huggingface -name 'None.py' — any hit is a strong indicator of prior compromise requiring incident response. 3. Pre-load inspection: before calling from_pretrained on any Hub repo, check for unexpected *.py files at the repo root and in component subdirectories (unet/, scheduler/, etc.). 4. Pin model commits: use commit-hash pinning for all Hub models in production to prevent silent swaps to malicious versions. 5. Restrict process egress: in production inference environments, prevent model-loading processes from making arbitrary outbound network connections. 6. SBOM/dependency tracking: verify no internal tooling depends on diffusers < 0.38.0 via pip-audit or equivalent.
What systems are affected by GHSA-j7w6-vpvq-j3gm?
This vulnerability affects the following AI/ML architecture patterns: diffusion model inference pipelines, model fine-tuning and training workflows, ML CI/CD and evaluation pipelines, research notebooks and experimentation environments, GPU inference serving infrastructure.
What is the CVSS score for GHSA-j7w6-vpvq-j3gm?
GHSA-j7w6-vpvq-j3gm has a CVSS v3.1 base score of 8.8 (HIGH).
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0010.003 Model AML.T0011.000 Unsafe AI Artifacts AML.T0058 Publish Poisoned Models AML.T0074 Masquerading AML.T0079 Stage Capabilities AML.T0107 Exploitation for Defense Evasion Compliance Controls Affected
What are the technical details?
Original Advisory
## Background This vulnerability is found in the `DiffusionPipeline.from_pretrained` flow, which is used to load a pipeline from the HuggingFace Hub. This function accepts an optional `custom_pipeline` keyword argument: the name of a Python file in the repo that contains a custom class inheriting from `DiffusionPipeline`. An equivalent flow is triggered when the `_class_name` field in `model_index.json` (the repo config file) is set to a custom class. Any attempt to use a custom pipeline throws the following exception, requesting that `trust_remote_code` is also passed: ```python DiffusionPipeline.from_pretrained( pretrained_model_name_or_path='ido-shani/custom-pipeline', custom_pipeline="custom" ) ValueError: The repository for ido-shani/custom-pipeline contains custom code in custom.py which must be executed to correctly load the model. You can inspect the repository content at https://hf.co/ido-shani/custom-pipeline/blob/main/custom.py. Please pass the argument `trust_remote_code=True` to allow custom code to be run. ``` The vulnerability is a silent RCE - it allows arbitrary code to be loaded through the custom\_pipeline flow from a Hub repo, with no `custom_pipeline` or `trust_remote_code` kwargs and nothing suspicious in the config. The `from_pretrained` call succeeds and returns a functional pipeline. ## Naive Flow First, all relevant arguments are popped from kwargs and stored in local variables. Given a `pretrained_model_name_or_path` that is a Hub repo ID, `DiffusionPipeline.download()` is called. This function serves two roles: it orchestrates downloading relevant model files, and it is the security gatekeeper for `trust_remote_code`. It is called even if the model is already cached; in that case it exits early. If the repo contains custom code, it checks whether `trust_remote_code` was passed and raises otherwise: ```python # pipeline_utils.py:1645-1652 load_pipe_from_hub = custom_pipeline is not None and f"{custom_pipeline}.py" in filenames ... if load_pipe_from_hub and not trust_remote_code: raise ValueError(...) ``` It then runs `_get_pipeline_class`, which returns the class object of the pipeline in order to inspect its `__init__` signature and determine which component files need to be downloaded. As part of building the `allow_patterns` list used to filter the snapshot download to necessary files only, the custom pipeline file is explicitly included if present: ```python # pipeline_utils.py:1707 allow_patterns += [f"{custom_pipeline}.py"] if f"{custom_pipeline}.py" in filenames else [] ``` The function then checks if all expected files are already present, and either exits early or triggers a snapshot download with those patterns. The next step in `from_pretrained` is loading the pipeline class a second time, this time to actually instantiate it. Before calling `_get_pipeline_class` again, `_resolve_custom_pipeline_and_cls` is called to translate the `custom_pipeline` name into a local path, since the files have already been downloaded: ```python # pipeline_loading_utils.py:965-974 def _resolve_custom_pipeline_and_cls(folder, config, custom_pipeline): custom_class_name = None if os.path.isfile(os.path.join(folder, f"{custom_pipeline}.py")): custom_pipeline = os.path.join(folder, f"{custom_pipeline}.py") elif isinstance(config["_class_name"], (list, tuple)) and os.path.isfile( os.path.join(folder, f"{config['_class_name'][0]}.py") ): custom_pipeline = os.path.join(folder, f"{config['_class_name'][0]}.py") custom_class_name = config["_class_name"][1] return custom_pipeline, custom_class_name ``` When `custom_class_name` is `None` (i.e. `custom_pipeline` was given as a kwarg rather than via the config), `_get_pipeline_class` will scan the file and automatically identify the class that subclasses `DiffusionPipeline`. Once this is done, `_get_pipeline_class` is invoked with the resolved local path, which loads the custom code, retrieves the class object, and proceeds with instantiation. ## The Vulnerability `_resolve_custom_pipeline_and_cls` receives `custom_pipeline` from the kwargs - when not supplied it defaults to `None`. That `None` is used in string formatting: `f"{None}.py"` = `"None.py"`. **If the repo contains a file with this name, it will be detected as a custom pipeline.** This is only reached on the second invocation of `_get_pipeline_class` (inside `from_pretrained`, after `download()` returns). The trust\_remote\_code check lives entirely in `download()`, which evaluated `custom_pipeline is None -> False` and skipped it. By the time `_resolve_custom_pipeline_and_cls` runs, it is no longer relevant. As a bonus, `None.py` even gets downloaded automatically when the model isn't cached yet. This isn't strictly required - it is quite plausible that the victim has already run `hf download <model>` and has all files locally - but if they haven't, revisiting the `allow_patterns` line above shows it makes the same error: `f"{None}.py"` = `"None.py"` is added to `allow_patterns` and fetched. What should `None.py` contain? To avoid breaking the pipeline load, it must define a class inheriting from `DiffusionPipeline`. To avoid leaving suspicious clues in the config, that class should shadow one that already exists in diffusers. The following satisfies both requirements: ```python from diffusers import FluxPipeline as _FluxPipeline class FluxPipeline(_FluxPipeline): pass # INSERT MALICIOUS CODE HERE import pathlib pathlib.Path("/tmp/pwned").write_text(":)") ``` With this, `model_index.json` can contain `"_class_name": "FluxPipeline"` - appearing to use the standard diffusers class - and the resulting pipeline is fully functional (it is also functional when running as a local directory). This has been verified against an extracted version of [DDUF/tiny-flux-dev-pipe-dduf](https://huggingface.co/DDUF/tiny-flux-dev-pipe-dduf). All the attacker needs the victim to run is: ```python from diffusers import DiffusionPipeline pipeline = DiffusionPipeline.from_pretrained('ido-shani/none-py-trust-remote-code-bypass') ``` ## PoC - Upload this zip as a model to the hub. https://drive.google.com/file/d/1mULARMLJJUTCi57xIv0wtDauko-JW0h7/view?usp=sharing - Run `DiffusionPipeline.from_pretrained` on the uploaded model hub identifier. - RCE occured; `/tmp/pwned` was created. If users are running the exploit on windows, they should change the path touched in `None.py`. # Impact The vulnerability is a silent RCE - it allows arbitrary code to be loaded through the custom\_pipeline flow from a Hub repo, with no `custom_pipeline` or `trust_remote_code` kwargs and nothing suspicious in the config. The `from_pretrained` call succeeds and returns a functional pipeline. # Occurrences https://github.com/huggingface/diffusers/blob/e1b5db52bda85d47a4f8f75954f77e672a8f7f1c/src/diffusers/pipelines/pipeline_loading_utils.py#L976 # Patches Yes. Fixed in **diffusers 0.38.0** via [PR #13448](https://github.com/huggingface/diffusers/pull/13448). All users on versions `< 0.38.0` should upgrade: ```bash pip install --upgrade "diffusers>=0.38.0" ``` The fix moves the `trust_remote_code` gate out of `DiffusionPipeline.download()` and into `get_cached_module_file` in `src/diffusers/utils/dynamic_modules_utils.py`, which is the actual chokepoint for every dynamic module load (local, Hub, or community mirror). All three variants now raise `ValueError` when trust_remote_code=False instead of executing untrusted code. # Workarounds If upgrading immediately is not possible: - Only call `from_pretrained` with `pretrained_model_name_or_path`, `custom_pipeline`, and local snapshot directories from sources you fully trust and have audited. - Do not pass `custom_pipeline=` pointing at a Hub repository different from the primary `pretrained_model_name_or_path` unless you have read its `pipeline.py`. - Before calling `from_pretrained` on a local snapshot, inspect the snapshot for unexpected `*.py` files, especially under component subdirectories (`unet/`, `scheduler/`, etc.) and at the snapshot root.
Exploitation Scenario
An adversary registers a credible HuggingFace account, uploads a FLUX or Stable Diffusion variant with a polished model card and a standard-looking model_index.json declaring _class_name: FluxPipeline. The repo includes None.py defining a FluxPipeline subclass that silently writes a reverse shell or credential harvester on import. The attacker promotes the model via LinkedIn AI communities, GitHub Awesome lists, or SEO-targeted model cards. A data scientist runs DiffusionPipeline.from_pretrained('attacker/flux-finetuned') — the exact same invocation used for any legitimate model. None.py executes immediately with the user's privileges, exfiltrating HUGGINGFACE_TOKEN, AWS credentials from ~/.aws/, and SSH keys before establishing a persistent cron-based backdoor, while the pipeline returns normally and produces valid images.
Weaknesses (CWE)
CWE-94 — Improper Control of Generation of Code ('Code Injection'): The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
- [Architecture and Design] Refactor your program so that you do not have to dynamically generate code.
- [Architecture and Design] Run your code in a "jail" or similar sandbox environment that enforces strict boundaries between the process and the operating system. This may effectively restrict which code can be executed by your product. Examples include the Unix chroot jail and AppArmor. In general, managed code may provide some protection. This may not be a feasible solution, and it only limits the impact to the operating system; the rest of your application may still be subject to compromise. Be careful to avoid CWE-243 and other weaknesses related to jails.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H References
Timeline
Related Vulnerabilities
CVE-2026-44513 8.8 diffusers: trust_remote_code bypass enables silent RCE
Same package: diffusers CVE-2026-45804 7.5 diffusers: TOCTOU race bypasses trust_remote_code, RCE
Same package: diffusers CVE-2024-2912 10.0 BentoML: RCE via insecure deserialization (CVSS 10)
Same attack type: Supply Chain CVE-2025-5120 10.0 smolagents: sandbox escape enables unauthenticated RCE
Same attack type: Supply Chain CVE-2023-3765 10.0 MLflow: path traversal allows arbitrary file read
Same attack type: Supply Chain