Wallet drainers

Polymarket wallet drainer served from DebugBear's script CDN

On 29 October 2026 the DebugBear monitoring script Polymarket loaded was replaced with a wallet drainer. In 52 minutes its recipient wallet took in 28,636,284.45 POL and positions from 33 accounts, and it had forwarded everything by 18:38 UTC.

A performance gauge plugged into a trading page siphons coins and position cards out of its wallet through a hidden tube into a purple vault.
Scriptcdn.debugbear[.]com/C7xcKKrjUnXK.js
SHA-2560755e767504d8103f6c27b9e765a1026507a17c04513242adb503df7777879e5
Active29 October 2026: first funds at 17:10:23 UTC, removed from Polymarket by 17:59:20 UTC
Recipient0x3F99139F1C5854bb628c2e576Aaf638e99dFfc18 (Polygon)
Received28,636,284.45 POL and positions from 33 accounts

Key takeaways

  • On 29 October 2026 the DebugBear real-user monitoring (RUM) script loaded by Polymarket was replaced with a wallet drainer. It ran in logged-in sessions between 16:54 UTC, when the script's origin went down, and 17:59 UTC, when Polymarket removed the script.
  • The drainer borrowed Polymarket's own trading client from the page, signed transfers with the account's existing signer and submitted them through Polymarket's own relayer. Every host it called, apart from DebugBear's, was on Polymarket's Content Security Policy.
  • The recipient wallet took in 28,636,284.45 POL and positions from 33 deposit wallets between 17:10 and 18:02 UTC, then moved the POL to three wallets and the positions to a fourth by 18:38 UTC.

Acknowledgements: Alon Gal of Hudson Rock captured the drainer at 18:12:06 and 18:13:04 UTC and shared the files with us. Hudson Rock's write-up covers how the warning surfaced.

We analyzed the drainer DebugBear's CDN served to Polymarket visitors on 29 October 2026, traced its recipient wallet on Polygon and compared it with archived copies of the same script URL. The script targets Polymarket alone: it reads Polymarket's storage keys, calls Polymarket's APIs and finds Polymarket's trading client inside the page.

Everything below comes from static analysis of the captured script, the Internet Archive's copies of cdn.debugbear[.]com/C7xcKKrjUnXK.js, and Polygon chain data read from public RPC nodes between 20:30 and 20:50 UTC on 29 October.

Timeline

The table combines archived responses, Polygon block times and published reports. Times from reports name their source:

Time (UTC) Event
2026-10-07 09:43:16 DebugBear's clean RUM file for snippet C7xcKKrjUnXK is last modified, per its archived headers
2026-10-27 12:50:43 Date on the drainer file, per the server that later served it
2026-10-29 11:48:50 A Cloudflare edge fetches the clean file from origin (archived at 12:06:55, 1,085 seconds old)
2026-10-29 16:54:57 Cloudflare returns error 521 for cdn.debugbear[.]com: the origin server is not responding (archived)
2026-10-29 17:10:23 First POL reaches the recipient, block 96392296
2026-10-29 17:26:29 First position tokens reach the recipient
2026-10-29 17:27:45 Cloudflare's Chicago edge caches the drainer (captured at 18:12:06, 2,661 seconds old)
2026-10-29 17:30 Users report drained wallets (BNO News)
2026-10-29 17:45:43 to 17:56:05 The recipient sends 25 position transfers to 0x5a2764fbbe5d139ef0ed988ec5f95320ce730c0b
2026-10-29 17:59:20 Polymarket builds production with "chore(polymarket): remove debugbear rum script (#15337)"
2026-10-29 18:00:58 DebugBear posts that it is impacted by a security incident and tells RUM customers to remove its embed code
2026-10-29 18:02:17 Last position tokens reach the recipient
2026-10-29 18:05 Polymarket's status page shows cancel-only mode with relayer actions paused (Unchained)
2026-10-29 18:15 The recipient holds 28,636,284.45 POL
2026-10-29 18:28:32 to 18:38:16 Three transfers move all 28,636,284.45 POL to three wallets
2026-10-29 18:30:58 TheFatMbappe posts a trace of 2.724M pUSD from 721 wallets
2026-10-29 18:33:12 Polymarket Traders posts that a third-party provider was compromised and that affected users will be made whole
2026-10-29 18:54:45 Polymarket Traders posts that trading will resume at about 19:00 UTC
2026-10-29 19:00:35 Polymarket Traders posts that trading is fully back online and affected traders will be reimbursed
2026-10-29 20:37 cdn.debugbear[.]com and data.debugbear[.]com return NXDOMAIN

How the compromise happened

DebugBear sells RUM, a script that measures page speed in visitors' browsers. Each customer embeds a per-project file from cdn.debugbear[.]com. Polymarket's file was C7xcKKrjUnXK.js.

The Internet Archive holds copies of that URL from June 2025 to the morning of 29 October 2026. Every copy is DebugBear's RUM code, with "snippetKey":"C7xcKKrjUnXK" and "sendTo":"https://data.debugbear.com" in its configuration. The last clean copy, archived at 12:06:55 UTC on 29 October, is 60,493 bytes and came from Google Cloud Storage, with an MD5 ETag and x-goog-* headers.

At 16:54:57 UTC Cloudflare could not reach the script's origin and returned error 521. Sixteen minutes later the first funds reached the recipient. The drainer response carried an nginx-format ETag, 6ae09e23-3b53, which encodes the file time and its 15,187-byte size, and none of the Cloud Storage headers. At 18:13:44 UTC data.debugbear[.]com answered with an nginx/1.24.0 (Ubuntu) 404 page. A different server was answering for both DebugBear hostnames.

At 18:00:58 UTC DebugBear posted that it was impacted by an ongoing security incident and told RUM customers to remove its embed code. Polymarket had already built a release without the script at 17:59:20 UTC.

DebugBear post on X: We're impacted by an ongoing security incident. If you use our RUM product, please remove the embed code from your website. Posted 6:00 PM, Oct 29, 2026, UTC.
Figure 1: DebugBear's post at 18:00:58 UTC. Source: x.com/DebugBear, captured 29 October 2026.

At 18:33:12 UTC the Polymarket Traders account said one of its third-party providers was compromised, that it referred the matter to law enforcement and that anyone affected will be made whole. At 19:00:35 UTC it said trading was fully back online and that it is reimbursing all affected traders.

Polymarket Traders post on X: We've identified and fixed an issue impacting our international platform, Polymarket.com. One of our 3rd party providers was compromised, and we have referred this to relevant law enforcement. Anyone who has been impacted will be made whole. Posted 6:33 PM, Oct 29, 2026, UTC.
Figure 2: Polymarket Traders at 18:33:12 UTC. Source: x.com/PolymarketTrade, captured 29 October 2026.
Two Polymarket Traders posts on X: We are coming back online. Trading will resume at approximately 3pm ET. Then, at 7:00 PM UTC: Trading is now fully back online. We'll make this right and are fully reimbursing all affected traders.
Figure 3: Polymarket Traders at 18:54:45 and 19:00:35 UTC. Source: x.com/PolymarketTrade, captured 29 October 2026.

Technical analysis

The drainer is a single 15,187-byte minified function named startdebugger. The code is plain JavaScript with no obfuscation.

Stage 1: The script waits for a logged-in trader

The script starts three requests at once: its configuration, the visitor's location from polymarket.com/api/geoblock, and a 1.5-second wait. Visitors the geoblock API marks as blocked are skipped.

The configuration request falls back to hard-coded values:

static async getInitialData(){try{return await(await fetch(this._apiUrl+"/metrics/init?referrer="+this.TRACK_ID,{method:"GET"})).json()}
catch{return{recipient:N,minimalPerPosition:10,minimalToProcess:10,priorityToken:"POL"}}}

N is 0x3F99139F1C5854bb628c2e576Aaf638e99dFfc18. The configuration endpoint returned an HTML 404 at 18:13:44 UTC, so the JSON parse failed and the defaults applied: that recipient, a $10 minimum for the account and for each position, and POL as the payout coin.

The script then reads three localStorage keys that Polymarket's app writes:

  • polymarket.auth.params, the login type and wallet address
  • polymarket.auth.proxyWallet, the deposit wallet that holds the account's funds
  • poly_clob_api_key_map, the trading API credentials for each wallet

A visitor with no login, or no trading credentials for the logged-in wallet, goes no further. The code sorts accounts into eoa, a browser wallet such as MetaMask, and magic, Polymarket's email login.

Stage 2: The script borrows the page's trading client

The drainer carries no keys. It uses the trading client Polymarket's own page has already built.

React stores each component's props and hook state on internal "fiber" objects attached to DOM elements under keys starting __reactFiber$. The script collects up to 400 buttons, links and containers and walks their fiber trees for up to 5 seconds or 60,000 nodes. It keeps any object with a postOrder method and a createOrder or createMarketOrder method: Polymarket's order client, with its signer attached.

If the client has not mounted, the script checks 15 more times at 250 ms intervals. After that it sets location.pathname to / followed by the wallet address. The new page loads the script again.

Stage 3: The script moves cash and positions to the recipient

The script reads the deposit wallet's two token balances over Polygon RPC and up to 200 open positions from data-api.polymarket.com. It stops when the account is worth less than $10.

Cash goes first when it is worth more than the positions. The script asks api.fun.xyz for a quote that swaps the balance and pays it to the recipient in POL. It falls back to native USDC, then to a direct USDC.e transfer.

Each set of calls is wrapped in an EIP-712 DepositWallet batch, signed with the page's signer and posted to relayer-v2.polymarket.com/submit with a one-hour deadline. EIP-712 is the standard for signing structured data. A browser wallet shows each batch as a signature request for DepositWallet.

Positions worth more than $10 each go next, by account type:

  • Browser-wallet accounts move every qualifying position to the recipient in one safeBatchTransferFrom call on the conditional-token contract 0x4D97DCd97eC945f40cF65F87097ACe5EA0476045.
  • Email-login accounts sell each position with fill-and-kill SELL orders, first at 97% of the current price, then at 50%, then at the market's minimum tick. Closed, inactive and redeemable markets are skipped. The proceeds are swept with the cash routine.

After the sales the script polls the balance for up to 50 seconds. The check assigns the new balance to the old one before comparing them, so it passes on the first read.

Stage 4: The script reports each step to the data host

Three endpoints on data.debugbear[.]com receive the victim's details:

Endpoint Sent
/metrics/submit Portfolio value, cash, and each position's market title, value and outcome, before anything moves
/metrics/update Each cash sweep: recipient, value, currency and transaction hash
/metrics/handle Each position transfer: recipient, value, position IDs and transaction hash

Every report also carries the deposit wallet, the login wallet, the login type, the operating system, the tracking ID 4c6b64a0-a7ef-4c0f-9c70-3a4e6f89065c, and the IP address and country from Polymarket's geoblock API.

Every other host the drainer calls appears in Polymarket's Content Security Policy: *.polymarket.com for the APIs and relayer, *.alchemy.com for RPC and *.fun.xyz for swap quotes. The policy allowed each request because each went to a host Polymarket uses.

Impact

The recipient wallet held nothing and had sent no transactions before 17:10:23 UTC on 29 October. Its POL balance held at about 811,000 from 17:14 to 17:26 UTC, then climbed steeply once Cloudflare's Chicago edge cached the drainer at 17:27. It stopped rising at 18:11 UTC, 12 minutes after Polymarket's release without the script.

POL held by the recipient wallet, 17:00 to 19:00 UTC on 29 October 20260M10M20M30M17:0017:3018:0018:3019:0017:27 Chicago edge caches the drainer17:59 Polymarket removes the script18:28 POL leaves the wallet17:00 UTC: 0 POL17:02 UTC: 0 POL17:05 UTC: 0 POL17:08 UTC: 0 POL17:11 UTC: 3,225 POL17:14 UTC: 811,161 POL17:17 UTC: 811,161 POL17:20 UTC: 811,161 POL17:23 UTC: 811,161 POL17:26 UTC: 820,559 POL17:29 UTC: 4,475,547 POL17:32 UTC: 5,506,649 POL17:35 UTC: 9,624,518 POL17:38 UTC: 10,544,915 POL17:41 UTC: 11,460,921 POL17:44 UTC: 13,677,990 POL17:47 UTC: 15,010,362 POL17:50 UTC: 17,132,195 POL17:53 UTC: 21,562,047 POL17:56 UTC: 22,452,612 POL17:59 UTC: 23,451,239 POL18:02 UTC: 28,378,270 POL18:05 UTC: 28,586,434 POL18:08 UTC: 28,621,756 POL18:11 UTC: 28,636,284 POL18:14 UTC: 28,636,284 POL18:17 UTC: 28,636,284 POL18:20 UTC: 28,636,284 POL18:23 UTC: 28,636,284 POL18:26 UTC: 28,636,284 POL18:29 UTC: 10,022,700 POL18:32 UTC: 5,727,258 POL18:35 UTC: 5,727,258 POL18:38 UTC: 0 POL18:41 UTC: 0 POL18:44 UTC: 0 POL18:47 UTC: 0 POL18:50 UTC: 0 POL18:53 UTC: 0 POL18:56 UTC: 0 POL19:00 UTC: 0 POL
Figure 4: POL held by the recipient wallet, sampled every three minutes from Polygon blocks between 17:00 and 19:00 UTC on 29 October 2026. Source: Polygon RPC, read by Ohey.

In the 52 minutes from 17:10 UTC it received:

  • 28,636,284.45 POL, delivered by swap contracts
  • 36 transfers of position tokens from 33 deposit wallets
  • 10,383.33 USDC.e in 15 transfers and 6,320.69 USDC in 14 transfers

The recipient forwarded all of it. It swapped the stablecoins in four transactions at 17:32 and 18:02 UTC. It sent the position tokens to 0x5a2764fbbe5d139ef0ed988ec5f95320ce730c0b in 25 transfers between 17:45 and 17:56 UTC. The POL left in three transfers:

Time (UTC) Amount (POL) To
18:28:32 18,613,584.00 0xe4c4c39c1733e1c7c06b79f97ed442b68df41ec6
18:30:41 4,295,442.00 0x5392587814ce8c6af6789764f88e5e9ab0b9962c
18:38:16 5,727,258.45 0xc91ee0574a14446de200923729c349b9cae19b60

At 20:50 UTC those three wallets held 15,331,308, 4,295,442 and 4,235,983 POL.

TheFatMbappe traced 2.724M pUSD from 721 source wallets in transactions that paid the recipient. BNO News reported about $2.6 million from transaction records. Polymarket has not published a figure.

TheFatMbappe post on X headed Polymarket Incident Breakdown, tracing 2.724M pUSD across 721 source wallets to the embedded recipient. Posted 6:30 PM, Oct 29, 2026, UTC.
Figure 5: TheFatMbappe's trace at 18:30:58 UTC. Source: x.com/TheFatMbappe, captured 29 October 2026.

Indicators of compromise (IOCs)

Network indicators

Indicator Type Context
hxxps://cdn.debugbear[.]com/C7xcKKrjUnXK.js URL Drainer script served on 29 October 2026
hxxps://data.debugbear[.]com/metrics/init URL Drainer configuration
hxxps://data.debugbear[.]com/metrics/submit URL Portfolio report
hxxps://data.debugbear[.]com/metrics/update URL Cash sweep report
hxxps://data.debugbear[.]com/metrics/handle URL Position transfer report

File hashes (SHA-256)

Indicator Context
0755e767504d8103f6c27b9e765a1026507a17c04513242adb503df7777879e5 Drainer, 15,187 bytes
0525f046c83daa429e0f676d36650d51d9e283ba5cacfa56097c80bad586128b Clean DebugBear RUM file for the same URL, 60,493 bytes, served until 29 October

Wallet addresses (Polygon)

Indicator Context
0x3F99139F1C5854bb628c2e576Aaf638e99dFfc18 Hard-coded recipient
0x5a2764fbbe5d139ef0ed988ec5f95320ce730c0b Received the stolen positions
0xe4c4c39c1733e1c7c06b79f97ed442b68df41ec6 Received 18,613,584 POL
0x5392587814ce8c6af6789764f88e5e9ab0b9962c Received 4,295,442 POL
0xc91ee0574a14446de200923729c349b9cae19b60 Received 5,727,258.45 POL