This is a booby-trapped package on the loading dock, not a burglar kicking in your front door
CVE-2026-4372 is a model-loading RCE in huggingface/transformers where a malicious model config.json can smuggle an internal field such as _attn_implementation_internal that points model loading toward attacker-controlled code. NVD currently says all versions before 5.3.0 are affected, while multiple secondary reports narrow practical exploitability to the newer 4.56.0 through 5.2.x line and sometimes mention the optional kernels path; absent a vendor advisory clarifying the scope, treat <5.3.0 as the safe operational boundary.
The vendor's 7.8 HIGH is directionally fair on raw impact but too generous on reachable population. This is not an unauthenticated network service bug; the attacker needs a victim workflow that fetches or opens an untrusted model, which means exposure is concentrated in ML workstations, CI jobs, GPU servers, and automated evaluation pipelines rather than your whole estate. The bypass of trust_remote_code=False is the amplifier, but the need to load hostile content is still the main friction, so this lands as MEDIUM for broad enterprise patch triage.
4 steps from start to impact.
Seed a malicious model repo on the Hub
config.json carries a hostile internal kernel-selection value such as _attn_implementation_internal. The weaponized tool here is the Hugging Face Hub itself as the distribution channel, with the payload hidden in a normal-looking model artifact rather than a standalone executable.- Attacker can publish or socially engineer use of a model repository
- Victim organization allows staff or automation to pull models from public or weakly governed sources
- Private allowlists of trusted publishers cut this off early
- Many enterprises do not let production jobs pull arbitrary public models
- The attacker still needs visibility or lure value so someone actually chooses the model
config.json for suspicious internal fields.Victim loads the model in a normal workflow
AutoModelForCausalLM.from_pretrained() against the malicious repo. The important operational twist is that this can occur during routine benchmarking, fine-tuning, notebook experimentation, or automated model evaluation, so the exploit rides on expected developer behavior.- A vulnerable
transformersversion is installed - A model-loading workflow fetches the attacker-controlled repo
- User interaction or pipeline execution occurs
- Requires a real model-load event, not just package presence
- Some environments pin models internally and never hit public Hub content
- Air-gapped or mirrored environments reduce spontaneous exposure
Internal field bypasses expected safety assumptions
a7f8e7f. The weaponized component is src/transformers/integrations/hub_kernels.py, where the fix explicitly limits acceptable kernel repos and blocks deserialization of problematic internal fields.- The runtime honors the malicious internal config field
- The vulnerable integration path is reachable in the installed build
- If the runtime never reaches kernel-loading logic, practical exploitability may be lower
- Reports disagree on whether older versions or only newer branches are truly weaponizable
_attn_implementation_internal or _experts_implementation_internal in cached Hub configs add better signal.Arbitrary code runs with the loader's OS privileges
- Successful code retrieval and execution from the malicious repo
- Victim process has access to secrets or adjacent infrastructure
- Least-privilege service accounts and ephemeral runners can contain impact
- Container sandboxes and egress controls can limit follow-on actions
- This does not automatically grant enterprise-wide control
config.json files for _attn_implementation_internal and _experts_implementation_internal to find likely exposure or tampering. There is no mitigation SLA — go straight to the 365-day remediation window, but do the scan early because it can uncover already-seeded artifacts.trust_remote_code=Falseby itself does not solve this; the vulnerability explicitly abuses a path that bypasses that safety expectation.- Perimeter network scanning does not help much because this is not mainly an internet-exposed service fingerprinting problem.
- Patching only developer laptops while leaving CI runners and GPU workers untouched misses the systems most likely to auto-load models at scale.
The supporting signals.
| In-the-wild status | No confirmed in-the-wild exploitation found in the sources reviewed, and the user-provided intel says KEV listed: No. |
|---|---|
| KEV status | Not present in CISA KEV as of the reviewed catalog page; this matches the user intel. |
| PoC / exploit path | A public exploit path is effectively embedded in the advisory narrative itself: malicious config.json plus AutoModelForCausalLM.from_pretrained(). NVD references a Huntr advisory and the patch commit; that is enough for competent operators to reproduce quickly. |
| EPSS | 0.00089 from the user intel, which is very low and consistent with a niche, workflow-dependent package exploit rather than mass internet exploitation. |
| CVSS vector reality check | CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H correctly signals user interaction and a non-network attack path. In plain English: devastating once triggered, but not directly wormable over the internet. |
| Affected versions | NVD says all versions before 5.3.0. Secondary reporting suggests the weaponized path may be concentrated in 4.56.0 through 5.2.x and in some writeups only when the optional kernels path is present; treat that narrower range as an inference from secondary sources, not the authoritative floor. |
| Fixed version | 5.3.0 according to NVD and multiple downstream databases. The referenced patch commit is a7f8e7ff37d87d1a1a0c8cf607971c607741452f. |
| Exposure / scanning reality | Shodan/Censys/FOFA/GreyNoise are poor lenses here because this is a client/library flaw, not an internet-exposed daemon with a banner to fingerprint. Exposure has to be measured through asset inventory, SBOM, package managers, containers, notebooks, and CI runners. |
| Disclosure timeline | User intel gives 2026-05-24 disclosure. NVD change history shows the CVE was received from huntr.dev on 2026-05-24 and NIST added enrichment on 2026-06-04. |
| Researcher / reporting org | The CNA on NVD is huntr.dev. A precise individual researcher name was not recoverable from the browsed Huntr page rendering. |
Sources.
Why this verdict
- Downgrade for attacker position: this is not unauthenticated remote against a listening service; it requires a victim model-load event, which implies prior lure success or a permissive automation pipeline.
- Downgrade for exposed population: only hosts that both run
transformersand ingest untrusted model repos are materially at risk. On a 10,000-host estate, that is usually a small ML subset, not the fleet. - Upgrade from LOW because trust assumptions are broken: the bypass of
trust_remote_code=Falsemeans normal defensive habits around 'safe model loading' are not sufficient, so impacted ML platforms deserve real urgency. - Downgrade for current threat intel: no KEV listing, no confirmed active exploitation, and very low EPSS mean the external exploitation pressure is currently weak.
- Keep it above backlog noise because blast radius is rich: the vulnerable hosts are often GPU servers, CI runners, or data-science workstations with secrets, cloud creds, source code, and proprietary datasets.
Why not higher?
It is not higher because the exploit chain is gated by user or pipeline behavior: somebody has to load a malicious model, and many enterprises already restrict public model intake. There is also no strong evidence yet of active exploitation or mass scanning pressure, which matters when deciding what jumps the queue for an enterprise patch calendar.
Why not lower?
It is not lower because the end state is still arbitrary code execution on high-value AI infrastructure under routine documented usage. The trust_remote_code bypass removes a safety assumption many teams were relying on, so affected ML assets should not be treated as mere hygiene.
Crowdsourced verification payload.
Run this on the target host, container image, notebook base image, or CI runner that may have transformers installed. Invoke it with python3 verify_cve_2026_4372.py or point it at an alternate cache root with HF_HOME=/srv/hf-cache python3 verify_cve_2026_4372.py; no admin rights are required, but the script only inspects locations the current user can read.
#!/usr/bin/env python3
"""
verify_cve_2026_4372.py
Checks for vulnerable Hugging Face transformers versions and scans common
Hugging Face cache locations for suspicious internal config fields related to
CVE-2026-4372.
Outputs one of:
VULNERABLE - vulnerable version present and/or suspicious cached configs found
PATCHED - transformers installed at >= 5.3.0 and no suspicious configs found
UNKNOWN - transformers not installed, version unreadable, or no readable evidence
Exit codes:
0 = PATCHED
1 = VULNERABLE
2 = UNKNOWN
"""
import json
import os
import re
import sys
from pathlib import Path
TARGET_VERSION = (5, 3, 0)
SUSPICIOUS_KEYS = {"_attn_implementation_internal", "_experts_implementation_internal"}
def parse_version(v):
nums = re.findall(r"\d+", str(v))
if len(nums) < 3:
nums += ["0"] * (3 - len(nums))
try:
return tuple(int(x) for x in nums[:3])
except Exception:
return None
def get_transformers_version():
try:
try:
from importlib.metadata import version, PackageNotFoundError
except ImportError:
from importlib_metadata import version, PackageNotFoundError # type: ignore
try:
return version("transformers")
except PackageNotFoundError:
return None
except Exception:
return None
def candidate_cache_roots():
roots = []
env_hf_home = os.environ.get("HF_HOME")
env_hub_cache = os.environ.get("HUGGINGFACE_HUB_CACHE")
home = Path.home()
if env_hub_cache:
roots.append(Path(env_hub_cache))
if env_hf_home:
roots.append(Path(env_hf_home) / "hub")
roots.extend([
home / ".cache" / "huggingface" / "hub",
home / ".cache" / "huggingface",
Path("/root/.cache/huggingface/hub"),
Path("/opt/huggingface/hub"),
Path("/var/cache/huggingface/hub"),
])
# de-duplicate while preserving order
seen = set()
out = []
for r in roots:
s = str(r)
if s not in seen:
seen.add(s)
out.append(r)
return out
def scan_for_suspicious_configs():
hits = []
scanned_files = 0
for root in candidate_cache_roots():
if not root.exists():
continue
for path in root.rglob("config.json"):
try:
scanned_files += 1
with path.open("r", encoding="utf-8", errors="ignore") as f:
data = json.load(f)
bad = sorted(SUSPICIOUS_KEYS.intersection(set(data.keys())))
if bad:
hits.append({"path": str(path), "keys": bad})
except Exception:
continue
return scanned_files, hits
def main():
version_str = get_transformers_version()
version_tuple = parse_version(version_str) if version_str else None
scanned_files, hits = scan_for_suspicious_configs()
print("=== CVE-2026-4372 verification ===")
print(f"transformers_version={version_str}")
print(f"scanned_config_files={scanned_files}")
print(f"suspicious_config_hits={len(hits)}")
for hit in hits[:20]:
print(f"hit={hit['path']} keys={','.join(hit['keys'])}")
if len(hits) > 20:
print(f"note=truncated_output total_hits={len(hits)}")
# Decision logic
if version_tuple is None and scanned_files == 0:
print("UNKNOWN")
return 2
if version_tuple is not None and version_tuple < TARGET_VERSION:
print("VULNERABLE")
return 1
if hits:
# Even if patched now, suspicious cached artifacts suggest prior exposure risk.
print("VULNERABLE")
return 1
if version_tuple is not None and version_tuple >= TARGET_VERSION:
print("PATCHED")
return 0
print("UNKNOWN")
return 2
if __name__ == "__main__":
sys.exit(main())