Skip to main content

A 42-byte weight file, 32,000 downloads, and a GPU cluster mining for someone else: the Load/Mine campaign on Hugging Face

Updated 15 min readMarkdown
Part of seriesNewsletters-eng
A 42-byte weight file, 32,000 downloads, and a GPU cluster mining for someone else: the Load/Mine campaign on Hugging Face

Summary

On October 29, 2026, Pluto Security published the Load/Mine campaign: 31 malicious model repositories across 7 Hugging Face accounts, totaling more than 32,000 downloads, installing a cryptominer that hijacks the GPU of anyone who loads the model.

The technique is not a vulnerability. It is trust_remote_code=True — a legitimate Transformers flag that lets a model repository ship its own Python code. That code runs the moment it is imported, before any weights load. The campaign's flagship repository shipped a model.safetensors file of 42 bytes containing no tensors at all: the entire value was in the Python.

Two details matter more than the malware itself:

  • After Hugging Face and ModelScope removed the accounts, 19 new repositories appeared within days.
  • One repository contained a container-to-host escape script aimed at GPUStack, posted 8.5 hours after Pluto sent its disclosure email. Priority actions:
  • Sweep every host running Xinference, GPUStack, vLLM or any self-hosted inference platform against the indicators at the end of this post — in particular the /tmp/.cch directory, a process masquerading as [kworker/u20:1], and unexpected lines in root's .bashrc / .profile.
  • Treat trust_remote_code=True as arbitrary code execution, not a compatibility option. If your pipelines set it by default, that is this week's work.

trust_remote_code=True: there is no CVE here

This part is worth understanding properly, because it is not a bug.

A Hugging Face model repository can declare an auto_map entry in config.json pointing at a Python module inside the repository. When a user calls from_pretrained(..., trust_remote_code=True), Transformers imports that module with the privileges of the calling process.

Python executes everything at module level on import. So if the payload sits at the top level, it runs before the weights load, before the model is instantiated, before anything machine-learning-related happens at all.

The practical consequence: the attacker needs no poisoned weights. The flagship repository MENXIANGYU/qwen2-tiny shipped a model.safetensors of 42 bytes with no tensors. It was never a model. It was a pretext for Python to import a file.

This is also why every scanner missed it, as covered below.

The payload: in-memory miner masquerading as a kernel thread

Load/Mine's payload chain is built to survive on Linux GPU servers.

The miner: a binary called peakminer (its source is public on GitHub), loaded and executed straight from memory via memfd_create, with an on-disk fallback. The process masquerades as [kworker/u20:1] — it looks like a real kernel worker thread, except the real command line still carries the worker ID.

The binary is UPX-packed, but its markers were altered so routine unpacking tools fail on it.

The watchdog: a script at /tmp/.cch/svc.py restarts the miner every 120 seconds if it dies, and is itself re-triggered from the shell's startup files — one login session is enough to bring it back.

Persistence:

Wave 1:   plants an SSH key (comment "qx-5090-op") into 6 authorized_keys paths
          cron entry at /etc/cron.d/.sync-helper
          miner installed into /root/.xinference/cache
Wave 2:   switches to outbound reverse shells (traverses NAT and inbound firewalls)
          hooks in /root/.bashrc, /root/.profile, ~/.bashrc

Clearing the field: the miner kills competing miner processes and removes rival model registrations — a sign the group knows that forgotten GPU servers often already host someone else's miner.

Why Xinference and GPUStack are in this story

Neither platform is a victim in the sense of having a vulnerability. They are the target environment.

Xinference (Xorbits Inference) is an open-source platform for operating and integrating a wide range of AI models — LLMs, embeddings, reranking, image, audio and video — on-premises or in the cloud, with the ability to register custom model weights and turn them into an API.

GPUStack is a GPU cluster manager for high-performance model serving (vLLM, SGLang, TensorRT-LLM) and on-demand SSH-accessible GPU instances. It runs in Docker, manages multiple clusters across on-premises, Kubernetes and cloud, and targets teams delivering Model-as-a-Service.

In other words: exactly the kind of machine a cryptominer wants — many GPUs, running continuously, with few people watching.

Load/Mine abuses them in two different ways:

1. Xinference as a status board. Rather than build a separate C2 channel for reporting, the payload writes mining receipts into Xinference's own local model registry, as base32-encoded model names: qwen2-tiny-<base32>, ezv-<base32>, hlrce-<host>-*. Skimming the model list, they look like test builds.

2. GPUStack as the target of a container escape. The repository MENXIANGYU/qa-whl-1021 contains e.sh, run from inside a privileged container:

1. activate LVM volumes
2. mount candidate host disks under /mnt/qa_<pid>
3. look for the host root filesystem, or the /var/lib/gpustack directory
4. grant the attacker root login on the host

The script's output strings: QA escape, PLANTED <device> and ESCAPE_OK=.

This repository was posted 8.5 hours after Pluto sent its disclosure email, and was still live when the report was published.

The economics of the campaign

Pluto traced the first wave's money: the payout wallet received roughly 2,276 PRL across 716 transactions, which Pluto estimated at around $5,000, with live workers on Kryptex and luckypool.

Other wallets map to MoneroOcean, c3pool, herominers and hashvault. Pluto lists seven wallets in total, one of which is a decoy that was never used.

One observation stands out: a batch of workers joined a pool within 90 seconds — a pattern consistent with one hijacked shared GPU cluster rather than scattered individual infections.

$5,000 sounds small. But that is the attacker's revenue, not the victim's loss: electricity, wear, and above all the GPU capacity taken away from the organization that owns it — at a moment when GPU capacity is the scarcest resource any AI team has — costs considerably more than that figure.

Takedowns ended nothing

The campaign timeline is the most operationally instructive part of the report:

2026-04-29   First account (hhzz667) created, then dormant for months
09-21..10-12 Wave 1 published in stages, from a simple SSH backdoor to a fileless miner
             Pluto reports to Hugging Face and ModelScope
             Both platforms remove the accounts
+days        Wave 2: 19 new repositories, revised loaders, switch to reverse shells
+8.5 hours   qa-whl-1021 (container escape) posted, after the disclosure email
2026-10-28   Flagship qwen2-tiny reaches 28,412 downloads
2026-10-29   Pluto publishes

Per Red Hot Cyber, at the time of its reporting a copy on the htt0118 account was still accessible even though MENXIANGYU/qwen2-tiny had been blocked.

Why no scanner caught it

Not one wave-one repository was flagged. The wave-two flagship came back clean from both antivirus and Hugging Face's partner scanners.

Not because the scanners are weak, but because they are looking in the wrong place: these tools check signatures and model file formats — hunting malicious pickles, anomalous weights, known hashes. They do not analyze what the accompanying Python does.

In Load/Mine the model file is entirely harmless (42 bytes, empty). All behavior lives in a .py file that looks like ordinary modeling code. Per AI Cybr, both Protect AI and JFrog scanners returned clean.

This is a structural gap: the model ecosystem inspects the artifact, while the threat lives in the code shipped beside it.

SolanaCry: the same trust failure, down a different road

On the same day, October 29, 2026, AIR Security (Eliad Mualem, Dor Granat) published a separate campaign that belongs next to this one, because it exploits the same weakness from a different surface.

The chain:

1. A developer asks an AI coding agent to build a Solana project
2. The agent searches the web and picks github.com/SolanaUpdateTools/solana-starter-kit
   (per AIR, the top result for that query)
3. The README's only install step is a single PowerShell one-liner: iwr | iex
   -> downloads a payload and runs it in memory
4. The dropper writes 14 files to a hidden folder under %LOCALAPPDATA%:
   13 genuine, signed NetSupport Manager components and 1 attacker config
5. Adds a "SecureModule" Run key so the RAT starts at every login
6. Deletes the run history and the script itself

The payload is not freshly written malware but NetSupport Manager, a legitimate commercial remote-control product with fully signed binaries. The attacker's configuration hides all user-facing prompts and accepts operators from any IP address.

AIR links the campaign to the NetSupport cluster eSentire tracks as EVALUSION, based on a shared configuration value and an install-command pattern matching the October 2025 TradingView campaign — meaning the same operator moved from traders to developers.

What it shares with Load/Mine is not technique but trust model: in both cases an automated system fetches code from the internet and runs it with nobody reading that code. Transformers trusts auto_map. The agent trusts the README. Neither has a human in the middle.

Assessment

This is not a vulnerability, so there is no patch, and that is the problem. trust_remote_code exists for a legitimate reason: new model architectures arrive faster than Transformers releases, and without the mechanism the ecosystem would stall. Hugging Face can remove repositories, but it cannot patch a feature the community needs. The defense has to sit on the user's side.

Takedowns are close to useless when account creation is free. Pluto reported, both platforms removed the accounts, and 19 new repositories appeared days later. That the attacker published a container escape script after knowing they had been caught suggests they rate the risk of consequences at roughly zero. Under this model, the metric worth tracking is not "how many repos were removed" but "how long until they are back".

GPU servers are now a direct economic target, not merely a stepping stone. A compromised server used to be valuable because of the network it sat in. Now the hardware itself is the prize. That changes the risk profile of internal GPU clusters: they do not need to hold any sensitive data to be a first-rank target.

The 42-byte file is the sharpest commentary available on the state of model scanning. An entire industry is scanning model formats for malicious pickles while the attacker abandons the model file altogether and moves the payload into a .py sitting next to it. Until scanners analyze the behavior of the accompanying code, a "clean" verdict on a model repository only means "nothing odd in the binary".

Writing status into Xinference's model registry is an elegant choice. It creates no unusual network connection, logs nothing anywhere, and blends into exactly the place where an inference system is expected to accumulate junk entries. It is living-off-the-victim's-infrastructure applied to a new layer of infrastructure.

These two campaigns are the same lesson at two different layers. Load/Mine exploits an ML library running a repository's code; SolanaCry exploits an AI agent running the commands in a repository's README. Both show that the chain "search the internet → download → execute" is now fully automated in AI development environments, and that the human review step disappeared without anyone deciding to remove it.

Relevance for Vietnam

  • Neither report names Vietnam among the victims. Pluto published no geographic breakdown, and neither did AIR. But this is not a campaign that selects by country — it selects by whether the machine has a GPU.
  • The number of internal AI teams in Vietnam running Xinference, GPUStack, vLLM or Ollama on dedicated GPU servers is growing fast, and most of those sit on internal networks with no EDR and no egress monitoring. That is close to an ideal environment for Load/Mine.
  • trust_remote_code=True gets set almost reflexively in internal notebooks and scripts, because many models will not load without it. Very few teams treat that line as a security decision.
  • Stolen GPU cycles are usually misread as a performance problem. The symptoms are unexpectedly slow training jobs and high utilization when nobody is running anything — things an AI team will blame on drivers or a colleague who forgot to kill a process, rather than report to the SOC.
  • AI coding agents are in wide use across Vietnamese dev teams, and SolanaCry shows that one fake repository ranking first is enough. A single iwr | iex line in a README will be executed by the agent before anyone looks at it.
  • NetSupport Manager is legitimate, signed software, so many EDR configurations will let it through. If your organization does not use NetSupport, block it by filename and hash rather than relying on an AV verdict.

Recommendations

For AI infrastructure teams

  • Sweep inference hosts now against the indicators below: the /tmp/.cch, /tmp/.cchx and /tmp/.kitsd directories, a [kworker/u20:1] or peakminer process, unexpected hooks in /root/.bashrc and /root/.profile, the /etc/cron.d/.sync-helper cron entry, and unfamiliar keys in authorized_keys.
  • Inspect the Xinference model registry for entries shaped like qwen2-tiny-<base32 string>, ezv-, hlrce-, hlcmd-, hlout-. This is a cheap and highly specific indicator.
  • Disable trust_remote_code by default in every pipeline, and enable it per model only after someone has read the accompanying Python. Where that is operationally impossible, load models inside a container with no privileges, no host disk mounts and no internet egress.
  • Do not run inference containers in privileged mode. The campaign's e.sh only works from inside a privileged container. This single control removes that attack path entirely.
  • Monitor egress from GPU clusters. Connections to mining pools (Kryptex, luckypool, MoneroOcean, c3pool, herominers, hashvault) are a low-noise signal, and better still if policy lets you block them.
  • Require authentication on Xinference and GPUStack APIs, and move them out of any network segment everyone can reach. An unauthenticated inference API on an internal network is a public API to anyone already inside.
  • Treat GPU utilization as a security metric, not only an operational one. Sustained high usage outside working hours that matches no scheduled job is worth investigating.

For development teams using AI agents

  • Do not let agents run download-and-execute commands (iwr | iex, curl | bash) without human review. This belongs in the agent's configuration, not only in a policy document.
  • Treat everything an agent retrieves from the internet as untrusted data, including the top search result. Search ranking is not a trust signal, and SolanaCry shows it can be manipulated.
  • Vet repositories with cheap checks: recently created, few commits, no history, and install instructions consisting of a single script from an unfamiliar domain.
  • If you suspect SolanaCry: look for a hidden folder under %LOCALAPPDATA%, the SecureModule Run key, a client32.exe process, and connections to maxwintodaybtc.com:443. On a hit, isolate the machine, remove the folder and Run key, then rotate every credential, token and wallet key that machine could reach.

IOCs

From the Indicators sections of the Pluto Security and AIR Security reports. These are the groups that are directly usable for hunting; the exhaustive list is in the primary reports.

Load/Mine — accounts

Wave 2 (Hugging Face):
  MENXIANGYU    # 13 repos, including qwen2-tiny, qwen2-mini-c4/c5, zz_ext, qa-whl-1021
  wjsbg         # eagle-tiny, qwen2-tiny-zh21, qwen2-final, zh-assets (payload host)
  htt0118       # qwen2-tiny, created Oct 25
  soumikmahato  # qwen2-tiny-chat, behind 4 cover repositories
  r1r13         # qwen-lora-text-v1
 
Wave 1 (removed):
  hhzz667, dsxk4mr8tq        # Hugging Face
  wssb123e                   # ModelScope
 
Linked accounts (shared technique, ownership not proven):
  MONIADF-2, antwoinhjro654, vaishupv, andrew3d, oifewuheu, KzuryHD

Load/Mine — wallets and mining pools

Wallets:
  prl1p9s2a9gdy4n5yfuf5suhya2d2pyvaahmz3n3gzm3npyxkj79m7yyq9kgnrv   # wave 1 payout
  prl1pek8rh8yjck5q7596aph7ua7xnkqs8ur9mkfmz8w7xfzcdfcfx5vsx2f4lz   # soumikmahato
  prl1phz0aglq4hg2jpqxrqgvycw798ruzlxjztfty6pmt3yr6y2f8lw0s4uppg2   # antwoinhjro654
  prl1p0aaff83xc3cvs6f95jj9pwx2uskez9hmrjd2ap2p3mj0nmkl3hfqua63wf   # decoy, unused
  83sB24CNcALNWZJrWb7zEnY6ZAtneXwfKAWyedqocXrd9cgzrLDcSdEch9xpHu6ZuqUdAh8VEMA15Zy6W5xMfZfPG4whgZj   # Monero, wave 1
  46i5xDQq56pCJW6vg1kpPwLxmLLNzFB8vHZk58twfKj7VRGZDucSHYY8TYSoZDW6PpTcMEP4UdjWrPhrQdDFNaUDVPekNcc   # Monero, MONIADF-2
 
Shared wave-2 pool login: krxXJ2PKQW.worker
 
Pools:
  prl-us.kryptex.network:8048 / prl-sg.kryptex.network:8048   (wave 2, SSL)
  prl-us.kryptex.network:7048 / prl-eu.kryptex.network:7048
  pearl-hk.luckypool.io:3361, :3956                           (wave 1)
  pearl-sg1.luckypool.io:3360
  de.pearl.herominers.com:1200
  pool.hashvault.pro:80, :443
  gulf.moneroocean.stream:10001
  mine.c3pool.com:80
  103.97.176.121:3361 / 178.33.123.26:3361, :3362

Load/Mine — network infrastructure

212.32.253.3      # reverse shells on 443, 9443; gated stage 2 on 8898; collector on 8895
38.55.133.49      # kitsd_v12.sh on 8901; recon exfil and reverse shell on 8901, 9001
8.137.187.63      # wave 1 C2 (Alibaba Cloud): 80, 443, 50053, 50054
103.97.176.121    # g20_reclaim.py on 8899; miner payload on 8090
2.28.9.199:7000   # GPU and metadata exfil (r1r13)
173.249.209.96    # MONIADF-2 agent (8081), hl C2 (8000), XMRig download (8001)
212.132.98.170    # gsocket backdoor host
49.12.44.127, 200.58.80.115    # gs-netcat mirrors
 
hz2006.aihzcc.top
3.112.174.116.sslip.io                              # GPU miner + CUDA runtime (sysd-kit.tgz)
grows-stocks-vatican-flood.trycloudflare.com        # status beacon (soumikmahato)
github.com/peakminer/peakminer/releases             # miner source

Load/Mine — files, paths, processes

/tmp/.cch/{sync,svc.py,o}         # miner, watchdog, log
/tmp/.cchx, /tmp/.kitsd           # additional drop directories
/root/.xinference/cache           # wave 1 miner install location
/etc/cron.d/.sync-helper          # wave 1 cron
/tmp/lf_py.py, /tmp/ezv_proof.txt
/tmp/.qa_pwned, ~/qa_pwned        # wave 1 infection markers
/mnt/qa_<pid>                     # qa-whl-1021 host disk mount points
Hooks in /root/.bashrc, /root/.profile, ~/.bashrc
SSH key comment: qx-5090-op       # planted in 6 authorized_keys paths
 
Processes: [kworker/u20:1]   (fake kernel-thread name), peakminer
Xinference registry entries: qwen2-tiny-<base32>, ezv-<base32>,
                             hlrce-<host>-*, hlcmd-*, hlout-*
Container escape output strings: "QA escape, PLANTED <device>", "ESCAPE_OK="

Load/Mine — SHA-256 hashes

88f4b95a1c3d0a0d9638dc6e69f5bbc9ce0e1b92d069a0e404f60fa5397ee020   syscore.bin (wave 2, 40,625,780 bytes)
587e3c3be3c75eb6586d69b25adb4c4208c1cf7d39157946da543dd823117adc   syscore.bin (wave 1, 33.8 MB)
b0e1ae6d73d656b203514f498b59cbcf29f067edf6fbd3803a3de7d21960848d   sysx.bin (wave 1, 7.9 MB)
414b3cf7a288eaa592b0f03d85c7a6960dbdb855fcb684478b9def9faa95c21d   modeling_evil2.py (wave 2)
170ef5754bea51c3b05ae4b6c15c541ffa51d972bf8fe0bd3d1c3a19946bd6ac   modeling_evil6.py (htt0118)
2cd9328715bbffb6b090a58de17222c830d910f5ec8198e3fefb0fca2f3e0ef0   modeling_qtz.py (wjsbg/qwen2-final)
849ce59ffb2ae55e4131111acd8a511444475639ba18515509310414b1661964   e.sh (qa-whl-1021, container escape)
6b1e531c9e799a6df1472acc143c6eaec6ea4dd22f997458ba519644d125197e   sysd-kit.tgz (soumikmahato)
0fc39482f05fb88c13ac9edc306e4cc838085e69d9faa458a19979ace143107a   modeling_hl.py (hl family, 4 repos)
5dc4b927fb91442a66a2e02c1636f629d4042a03a5c1523cd841d6ece66e0beb   m2.bin (wjsbg/zh-assets)
 
Unpacked BuildID (wave 2 syscore.bin): c492732de98117b7b28ce0e410f5966e1232e9dc

SolanaCry

github.com/SolanaUpdateTools/solana-starter-kit     # fake repo, created Nov 2025
github.com/TradingViewGit/developer-features        # related campaign repo
developer-update.app/update/indicator               # install URL, domain registered 2025-10-20
maxwintodaybtc.com:443                              # C2
 
indicator.ps1
  SHA-256 41902d18fa2c285c0f4a6b39cf0f2eefbc7acbff33b6eb6fc88ba680a201d17d
 
%LOCALAPPDATA%\y2inwRnI6G\                          # hidden folder, 14 files
client32.exe, client32.ini                          # NetSupport Manager components
HKCU\Software\Microsoft\Windows\CurrentVersion\Run  # value "SecureModule"

References

No comments yet. Be the first to comment.

More from this blog