A time-of-check/time-of-use race condition in HuggingFace diffusers allows an attacker-controlled Hub repository to silently execute arbitrary code during a routine DiffusionPipeline.from_pretrained() call, completely circumventing the trust_remote_code security guard. The attack exploits the ~0.5-second window between two independent Hub API calls: the trust check reads a clean model_index.json (commit A), but snapshot_download immediately after can silently fetch a malicious commit B containing a custom pipeline.py — no warning is shown and the pipeline loads and functions normally. With 393 downstream packages depending on diffusers and the library being central to generative AI workloads, any team running unversioned model loads in CI/CD, automated training, or inference pipelines is exposed; an attacker doesn't need per-victim timing precision against a popular repo, only statistical success across many concurrent downloads. Patch to diffusers >= 0.38.0 immediately, and as a compensating control pin all from_pretrained() calls with revision=<commit_sha> to eliminate the split-fetch window entirely.
What is the risk?
High risk for organizations running automated diffusion model pipelines that load from HuggingFace Hub without pinned commit revisions. CVSS 7.5 reflects AC:H for the timing requirement, but the statistical exploitation model against high-traffic repositories makes this operationally realistic — an attacker cycling repository state every few seconds against a popular model achieves meaningful hit rates at scale without targeting individual victims. The RCE is silent: the pipeline returns fully functional, making detection without process-level monitoring or egress filtering extremely difficult post-exploitation. OpenSSF Scorecard of 4.3/10 signals broader security hygiene concerns in the package. Risk is substantially lower for deployments using pinned revision hashes or loading from local directories, both of which close the race window entirely.
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 diffusers to >= 0.38.0 which resolves the race condition.
-
Immediate workaround: Pin all from_pretrained() calls with revision='<specific_commit_sha>' — both Hub calls resolve to identical content, eliminating the race window entirely.
-
Offline loading: Use local_files_only=True after a verified baseline download, or load from local directory paths to avoid Hub calls altogether.
-
Audit: Search codebases and CI/CD configs for DiffusionPipeline.from_pretrained() calls missing a revision= parameter; treat any dynamic Hub load as high-risk until patched.
-
Detection: Monitor for unexpected Python imports or outbound network connections spawned during model loading; alert on pipeline.py or similarly named files appearing in HuggingFace cache directories (~/.cache/huggingface/) post-download.
-
Supply chain hygiene: Enforce a policy of loading models only from organization-controlled forks with locked revisions for production workloads.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-45804?
A time-of-check/time-of-use race condition in HuggingFace diffusers allows an attacker-controlled Hub repository to silently execute arbitrary code during a routine DiffusionPipeline.from_pretrained() call, completely circumventing the trust_remote_code security guard. The attack exploits the ~0.5-second window between two independent Hub API calls: the trust check reads a clean model_index.json (commit A), but snapshot_download immediately after can silently fetch a malicious commit B containing a custom pipeline.py — no warning is shown and the pipeline loads and functions normally. With 393 downstream packages depending on diffusers and the library being central to generative AI workloads, any team running unversioned model loads in CI/CD, automated training, or inference pipelines is exposed; an attacker doesn't need per-victim timing precision against a popular repo, only statistical success across many concurrent downloads. Patch to diffusers >= 0.38.0 immediately, and as a compensating control pin all from_pretrained() calls with revision=<commit_sha> to eliminate the split-fetch window entirely.
Is CVE-2026-45804 actively exploited?
No confirmed active exploitation of CVE-2026-45804 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-45804?
1. Patch: Upgrade diffusers to >= 0.38.0 which resolves the race condition. 2. Immediate workaround: Pin all from_pretrained() calls with revision='<specific_commit_sha>' — both Hub calls resolve to identical content, eliminating the race window entirely. 3. Offline loading: Use local_files_only=True after a verified baseline download, or load from local directory paths to avoid Hub calls altogether. 4. Audit: Search codebases and CI/CD configs for DiffusionPipeline.from_pretrained() calls missing a revision= parameter; treat any dynamic Hub load as high-risk until patched. 5. Detection: Monitor for unexpected Python imports or outbound network connections spawned during model loading; alert on pipeline.py or similarly named files appearing in HuggingFace cache directories (~/.cache/huggingface/) post-download. 6. Supply chain hygiene: Enforce a policy of loading models only from organization-controlled forks with locked revisions for production workloads.
What systems are affected by CVE-2026-45804?
This vulnerability affects the following AI/ML architecture patterns: Diffusion model inference pipelines, Generative AI training pipelines, MLOps CI/CD automation, Model evaluation and benchmarking workflows, HuggingFace Hub-integrated services.
What is the CVSS score for CVE-2026-45804?
CVE-2026-45804 has a CVSS v3.1 base score of 7.5 (HIGH). The EPSS exploitation probability is 0.05%.
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0010.001 AI Software AML.T0010.003 Model AML.T0011.000 Unsafe AI Artifacts AML.T0079 Stage Capabilities AML.T0109 AI Supply Chain Rug Pull Compliance Controls Affected
What are the technical details?
Original Advisory
## Background This vulnerability is found in the `diffusers` package - the `transformers`-equivalent library for diffusion models. It is found in the `DiffusionPipeline.from_pretrained` flow, which is used to load a pipeline from the HuggingFace Hub. This function has a `trust_remote_code` guard: if the repository’s `model_index.json` references a custom pipeline class defined in a `.py` file in the repo, the load is blocked unless `trust_remote_code=True` is explicitly passed: ``` ValueError: The repository for attacker/repo contains custom code in pipeline.py which must be executed to correctly load the model. You can inspect the repository content at https://hf.co/attacker/repo/blob/main/pipeline.py. Please pass the argument `trust_remote_code=True` to allow custom code to be run. ``` The vulnerability allows arbitrary code execution through the custom pipeline flow from a Hub repo, with no `custom_pipeline` or `trust_remote_code` kwargs passed. The `from_pretrained` call succeeds and returns a functional pipeline. --- ## Naive Flow `DiffusionPipeline.from_pretrained` begins by popping all relevant arguments from `kwargs` into local variables, then calls `DiffusionPipeline.download()` to fetch the repo files: ```python # pipeline_utils.py:853 cached_folder = cls.download( pretrained_model_name_or_path, ... custom_pipeline=custom_pipeline, trust_remote_code=trust_remote_code, ... ) ``` Inside `download()`, `model_index.json` is fetched first as a standalone file via `hf_hub_download`: ```python # pipeline_utils.py:1636 config_file = hf_hub_download( pretrained_model_name, cls.config_name, ... ) config_dict = cls._dict_from_json_file(config_file) ``` This config is used to detect custom pipeline code and enforce the trust check: ```python # pipeline_utils.py:1672 if custom_pipeline is None and isinstance(config_dict["_class_name"], (list, tuple)): custom_pipeline = config_dict["_class_name"][0] 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(...) ``` After the check passes, `snapshot_download` then fetches all files and saves them to disk: ```python # pipeline_utils.py:1778 cached_folder = snapshot_download( pretrained_model_name, ... revision=revision, allow_patterns=allow_patterns, ... ) ``` Back in `from_pretrained`, the config is read a second time from the downloaded snapshot, and`_resolve_custom_pipeline_and_cls` reads the config to re-check if custom code needs to be loaded: ```python # pipeline_loading_utils.py: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 ``` If the config points to a `.py` file, it is imported. --- ## The Vulnerability `hf_hub_download` and `snapshot_download` are two independent HTTP calls to the Hub, both resolving the repository’s default branch (if `revision=None`) to its current HEAD at call time. There is no atomicity guarantee between them - if the repository is updated between the two calls, they will resolve to different commits and download different content, with no warning displayed to the user. The trust check in `download()` operates on the content fetched by `hf_hub_download` (commit A). The `snapshot_download` call that immediately follows can silently fetch a newer commit (commit B). The config in the newer commit will be the one parsed by `_resolve_custom_pipeline_and_cls`. **Therefore, it’s possible to introduce remote code into the repo between the two calls, bypassing the trust check.** The race window is everything between the two Hub calls inside `download()`: ```python # pipeline_utils.py:1636 config_file = hf_hub_download(...) # ← sees commit A, trust check passes # ... filenames processing, pattern building, pipeline_is_cached check ... # ~~~ ATTACKER PUSHES COMMIT B HERE ~~~ # pipeline_utils.py:1778 cached_folder = snapshot_download(...) # ← sees commit B, downloads pipeline.py ``` For the exploit, commit A carries a clean config with `_class_name` as a plain string, which causes `load_pipe_from_hub` to be `False` and the trust check to pass. Commit B changes `_class_name` to a list and adds `pipeline.py`: **Commit A - `model_index.json`:** ```json { "_class_name": "FluxPipeline", "_diffusers_version": "0.31.0" } ``` **Commit B - `model_index.json`:** ```json { "_class_name": ["pipeline", "FluxPipeline"], "_diffusers_version": "0.31.0" } ``` When `from_pretrained` reads the snapshot after `download()` returns, `config["_class_name"]` is now a list, `pipeline.py` exists on disk (fetched by `snapshot_download`), and `_resolve_custom_pipeline_and_cls` resolves `custom_pipeline` to the local path of that file. `_get_pipeline_class` then imports it - with no trust check at this point in the code. --- ## PoC 1. Create a Hub repo with commit A’s `model_index.json` (plain string `_class_name`). 2. Run `DiffusionPipeline.from_pretrained("attacker/repo")` with a breakpoint set at `pipeline_utils.py:1778` (the `snapshot_download` call). This is for the window to be large enough to manually respond to it. 3. When execution pauses at the breakpoint, push commit B: update `model_index.json` to use a list `_class_name` and add `pipeline.py`. 4. Resume execution. 5. `snapshot_download` fetches commit B; `/tmp/pwned` is written during the subsequent `_get_pipeline_class` call. --- ## Constraints - Does not apply when `revision` is pinned to a specific commit hash - both Hub calls resolve to the same content. - Does not apply when loading from a local directory. - If all expected files are already present in the local HF cache, `download()` returns early before reaching `snapshot_download` (line 1767 early-return), closing the race window. The exploit therefore requires a first (or forced) download. --- ## Exploitability The window between the two calls is very short. Local testing resulted in a window of approximately ~0.5 seconds for the attacker to push the change. This is, of course, unfeasible to accomplish for each and every new download. However, given a popular repo with many downloads per day, one may achieve **statistical success** by changing the repo’s state every once in a while or every few seconds, with some percentage of downloaders falling on the exact window. --- ## 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. The `from_pretrained` call succeeds and returns a fully functional pipeline.
Exploitation Scenario
An adversary creates a convincing HuggingFace repository hosting a popular diffusion model (or compromises an existing one with significant traffic). They maintain commit A with a clean model_index.json where _class_name is a plain string — this ensures the trust check passes for all incoming downloads. They deploy automation that rapidly cycles the repository between commit A and a malicious commit B every few seconds: commit B changes _class_name to a list and adds a pipeline.py containing a reverse shell payload or credential harvester targeting cloud provider metadata endpoints. Against a repo receiving thousands of daily downloads, a statistically significant fraction of from_pretrained() calls hit the split-commit window: hf_hub_download resolves commit A (trust check passes), then snapshot_download resolves commit B (malicious pipeline.py downloaded). Victims' ML workers silently execute the payload — with no error, no warning, and a fully functional pipeline returned — giving the attacker persistent access to GPU clusters, training datasets, and cloud credentials before any detection occurs.
Weaknesses (CWE)
CWE-367 — Time-of-check Time-of-use (TOCTOU) Race Condition: The product checks the state of a resource before using that resource, but the resource's state can change between the check and the use in a way that invalidates the results of the check.
- [Implementation] The most basic advice for TOCTOU vulnerabilities is to not perform a check before the use. This does not resolve the underlying issue of the execution of a function on a resource whose state and identity cannot be assured, but it does help to limit the false sense of security given by the check.
- [Implementation] When the file being altered is owned by the current user and group, set the effective gid and uid to that of the current user and group when executing this statement.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:H/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 GHSA-j7w6-vpvq-j3gm 8.8 diffusers: silent RCE via None.py trust_remote_code bypass
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