The flag said no
On August 3, 2026, The Hacker News covered FaceHugger: three flaws in Hugging Face's Diffusers library — seven million downloads a month — that let a crafted model repository execute arbitrary code on the machine that loads it, even with trust_remote_code=False set. The root cause: the trust check ran at a different point from the code load. Coding agents pull models the way they pull packages, so the flag that lied was guarding developer laptops. Bromure Agentic Coding puts the boundary under the process instead of inside it: the load runs in a disposable VM, the credentials in reach are decoys, and the payload's call home dies at the host proxy — so the malicious repo costs the attacker their payload and costs you nothing.
The repository contained a file named None.py, and that was the whole
trick. The safety flag was set, the check ran, and the code ran anyway,
because the check and the load happened at two different moments — and
the attacker only had to be present at the second one.
On August 3, 2026, The Hacker News
picked up research that Zafran Labs published in late July:
three vulnerabilities in Hugging Face's Diffusers library, collectively
named FaceHugger, that let a crafted model repository execute arbitrary
Python code on any machine that loads it. Diffusers is the standard way to
run diffusion models — about seven million downloads a month, sitting in
production pipelines, CI jobs, notebooks, and container images. The library
has a safeguard for exactly this scenario: trust_remote_code, a flag that
is supposed to stop unreviewed code from a model repo from running on your
machine. All three flaws walk past it while it is set to False.
The check and the load were two different events
Loading a model from the Hub is not one operation. The library makes two
sequential HTTP requests: the first fetches the repository's configuration,
the second fetches the code and weights the configuration points to. The
trust_remote_code gate did its inspection during the first request. The
thing it was guarding against arrived in the second.
Zafran's researchers found three ways through that gap, now
CVE-tagged
and patched in Diffusers 0.38.0. In CVE-2026-44827 (CVSS 8.8), a
string-formatting quirk builds the filename None.py when no custom
pipeline is requested — and the existence check for that file runs on a
code path that doesn't flag it, so a repository that ships a file named
None.py passes the gate and executes on load. CVE-2026-45804 is a
race: about a third of a second separates the two requests, and a
repository that changes between them is validated as one thing and executed
as another. CVE-2026-44513 skips the gate on local snapshot loads.
Zafran's researchers compressed all three into one sentence: any method
that makes the loader see custom code that the gate did not, allows
bypassing.
The pattern has a history. Transformers — the other pillar of the Hugging
Face ecosystem — had its own trust_remote_code=False bypass
(CVE-2026-4372) exploitable through a standard model-loading call. The
bug class comes with the design: a security promise living inside the
process it polices. The flag is an argument to a function, while the
enforcement is scattered across code paths, filename formatting, and
request timing. When any of those disagree with the flag, the flag loses,
and nothing tells you it lost. The model loads and the demo works.
Your agent loads models on a Tuesday
Read that story from a coding agent's chair. "Pull the inpainting pipeline
off the Hub and benchmark it against ours" is an ordinary Tuesday task.
The project pins diffusers==0.37, because projects pin things. The agent
writes what a careful engineer would write —
from_pretrained(..., trust_remote_code=False) — and the careful version
is the vulnerable version. You can read a package's source before
installing it; a model repo is config, weights, and, thanks to the loader,
whatever the second HTTP request says it is.
Agents have made that load path hot. They fetch models the way they fetch packages, mid-task and by name, and attackers have already worked out that the recommendation itself can be poisoned. A model repo that detonates on load is the same play one registry over, with a better disguise: the payload sits in an artifact you file under data, not code.
Zafran's advice is to treat model repositories as untrusted code. Correct — and it has a prerequisite the report doesn't dwell on: a place where untrusted code can run without costing you anything. A policy of suspicion is a discipline you maintain until a deadline gets loud. A room built for detonation doesn't need your discipline.
The load happens in the box
Bromure Agentic Coding is that room, and it starts where FaceHugger's flag failed: the boundary sits underneath the process, in an environment the loader's internals can't argue with, enforced whether or not the process cooperates.
Walk the attack through a profile. Your agent takes the benchmark task,
pins and all, and calls the loader on the malicious repo. None.py
executes — inside a disposable Linux VM, one hypervisor away from macOS.
The machine the payload now owns is a machine that exists for this task,
holds this task's workspace, and gets discarded when the work is merged.
The payload's first real move is outbound. It has to be: fetch a second
stage, or ship what it found to its operator. Each of those requests
crosses the host proxy, where the destination is checked against the list
you approved for this profile. huggingface.co is on that list — that's
how the model arrived. The attacker's drop box is not. The wire refuses,
the way the gate should have —
except this refusal doesn't depend on the library's code paths agreeing
with each other. The proxy doesn't know or care which Python file is
making the request. It checks the destination, finds nothing approved,
and drops the connection.
Suppose the payload skips the network and goes shopping instead, through env vars and key files. It finds broker decoys. The real values live on the host and are injected at the wire, only for approved destinations — the passwords were never in the VM to steal. And if it tries to make its stay permanent — rewrite a config, switch something off — destructive shapes pause for a guardrail dialog on the host, and persistence in a machine with a lifespan of one task is a losing trade anyway.
Meanwhile the task succeeds. The benchmark runs, the numbers come back, you read the diff. The attacker spent a supply-chain position on a payload that executed in a room with nothing to steal and no way to call home. You spent nothing — not even attention, unless you enjoy reading proxy logs for sport.
Patch the library, keep the room
The fix note in Zafran's report says organizations should upgrade and then go hunting: applications, notebooks, inference services, and container images that pin older Diffusers releases. That hunt has no finish line — somewhere in your dependency tree, some project pins the version with the gap, and the next FaceHugger will be a different library's flag with a different bypass. The guard and the shell disagreed was this failure in a command allowlist; the sandbox was a sentence was this failure in a prompt. In each case a check lived inside the thing it was checking, and the thing won.
A profile's boundary doesn't share that fate, because it doesn't
participate in the process it contains. The VM doesn't parse pipeline
configs. The proxy doesn't have a None.py code path. Their one job —
this machine is disposable, this wire refuses unlisted destinations — is
the same job on the day a bypass is published as on the day it's patched.
A Sumo Logic CISO, commenting on this research with no Bromure in mind,
named the defenses that hold: "the unglamorous ones: egress control,
segmentation and credential hygiene."
We built the unglamorous ones into a profile you click.
Keep pulling models — that is where the leverage is, and your agent should be trying them for you. Stop letting a function argument be the only thing between the Hub and your laptop. Install Bromure Agentic Coding, give the agent a profile whose rules hold at the wire, and the next time a loader's promise breaks — and one will — the news story stays someone else's.