Hugging Face “Load/Mine” campaign: fake Qwen2 test models ran miners and backdoors through trust_remote_code
31 fake Qwen2 test models on Hugging Face ran a GPU miner and backdoors when loaded with trust_remote_code. Check hosts for /tmp/.cch and rotate keys.
- Published
- Updated
- Ecosystem
- AI developer toolsOpen source
- Exploitation
- Yes. Pluto Security recorded about 32,376 downloads and watched mining workers connect to the attacker’s pool
What happened
Pluto Security published research on 29 October 2026 into 31 malicious Hugging Face repositories across seven accounts, in two waves. Linked repositories bring the total to 44 and about 32,376 recorded downloads. Downloads are an exposure signal, not a count of infections (Pluto Security).
- 21 to 23 September 2026: the first wave. Account
hhzz667publishedqwen2-shell,qwen2-tiny,qwen2-mini,qwen2-nano,qwen2-legacyandqwen2-final. Early builds planted an SSH key;qwen2-finalwas the first full fileless miner. - 12 October: account
dsxk4mr8tqpublished sixqwen2-tinyvariants in 2.5 hours. A ModelScope account,wssb123e, mirrored the earlier wave. - After takedown: Pluto reported the first wave and those accounts were removed. A second wave appeared within days from
MENXIANGYU,wjsbg,soumikmahato,r1r13andhtt0118. On 28 October,MENXIANGYU/qwen2-tinyalone had 28,412 downloads. - Escalation:
MENXIANGYU/qa-whl-1021carried a container-to-host escape script aimed at GPUStack. Pluto says it went up 8.5 hours after Pluto’s disclosure email.
The repositories posed as small Qwen2 test checkpoints. The flagship model.safetensors was 42 bytes and held no tensors (AICybr). Hugging Face’s ClamAV scan and partner scanners did not flag the flagship payload; one reported that modeling_evil2.py was “not a machine-learning model” (Pluto Security).
When we queried the Hugging Face API on 2 November 2026, MENXIANGYU/qwen2-tiny, wjsbg/qwen2-final, soumikmahato/qwen2-tiny-chat, r1r13/qwen-lora-text-v1 and MENXIANGYU/qa-whl-1021 were marked disabled. htt0118/qwen2-tiny was not (Hugging Face API).
How the payload works
All details from Pluto Security.
config.jsonusesauto_mapto point at a Python file in the repository, for examplemodeling_evil2.EvilModel. Withtrust_remote_code=True, Transformers imports that file, and its top-level code runs before the class is returned. Errors are swallowed, so the load can fail and the payload still runs.- It writes a watchdog to
/tmp/.cch/svc.pythat restarts the miner every 120 seconds, and adds a line that launches it to/root/.bashrc,/root/.profileand~/.bashrc. - It downloads
syscore.bin, a UPX-packed build of the peakminer miner, and runs it from memory throughmemfd_createunder the fake name[kworker/u20:1], with a fallback copy at/tmp/.cch/sync. The downloader ignoresHTTP_PROXYandHTTPS_PROXY. qwen2-tiny-5,-6and-9first open a reverse shell to212.32.253.3on port 443 or 9443.- It records progress as fake model registrations named
qwen2-tiny-<base32>orezv-<base32>in the local Xinference registry, and kills competing miners. - Wave one planted this SSH key in six places, including the
xinferenceuser’s home:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDYjYyvcEJW5ZVgsESyZE5uxqSwN2vmd5asrWICeHxu1 qx-5090-op. Wave one also used/etc/cron.d/.sync-helperand marker files/tmp/.qa_pwnedand~/qa_pwned.
Am I affected?
Find code and configs that pull these accounts or allow remote code:
grep -rnE '(MENXIANGYU|wjsbg|soumikmahato|r1r13|htt0118|hhzz667|dsxk4mr8tq|wssb123e)/' --include='*.py' --include='*.ipynb' --include='*.yaml' --include='*.yml' --include='*.json' .
grep -rn 'trust_remote_code\s*=\s*True' --include='*.py' --include='*.ipynb' .Check the local model cache for the accounts:
ls ~/.cache/huggingface/hub/ 2>/dev/null | grep -iE 'menxiangyu|wjsbg|soumikmahato|r1r13|htt0118|hhzz667|dsxk4mr8tq'Run Pluto’s read-only host checks on any GPU host, notebook server or runner that might have loaded one:
grep -c "svc.py" /root/.bashrc /root/.profile ~/.bashrc 2>/dev/null
ls -la /tmp/.cch/ /tmp/.cchx /tmp/.kitsd 2>/dev/null
grep -a "krxXJ2PKQW" /proc/*/cmdline 2>/dev/null
ss -tnp | grep -E "212\.32\.253\.3:(443|9443|8898)" 2>/dev/null
grep "qx-5090-op" /root/.ssh/authorized_keys ~/.ssh/authorized_keys /home/xinference/.ssh/authorized_keys 2>/dev/null
ls -la /etc/cron.d/.sync-helper /tmp/.qa_pwned ~/qa_pwned 2>/dev/nullkrxXJ2PKQW is the miner’s pool login. Also look for a [kworker/u20:1] process owned by a non-root user or a Python parent, and for qwen2-tiny- or ezv- entries you did not create in Xinference.
Search egress logs for 212.32.253.3, 38.55.133.49, 103.97.176.121, 173.249.209.96 and the mining pools kryptex.network, luckypool.io, herominers.com and hashvault.pro. Pluto lists the full set of hashes, hosts and wallets (Pluto Security).
What to do now
- Preserve evidence, then isolate. Pluto advises capturing processes, connections, logs, the model cache, startup files and Xinference registrations before deleting anything, and treating the host as compromised even if the model load failed or GPU use looks normal.
- Remove persistence. Shell startup lines that launch
svc.py,/tmp/.cch, cron entries, theqx-5090-opkey inauthorized_keys, and rogue Xinference registrations. Rebuild from a trusted image if you cannot establish integrity. - Rotate credentials the loading process could reach: tokens, cloud credentials, passwords and SSH keys on that host. On a model host that usually includes the Hugging Face token (Pluto Security).
- Lock down Xinference. Pluto recommends requiring authentication on the Xinference API.
- Stop loading remote code by default. Read the
.pyfile named inauto_mapbefore settingtrust_remote_code=True, and pin a reviewed commit withrevision=so a repository cannot change under you (AICybr). Flagsubprocess,socket,ctypes,memfd_createand raw IP addresses in those files.
Why this matters
A model scan is not a code scan. The weights file was empty and harmless. The malware lived in an ordinary Python module that model scanners treat as out of scope, and trust_remote_code=True runs it on import. Takedowns also did not end it: a second set of accounts was up within days.