Skip to content

Diffusers has a `trust_remote_code` bypass via `custom_pipeline` and local custom components

High severity GitHub Reviewed Published May 1, 2026 in huggingface/diffusers • Updated Jul 16, 2026
Withdrawn This advisory was withdrawn on May 7, 2026

Package

pip diffusers (pip)

Affected versions

< 0.38.0

Patched versions

0.38.0

Description

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:

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:

# 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:

# 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:

# 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:

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.

All the attacker needs the victim to run is:

from diffusers import DiffusionPipeline

pipeline = DiffusionPipeline.from_pretrained('ido-shani/none-py-trust-remote-code-bypass')

PoC

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. All users on versions < 0.38.0 should upgrade:

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.

Why this should have a dedicated CVE

GHSA-j7w6-vpvq-j3gm is a distinct defect from CVE-2026-44513. CVE-2026-44513 is a misplaced-security-gate bug requiring a user-supplied custom_pipeline argument or a config entry declaring custom code. GHSA-j7w6 is a string-formatting bug where the default custom_pipeline=None is interpolated into the filename None.py, allowing silent RCE on a fully default from_pretrained('repo') call with no kwargs and a model_index.json that shadows a legitimate class. The root cause root cause and trigger are different, although the fix applied to address CVE-2026-44513 also addresses this vulnerability.

References

@DN6 DN6 published to huggingface/diffusers May 1, 2026
Published to the GitHub Advisory Database May 7, 2026
Reviewed May 7, 2026
Withdrawn May 7, 2026
Published by the National Vulnerability Database May 14, 2026
Last updated Jul 16, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

EPSS score

Exploit Prediction Scoring System (EPSS)

This score estimates the probability of this vulnerability being exploited within the next 30 days. Data provided by FIRST.
(43rd percentile)

Weaknesses

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. Learn more on MITRE.

CVE ID

CVE-2026-44827

GHSA ID

GHSA-j7w6-vpvq-j3gm

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.