The script tag you added years ago can still run code you didn't write
In June 2024, more than 100,000 websites suddenly started redirecting their visitors to scam pages. The owners hadn't changed anything. The malicious code came from a <script> tag they'd added years earlier, pointing at polyfill.io, a service that quietly filled in browser features for older clients. The domain had changed hands, the new owner pushed malware through it, and every site still loading that script served the attack to its own users. CVE-2024-38526. Nobody got hacked in the usual sense. They just kept trusting a host that stopped being trustworthy.
That's the part worth sitting with. A <script src="https://someone-else.com/thing.js"> in your HTML is a standing instruction to your visitors' browsers: fetch this file from a server you don't control and run it with my page's full permissions. It can read the DOM, read non-HttpOnly cookies, see what users type into your forms, and make requests as the logged-in user. For as long as that tag is in your page, you are trusting whoever controls that host, today and every day after.
So the question isn't "did I write good JavaScript." It's "every host my page loads code from, do I still control what it serves?" There are three ways the answer turns out to be no, and all three are readable from outside, in the HTML you already serve to everyone.
One: the CDN is already known to be hostile
The polyfill.io attack wasn't a one-off. The same operator was tied to bootcdn.net, bootcss.com, and staticfile.net/staticfile.org, a cluster of "free" CDN mirrors that a lot of older tutorials told people to use. When one of those goes bad, every page still pointing at it is serving the payload.
This is the cleanest check there is, because it's a closed list. There's no guessing, no heuristic. If your page loads a script or stylesheet from one of those confirmed-compromised hosts, it fires, critical, every time. No false positives, because the set is fixed and each entry has a public, documented compromise behind it.
The fix is to stop pointing at it. Remove the tag, or self-host a known-good copy of the library you actually wanted. If you genuinely needed a polyfill service, move to a mirror with an operator you trust, like cdnjs, and pin it with a Subresource Integrity hash so the browser rejects the file if even one byte of it changes.
Two: the host doesn't exist anymore
The second case is quieter and, in a way, scarier, because nothing in your page looks broken to you. A script tag points at a third-party host whose domain has lapsed. The dependency is already dead, the feature it powered silently stopped working, and you may not have noticed because it was an analytics beacon or a widget nobody mentions.
Here's the problem. A lapsed domain is a domain anyone can buy. The moment someone registers the name your script tag still points at, they own the file at that URL, and your page hands their JavaScript to every visitor. It's the client-side version of subdomain takeover: a dangling reference that becomes a live attack the day someone claims the other end.
The check is conservative on purpose. It only flags a host that returns a hard NXDOMAIN on both the A (IPv4) and AAAA (IPv6) records. A timeout or a SERVFAIL doesn't count, because those mean "couldn't reach the nameserver right now," not "this name is gone." Only a clean, authoritative "no such host" trips it, the same gate the dangling nameserver and dead-MX checks use. We phrase the finding as dangling rather than confirmed-takeover-able, because confirming the domain is buyable would need a registrar lookup we don't do, but a script reference to a host that no longer resolves is worth removing either way.
Three: the host is a cloud bucket nobody owns
The third case is a specific, common shape of the second. The script loads from a cloud storage bucket, an S3 URL like mybucket.s3.amazonaws.com or an Azure blob host, and the bucket has been deleted. The host still resolves, because the provider's domain is fine. It's the bucket name underneath that's vacant.
When you GET the root of a deleted bucket, the provider tells you so in plain text: S3 returns NoSuchBucket, Azure returns ContainerNotFound or BlobNotFound. That response is also an invitation. Bucket names on S3 and Azure are first-come, first-served and globally unique, so an attacker who sees that banner can create a bucket with the exact same name, upload their own app.js, and your page starts serving it. Same URL, same trust, attacker's code.
The gate here is multi-signal so it can't misfire: the host has to be S3-shaped or Azure-blob-shaped, the GET has to come back 404 or 400, and the body has to carry the provider's literal missing-bucket banner. Only all three together fire. Google Cloud Storage is deliberately left out, because GCS doesn't let a deleted bucket's name be re-registered for a long retention window, so a missing GCS bucket isn't claimable the way an S3 one is.
The fix for both the dead host and the dead bucket is the same: remove the reference if you don't need it, or, if you do, re-create the resource under your own control and lock the name down. Then, as with every third-party script, pin it with SRI so a swapped file gets rejected instead of run.
Reading it from outside
All three of these are sitting in the HTML you serve to every visitor, which is exactly why a stranger can assess your supply chain without touching your backend, and why SurfaceCheckr can too. We fetch your pages, pull out every cross-origin <script> and <link>, and check each host three ways: is it on the known-compromised list, does it still resolve in DNS, and if it's a bucket, is the name unclaimed. First-party assets are skipped, because the risk is the code you load from someone else.
What we can't tell you is whether a third party's code is doing something hostile right now while it's still under the right owner's control. That part runs in the browser, out of our view, and we won't pretend to see it. What we will hand you is the list of script hosts your page trusts that it shouldn't: the hijacked CDN, the lapsed domain, the empty bucket. For most sites that's a list nobody has looked at since the tags went in, which is the whole problem.
Read next
- What can someone learn about your site without hacking it?What an attacker sees before they touch your site
- Your tech stack is public. Does that matter?What an attacker sees before they touch your site
- What does "passive scanning" actually mean, and why is it legal?What an attacker sees before they touch your site
- What is a security grade and what does an A vs an F mean?What an attacker sees before they touch your site
Find it before someone else does.
Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.