Security Update: In the wake of AI-fueld security incidents, we're auditing all our systems and practices. Here's how we protect the TrackJS agent from attack, and how to host it yourself. Read the post →

How We Protect Our JavaScript Agent, and How to Host It Yourself

On October 29, a web performance script started emptying Polymarket wallets.

The script belonged to DebugBear, a performance monitoring service a lot like Request Metrics. Polymarket ran it to measure how fast its site loaded for real visitors. Scott Helme’s analysis shows that cdn.debugbear.com stopped serving DebugBear’s script. An nginx server answered in its place with a script built to drain Polymarket accounts.

The malicious script read each visitor’s session out of the browser, found the trading client inside the app, and used the visitor’s own signed-in session against them. It either sold their positions and sent the cash to the attacker, or transferred the positions to the attacker directly. It also tried to send each victim’s portfolio, IP address, country, and operating system to data.debugbear.com, which ohey.ca’s header analysis shows had also been replaced by the attacker’s server.

The script was live for about fifty minutes before Polymarket pulled it and paused trading. Helme traced about 28.6 million POL, roughly $2.6 million, into the attacker’s wallet. Polymarket hasn’t confirmed a total, but says it will repay everyone affected. DebugBear told its RUM customers on X to remove its embed code. It hasn’t said yet how the attackers got in, but it sounds like a DNS compromise.

Unfortunately, this isn’t the first time this has happened. In 2024, polyfill.io got a new owner and its CDN started injecting malware into more than 100,000 websites. Polymarket users lost about $3 million in June when a third-party vendor, which Polymarket never named, was compromised. Any script you load from someone else’s server runs with the same power as your own code, and you’re trusting every system that controls what that URL returns.

Our agent works the same way

TrackJS runs as a JavaScript agent on your pages. Most of you load it from our CDN, and it sends error data back to our servers. If someone took over our CDN’s hostname, or swapped the file behind it, their code would run on your pages just like the drainer ran on Polymarket’s.

We watch the agent files on our CDN and get alerted if their contents ever change. The TrackJS agent hasn’t changed since December 2023, so any change is an alarm. Since the breach, we’ve been reviewing how our agent gets published and who can touch it, and making changes to our services.

Our agent also doesn’t take instructions from our servers. It sends data to us and checks only whether the request went through, and it never downloads code or settings. So if you host our agent yourself, it can’t be changed by anything that happens on our side. It still sends data to us, so keeping our servers and domains secure is still our job.

Host the agent yourself

One of our customers emailed the day after the breach to ask whether they could host our script themselves. Yes. You always could, and for any site that handles money or sensitive data, we recommend it.

Install the agent from npm and bundle it with your code. Your lockfile decides the version, so you control exactly what your users get.

npm install trackjs
import { TrackJS } from "trackjs";
TrackJS.install({ token: "YOUR_TOKEN" });

If you load TrackJS with a script tag, point it at a pinned version instead of latest and add a Subresource Integrity hash. If the file ever changes, the browser refuses to run it.

<script src="https://cdn.trackjs.com/agent/3.10.4/t.js"
  integrity="sha384-njM4XFxHYqEd5zVpi3Zt1t/TGkshDeMEjIGUJtLQ38d6zwGZpTj9tPXlhspkHNhK"
  crossorigin="anonymous"></script>

That hash is the check that would have stopped the Polymarket drainer. Helme points out that Polymarket’s Content Security Policy allowed every request the drainer made, because the URL never changed. Only the bytes did.

You can also download that same file and serve it from your own domain. The TrackJS installation docs cover the npm setup, and the TrackJS SRI guide covers pinned script tags.

Whichever you pick, update your Content Security Policy. Once the agent comes from your own domain, you can drop our CDN from script-src. Keep the hosts the agent sends data to. For TrackJS, that’s capture.trackjs.com in connect-src and usage.trackjs.com in img-src.

If you’re running your own security review and have questions, email hello@trackjs.com. It goes straight to our team.

Join our JavaScript Debugging Masterclass

Turn JavaScript debugging from frustration into mastery. Our expert training helps you build more reliable, error-free web applications.

Scriptguy playing darts