Skip to content

Build1 publisher3 min readPublished

Fake Polymarket copy-trading bots hid key stealers in their npm dependencies

One attacker pushed more than twenty malicious GitHub repos from a hijacked account in February, several posing as Polymarket copy-trading bots. Garnet's maintainer published a fifteen-minute checklist for tracing what a bot does with the one signing key it needs.

The Engineer · Build desk

Illustration accompanying Fake Polymarket copy-trading bots hid key stealers in their npm dependencies

What happened

  • Following those repositories' setup steps installed a hidden npm dependency that read the key from .env, sent it to the attacker's server and opened an SSH backdoor, StepSecurity found.
  • In July, a fake "arbitrage bot" gathered dozens of GitHub stars and forks before researchers tied it to thirty malicious npm packages.
  • In most 2026 cases the bot's own code looked clean and the stealer sat in a package installed during setup, according to Garnet's maintainer.
  • Garnet's README says its engine reads the key in exactly two places, both passing it straight to the signer as a SecretString, and that logs show it as REDACTED.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A legitimate bot needs one signing key, so a request for a 12- or 24-word seed phrase disqualifies it before any code is read, since the phrase controls every derived account.
  • decision Users choosing a hosted copy-trading service instead of self-hosted software are picking a different trust model for the key, and the checklist asks them to make that choice knowingly.
  • exposure A bot that prints its configuration in full at startup leaves the key in log files, journald or a hosting provider's console, readable by anyone with access to those logs.
  • cost Every update restarts the review: the dependency, key and host checks have to be re-run each time, against a dedicated wallet holding only money the user can afford to lose.

"Here is the uncomfortable part: a self-hosted trading bot legitimately needs your private key. It has to sign orders," wrote the maintainer of Garnet, a self-hosted Polymarket copy-trading engine [5][7]. The post goes on to say that advice never to give a bot your key is not useful, and that the useful advice is to know exactly what the bot does with it [6].

Garnet's README describes the design done properly. The key sits in .env on the user's machine and signs orders locally under EIP-712. The signature goes to the exchange and the key does not [8]. A bot built this way has no reason to put the key on the network.

Review starts with the full dependency tree: `npm ls --all` for JavaScript and TypeScript, `cargo tree` and `cargo audit` for Rust [9]. The checklist flags names one letter off a popular package, such as big-nunber for bignumber, along with packages that have almost no downloads and any postinstall script [9]. A lockfile has to exist and be committed. Without one, what installs today may differ from what was reviewed yesterday [10].

Reading the bot's own code comes next. One grep finds every use of PRIVATE_KEY in .ts, .js, .py and .rs files, and each hit should lead to signing and nowhere else [11]. The maintainer wrote: "A key that gets read and then concatenated into a string, serialised, logged or passed to an HTTP call is the whole attack in one line." [12] The search matches one literal name. Code that loads the key under a different name will not show up in it [1].

A second grep lists every http, https, ws and wss host in the repository. For a Polymarket bot the expected set is short: Polymarket's clob.polymarket.com, gamma-api, data-api and ws-live-data hosts, a Polygon RPC endpoint, and perhaps Telegram's API [13]. Every other host needs an explanation. The maintainer notes that this check covers only the bot's own code, so the dependency review has to catch the rest [14].

Behaviour can be checked before any key is involved. A well-built bot offers paper trading on real market data [15]. "If the only way to see it work is to fund a wallet first, you're being asked to trust it blind," the maintainer wrote [16].

Garnet's README was written to be checked against this list [17]. It is a rare README that explains how to catch its own author. The engine is Rust end to end with no npm, every dependency is pinned in Cargo.lock, and the Polymarket SDK is pinned to an exact version [18]. The detail I'd weight most is the disclosed exception. Its cargo audit run is clean apart from one advisory in a crate that sits in the lockfile but is not compiled into any binary, and the README says so [19]. These are the maintainer's own statements about the maintainer's own project, and the post does not cite an outside review [17].

What to watch

  • Publication of the thirty npm package names from the July campaign, so installed dependency trees can be checked against a known list.
  • An independent review of Garnet's README claims, including the two key reads and the advisory in the uncompiled crate.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories