Pluto Security found 31 repos that run attacker code through trust_remote_code on Xinference and GPUStack hosts. A second wave got past Hugging Face's scanners.
Pluto Security has documented a campaign it calls Load/Mine: 31 malicious repositories on seven Hugging Face accounts, dressed up as small Qwen2 test checkpoints, that run attacker code the moment a victim loads them with trust_remote_code=True. The targets are GPU hosts running self-hosted inference servers, mainly Xinference and GPUStack. A second wave of 19 repositories appeared within days of the first being reported and was still live eight days after the report. Downloads of the campaign's repositories reached 29,818; across all linked models Pluto counts 44 repos and 32,376 downloads. Pluto stresses this counts downloads, not confirmed infections.
What happened
The first wave, published around 21-22 September 2026 by an account created in April, consisted of 12 repositories according to Pluto's text (its account table adds up to 14; we use the headline figure). Pluto reported them to Hugging Face and China's ModelScope, and both removed the accounts. New accounts followed. The flagship of the second wave, MENXIANGYU/qwen2-tiny, contained no model at all. Its downloads rose from 7,354 at the time of Pluto's report to 28,412 on 28 October, 97.7% of all second-wave downloads. Hugging Face has since disabled access to it, but BleepingComputer confirmed that a near-identical copy, htt0118/qwen2-tiny, uploaded on 25 October, was still live and carried the same miner and reverse shell.
Pluto assesses a financially motivated, Chinese-speaking operator, citing Simplified Chinese code comments, command-and-control servers on Alibaba Cloud and late-night activity. The main wallet had received roughly $5,000 in Pearl (PRL), per BleepingComputer. Pluto saw nine machines that appear to belong to a shared research GPU cluster start mining for the operator at once and stop about six minutes later, consistent with an administrator killing the jobs.
Downloads of the Load/Mine repositories, counting downloads rather than confirmed infections.
How it works
No poisoned weights or pickle exploit are involved. With trust_remote_code=True, Transformers imports the Python files named in a repository's config.json auto_map. Code at the top level of those modules executes before the model class is returned.
Pluto describes a three-phase payload. First, a foothold: a watchdog at /tmp/.cch/svc.py restarts the miner every 120 seconds and shell startup files are modified; some builds open reverse shells to 212.32.253.3 on port 443 or 9443. Second, the miner: a 40.6 MB UPX-packed binary (syscore.bin, with the UPX marker altered to PEK!) is run from memory via memfd_create/fexecve under the process name [kworker/u20:1] and unpacks to peakminer. It kills rival miners. Third, status reporting: receipts are written as base32-encoded model names in the local Xinference registry, which makes the registry itself an indicator. One repository, qa-whl-1021, carried a container-to-host escape script aimed at GPUStack's /var/lib/gpustack; it appeared 8.5 hours after Pluto's disclosure email. Another operator account targeted LLaMA-Factory deployments.
Why the scanners missed it
Hugging Face scans with ClamAV and a pickle import scanner, and partners Protect AI and JFrog check weight formats. Pluto's point is that none of them analyze what a plain .py file does. The second-wave flagship came back clean from every scanner that ran; JFrog's verdict was that the file was "not a machine-learning model."
What defenders should do
If anyone loaded one of these models with trust_remote_code enabled, treat the machine as compromised. Killing the miner does not remove the backdoors.
- Check for
/tmp/.cch/,svc.pyprocesses, a[kworker/u20:1]process with mining arguments, odd shell startup entries,/etc/cron.d/.sync-helper, unexpectedauthorized_keysentries, Xinference registrations with encoded names, and connections to212.32.253.3. Pluto's report has the full indicator list. - Isolate and preserve evidence before deleting anything. Rebuild from a trusted image if integrity is in doubt, and rotate every token, cloud credential and SSH key the process could reach.
- Leave
trust_remote_codeoff by default. Where a model genuinely needs custom code, read the files named inauto_mapfirst. - Pin the revision. The Transformers documentation recommends loading custom models from a specific commit hash so code that changes later is not pulled in:
model = AutoModel.from_pretrained("org/model", trust_remote_code=True, revision="<commit-sha>")
- Require authentication on the Xinference API and limit who can reach it. Run loaders as a low-privilege user in a container or VM without production secrets or cloud metadata access, and restrict outbound traffic so raw-IP connections and mining pools fail.
- Treat Hub scanning as no verdict on Python code. Pluto suggests static checks on
auto_mapfiles forsubprocess,socket,ctypesand raw-IP network calls; self-hosters can run the same check before admitting a model.
Sources
- Pluto Security, Load/Mine: https://pluto.security/blog/hugging-face-malware-campaign-load-mine/
- BleepingComputer: https://www.bleepingcomputer.com/news/security/hugging-face-malware-campaign-returns-to-hijack-gpus-for-crypto-mining/
- Transformers docs, loading models: https://huggingface.co/docs/transformers/en/models