FaceHugger: When the Safety Gate Lives on the Wrong Line

August 4, 2026 · SPR{K}3 Research

Hugging Face's diffusers library — the reference codebase for the majority of enterprise Stable Diffusion, image-generation, and video-generation pipelines — spent the last three months shipping a trust_remote_code guard that could be walked around three different ways. Zafran Labs published the writeup last week under the name FaceHugger; The Hacker News picked it up on Aug 3, 2026. Three CVEs — CVE-2026-44827 (CVSS 8.8), CVE-2026-45804 (CVSS 7.5), CVE-2026-44513 (CVSS 8.8) — all fixed in diffusers 0.38.0 via PR #13448.

The interesting thing here is not the bugs. It is what they say about where a security control has to live to actually work.

What went wrong

trust_remote_code=False is the Hugging Face ecosystem's promise. The whole point is that a user who declines the flag — or simply omits it, which is the default — should not be running arbitrary Python from a model repository. That is the trust boundary.

FaceHugger is three variants of the same design error. The gate that enforces trust_remote_code was implemented inside DiffusionPipeline.download(). But the code that actually loads and executes a custom pipeline lives elsewhere — in get_cached_module_file — and there are several code paths that reach that loader without going through download():

A middle variant, CVE-2026-45804, is a race: modify the repository configuration between hf_hub_download and snapshot_download — two sequential HTTP calls — and the gate checks one file list while the loader executes another.

Zafran's framing, quoted by The Hacker News: "artifacts pulled from AI repositories are frequently treated as passive data, when configuration files, loaders, and custom pipeline code can quietly cross into executable code and turn a routine model load into an initial-access vector."

The load-is-run problem, again

We wrote about this class in Load Is the New Run. Same idea, different instance: a routine model load reaches a code-execution surface, and the safety flag meant to prevent it is enforced on a different call than the one that runs the code.

diffusers was downloaded 8.1 million times in July 2026 on pepy.tech alone. Any pipeline pinned to diffusers < 0.38.0 that pulls a user- or partner-supplied model reference — including code that trusts a custom_pipeline argument from configuration — is a live initial-access surface. The cross-repo variant of CVE-2026-44513 is the SolarWinds shape: trusted parent artifact, malicious dependency, gate applied to the parent's identity rather than the dependency's.

Why the fix location matters

The patch moves the trust_remote_code gate from DiffusionPipeline.download() into get_cached_module_file inside src/diffusers/utils/dynamic_modules_utils.py. The check now attaches to the moment of loading a dynamic module, not the moment of fetching bytes.

Every AI-library security control that lives at the "download" call — where an artifact enters the system — inherits the same hole. If any local, cached, or short-circuit path can reach the loader without going through download, the gate does nothing. The check has to live at the chokepoint where attacker-controlled bytes turn into running code.

This is not Hugging Face-specific. Guardrails set up at import time, registry time, agent-provisioning time — all reasonable-looking locations — sit one call short of the surface where execution actually happens. The trust_remote_code primitive is not the problem. Its placement was.

What a runtime watchdog would have seen

There is likely a fourth FaceHugger variant somewhere in diffusers, and equivalents in other libraries that expose a "trust remote code" gate. Short-term: upgrade to diffusers >= 0.38.0 and audit any custom_pipeline= argument that doesn't come from a repository you control.

The durable thing to watch is behavior. A call to DiffusionPipeline.from_pretrained that immediately spawns a subprocess, resolves an unexpected import chain, or reaches a credential store looks the same whether it came from CVE-2026-44513, CVE-2026-44827, or the next variant. Load-then-execute is a distinctive shape.

Two practical notes. Do not treat custom_pipeline= values as configuration; treat them as code references. Before calling from_pretrained on a local snapshot, look for .py files under component subdirectories — unet/, scheduler/ — that could be pulled in by a model_index.json you didn't write.

The takeaway

FaceHugger is a small design mistake with a wide blast radius. A safety control has to live at the exact call it is trying to protect. Every AI-library guardrail implemented one function short of the code-execution surface has the same failure waiting to be found — and the patch tempo, even for cooperative disclosures, is slower than the exploitation tempo.


SPR{K3 is a security research operation that pairs offensive vulnerability research with runtime behavioral defense. Defend is our runtime agent. To talk about a deployment, reach us at support@sprk3.com.