@sudobility cryptominer
A cryptominer hit four @sudobility packages.
What happened (facts)
- Four packages in the
@sudobilityscope got a new version within 2m28s on 2026-10-21. - Each new version adds
"postinstall": "sh scripts/miner.sh". scripts/miner.shis byte-identical in all four (3,514 bytes).- The script downloads and runs an XMRig Monero miner under a disguised process name.
- All four were published by the npm account
johnqh, the scope’s sole maintainer. - None of the four malicious versions were ever served by Greenflagged.
- As of 2026-10-21 ~15:40 UTC, all four were still
lateston npm.
Affected versions
| Package | Bad version | Published (UTC) | Last clean version | npm weekly downloads (10-12 → 10-18) |
|---|---|---|---|---|
@sudobility/testomniac_client | 0.0.131 | 15:24:40.715 | 0.0.129 (10-17 20:00) | 493 |
@sudobility/wildduck_client | 2.3.102 | 15:26:36.953 | 2.3.100 (10-17 19:54) | 393 |
@sudobility/indexer_client | 0.0.146 | 15:26:51.156 | 0.0.144 (10-17 19:55) | 373 |
@sudobility/shaperouter_client | 0.0.14 | 15:27:08.858 | 0.0.12 (10-17 19:54) | 362 |
The johnqh account maintains 175 npm packages (172 under @sudobility, plus
@johnqh/haraka, @johnqh/wildduck, signic_sdk). Combined weekly downloads
across all 175: 19,495 (npm search API, 2026-10-21). Only these four carry the
miner. Every other version across all 175 packuments was checked for
preinstall / install / postinstall. The pre-existing hooks are benign:
echo '...installed successfully!' (@sudobility/contracts) and
patch-package (@johnqh/haraka, @johnqh/wildduck).
Clean prior versions are unaffected. Greenflagged still serves them.
Hashes
| Artifact | SHA-256 | SHA-1 (npm shasum) |
|---|---|---|
scripts/miner.sh (all four) | 50df05975352cfa4558892d0a9ab493744c8ddd40948efa6755bb11dc5050118 | |
testomniac_client-0.0.131.tgz | eba7da4c1c6b3a22f0cb29ab569f750ce56dd8c62f528ae289df011c262443d8 | bc3a6d8ee27d80d8c744fd45f2eb6766cfd55182 |
wildduck_client-2.3.102.tgz | 96a607c1f052d9997cf207047fed381b8338a94867a667453bbb8c8806809f8f | 5439d124d9c5c1419c84e2ad7ef0ae92f6b07c7a |
indexer_client-0.0.146.tgz | 417cd6de672151ca0a7fa1e6328c7a06d9eab9c0a42e5d121d68656043a3f4e1 | 52144949ba1b70b2391529e169ae97ea05948d60 |
shaperouter_client-0.0.14.tgz | 867649018b2ed3cf1ccfa9071d98b490693983f82f437aa8e01cd40798b7cf4c | 372f87819798f88dfdd8a18f513e3607315c1910 |
The diff (testomniac_client 0.0.129 → 0.0.131)
Tarball diff: one file added, one file changed. No source changes.
- Added:
package/scripts/miner.sh - Changed:
package/package.jsonfiles:["dist/**/*"]→["dist/**/*", "scripts"]scripts.postinstalladded:sh scripts/miner.shscripts.prepublishOnlyremoved. It was:sh -c 'test ! -f ../workflows/scripts/check-publish-safety.cjs || node ../workflows/scripts/check-publish-safety.cjs' && bun run clean && bun run buildThe other three packages’
prepublishOnlywasbun run clean && bun run build. It was removed from all four.
Publish fingerprints: clean vs malicious
| Field | Clean releases (10-17) | Malicious releases (10-21) |
|---|---|---|
_npmUser | johnqh | johnqh |
_npmVersion | 10.9.9 | 10.9.8 |
_nodeVersion | 22.23.3 | 22.23.1 |
gitHead | present (e.g. 7071e2e2…, c2f3f956…) | absent in all four |
| Version step | +1 | +2 in all four (0.0.130, 2.3.101, 0.0.145, 0.0.13 never existed in npm time) |
prepublishOnly | present | removed |
| Provenance | none | none |
GitHub (github.com/johnqh/*, public API, 2026-10-21): the latest commit in
mail_box_indexer_client, wildduck_client and shaperouter_client is the
10-17 version bump to the last clean version. No commit contains miner.sh.
entitytestomniac_client returns 404 (private or renamed).
What miner.sh does
Header comment, verbatim: # rig2 stealth miner — background, idempotent, quiet. Runs on npm postinstall.
- Exits if anything already has a TCP connection on port 8443 (
grep 20FB /proc/net/tcp), or if/tmp/.sysmond.locknames a live PID. - Downloads a gzip from a pre-signed S3 URL to
/tmp/.smhand gunzips it to/tmp/.smi. - If root:
nohup sh /tmp/.smi agent. The script comments call this “full persistent install … cron + relay/tor”. - If passwordless sudo works (
sudo -n true):nohup sudo -n sh /tmp/.smi agent. - Otherwise: extracts a base64 tarball after a
__PAYLOAD__marker in/tmp/.smi, takesbin/miner-linux-amd64, and copies it to~/.local/lib/<name>/<name>.<name>is picked at random from 25 fake system-daemon names. - Writes the XMRig config to
~/.sysmond.json:rx/0(RandomX), TLS,donate-level: 0,log-file: /dev/null, threads = min(nproc, 4). - The worker name is derived from the hostname, so the pool can tell victims apart.
Fake process names (SNAMES):
edid-mon smmon irqstatsd kmod-cache dmi-cache acpi-polld sysfs-mon bus-quotad mem-syncd pci-cache cpu-slot-mon hwmon-sync udev-cache netlink-mon uevent-polld kset-mon slab-mon vmstatd pagemapd smbios-mon edid-cache hpet-mon rtc-syncd dmi-polld
Linux only (/proc/net/tcp, miner-linux-amd64). Every failure path exits 0, so npm install never fails visibly.
IOCs
| Type | Value |
|---|---|
| Mining pool | networkservicesupp.flashhub.net:8443 (TLS) |
| Monero wallet | 49u1mKb1dNoMixgRHt11Ra32D1w4hviUuRDbaJhXFwXUjQP4FiHsSjnDa7XZepDUW1Tx6HhR6gFwWgbuwNxby4UAMiqxieX |
| Payload host | amplify-gursha-dev-121835-deployment.s3.amazonaws.com |
| Payload object | /amplify-cfn-templates/cloud-backend-cache-20261019.gz |
| AWS access key in pre-signed URL | AKIAUHL4Y2TJZ7L7AE3F |
Pre-signed URL Expires | 2107891463 (2036-10-17 21:24 UTC) |
| Files on host | /tmp/.smh, /tmp/.smi, /tmp/.sysmond.lock, /tmp/.sysmond.log, /tmp/.sm.XXXXXX/, ~/.sysmond.json, ~/.local/lib/<fake-name>/<fake-name> |
Not checked: the stage-2 payload itself. We did not download it.
The bucket name and object path look like an AWS Amplify deployment bucket
(amplify-<app>-<env>-<id>-deployment), likely someone else’s account being
abused. That is an inference from the name only.
How Greenflagged caught it
| Time (UTC) | PT | Event |
|---|---|---|
| 15:24:40.715 | 08:24 | testomniac_client@0.0.131 published |
| 15:24:55 | Greenflagged imports it (npm change feed), +14s | |
| 15:25:16 | Static analysis done, +36s. Routed to AI review | |
| 15:26:36.953 | wildduck_client@2.3.102 published | |
| 15:26:45 | Change feed fetches wildduck_client, but npm returns the old listing. 2.3.102 missed | |
| 15:26:51.156 | indexer_client@0.0.146 published | |
| 15:27:00 / 15:27:04 | Imported (+9s) / analyzed (+13s) | |
| 15:27:08.858 | shaperouter_client@0.0.14 published. We did not mirror this package | |
| 15:32:06 | 08:32 | AI reviewer rejects testomniac_client@0.0.131, confidence 99. Novel-malware alert sent. 7m26s after publish |
| 15:34:38 | 08:34 | AI reviewer rejects indexer_client@0.0.146, confidence 99. Alert sent. 7m47s after publish |
| 15:39:07 | shaperouter_client imported on request during triage | |
| 15:42:56 | 08:42 | AI reviewer rejects shaperouter_client@0.0.14, confidence 99 |
| 15:45:46 | wildduck_client@2.3.102 imported on request during triage. Risk 100, routed to AI review | |
| 15:53:29 | 08:53 | AI reviewer rejects wildduck_client@2.3.102, confidence 99 |
Greenflagged
Greenflagged never served any of these versions. Get started with Greenflagged today at https://greenflagged.dev