---
key: "case-study-polyfill-io"
title: "Case study: the polyfill.io CDN takeover (2024)"
url: "https://softwaresecurity.com/domains/supply-chain/dependencies/case-study-polyfill-io/"
api: "https://softwaresecurity.com/api/v1/articles/case-study-polyfill-io.json"
domain: "supply-chain"
topic: "Software supply chain"
type: "Case study"
identifiers: ["CWE-829","CWE-494","A03:2025","A08:2025"]
tags: ["Browser security","Supply chain attacks","Supplier and open source risk"]
stages: ["build","respond","govern"]
roles: ["appsec-engineer","platform-sre"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# Case study: the polyfill.io CDN takeover (2024)

How the sale of the polyfill.io domain and project in February 2024 turned a script tag on more than 100,000 websites into a channel for malicious redirects by June, why Subresource Integrity could not have protected it, and the controls that would have.

Canonical URL: https://softwaresecurity.com/domains/supply-chain/dependencies/case-study-polyfill-io/

## In short

polyfill.io was a free service that sent each browser the JavaScript polyfills it needed. In February 2024 its domain and GitHub account were sold to a company called Funnull; by June the service was injecting code that redirected some mobile visitors to scam and betting sites, on more than 100,000 websites by Sansec's estimate. Because the response changed with each browser, Subresource Integrity could not pin it. Removing the dependency, self-hosting fixed files with SRI, and a strict script-src policy were the controls that worked.

- The attack needed no compromise: whoever owns a script's domain decides what every embedding site runs.
- Warnings came early: the original author urged sites to stop using it, and Cloudflare flagged the new ownership as a supply-chain risk on 2024-02-29, four months before the malicious code was reported.
- SRI pins one exact file, and polyfill.io returned different code per `User-Agent`, so the people using it could not pin it.
- The injected code activated only on some mobile devices at some hours and stayed quiet for admins and analytics, which kept it hidden.
- Namecheap suspended the domain on 2024-06-27; Cloudflare and Fastly offered mirrors, and Cloudflare rewrote links for sites it proxied.
- For payment pages, PCI DSS v4.0.1 requirements 6.4.3 and 11.6.1 now require an inventory and integrity checks for every script.

## Incident at a glance

- Date: 2024-02 to 2024-06-27
- Category: Supply-chain compromise
- Organizations: polyfill.io; Funnull; pdoc
- CVE: [CVE-2024-38526](https://nvd.nist.gov/vuln/detail/cve-2024-38526)
- Impact: After the domain's sale, the polyfill.io CDN served code that redirected some mobile visitors to scam and betting sites, on more than 100,000 websites by Sansec's estimate. The code differed per browser, so Subresource Integrity could not protect sites that loaded it.
- How it was found: Sansec reported on 2024-06-25 that `cdn.polyfill.io` was injecting malware; Cloudflare later found indicator matches in its page-script monitoring from 2024-06-08.

### Timeline

1. 2024-02: The polyfill.io domain and GitHub account are sold to Funnull; the service's original author tells sites to remove it.
2. 2024-02-29: Cloudflare publishes a mirror on cdnjs and warns of the supply-chain risk of the new ownership.
3. 2024-06-08: Cloudflare's page-script monitoring later finds matches for the published indicators from this date.
4. 2024-06-25: Sansec reports the malicious injection; Google blocks ads for e-commerce sites that use it; CVE-2024-38526 is published for pdoc.
5. 2024-06-26: Cloudflare starts rewriting polyfill.io links on sites it proxies.
6. 2024-06-27: Namecheap places the domain on hold.
7. 2024-06-28: Sansec links other CDN domains to the same operator.
8. 2025-05-29: The US Treasury sanctions Funnull Technology Inc.

## Recommended defaults

| Setting | Value | Note | Source |
| --- | --- | --- | --- |
| Ownership change | February 2024: domain and GitHub account sold to Funnull | Cloudflare warned of the supply-chain risk on 2024-02-29. | Cloudflare blog; Sansec |
| Malicious injection reported | 2024-06-25 | Cloudflare later found indicator matches from as early as 2024-06-08. | Sansec; Cloudflare |
| Scale | 100,000+ sites (Sansec estimate) | Estimates vary by method and date; treat any single figure as approximate. | Sansec |
| Domain status | Suspended by Namecheap on 2024-06-27 | Removing references is still required: a domain can return. | Sansec |
| Related CVE | CVE-2024-38526: pdoc `--math` output linked polyfill.io; fixed in pdoc 14.5.1 | Most affected sites had no CVE; the dependency was a script tag in their own HTML. | CVE record; GHSA-5vgj-ggm4-fg62 |
| Replacement | Remove the polyfill, or self-host a fixed build with SRI | Cloudflare (`cdnjs.cloudflare.com/polyfill`) and Fastly offered mirrors; modern browsers rarely need the service. | Cloudflare; Sansec |

## Verify it

1. Search source, templates, CMS settings and built HTML for polyfill.io and the related domains, and remove every reference.
2. List every third-party script origin your pages load, with an owner and a reason for each.
3. Check that each remaining external script is a fixed file with an `integrity` attribute, or self-hosted.
4. Confirm the Content-Security-Policy `script-src` names only the origins you listed, with no wildcards; /tools/csp-evaluator/ checks a policy in the browser.
5. On payment pages, confirm the script inventory and change detection that PCI DSS 6.4.3 and 11.6.1 require are running.

## What you will learn

- Explain how a domain sale turned a trusted script tag into a malicious one without any compromise.
- Explain why SRI could not protect a service that returned different code per browser.
- Find third-party script references in source, builds and live pages.
- Apply inventory, pinning, CSP and PCI DSS 6.4.3 and 11.6.1 controls to third-party scripts.

Audience: Front-end and web platform engineers, and AppSec teams responsible for third-party scripts on websites and payment pages

Before you start:

- Know how a browser loads and runs a script from a third-party domain.
- Know what a Content Security Policy and the `integrity` attribute do.
- For the code: ripgrep, curl and OpenSSL.

Time: About 27 minutes: 7 to read and 20 for the exercise

## A script tag whose owner changed

Polyfills add modern JavaScript features to older browsers. The polyfill.io service made that easy: a site added one script tag pointing at `cdn.polyfill.io`, and the service read each visitor's `User-Agent` header and returned only the polyfills that browser needed. Many sites added the tag years ago and never revisited it.

In February 2024 the domain and the project's GitHub account were sold to Funnull, a company with no track record in running the service. The project's original author publicly told sites to remove it, and Cloudflare warned on 2024-02-29 that sites now depended on a new, unknown operator. On 2024-06-25 Sansec reported that the service was injecting malicious code. Nothing had been hacked: the new owner of the domain served what it chose, and every site that embedded the tag ran it.

## Timeline



| When | Event | Source |
| --- | --- | --- |
| February 2024 | The polyfill.io domain and GitHub account are sold to Funnull; the original author warns sites to remove polyfill.io. | Sansec; Cloudflare |
| 2024-02-29 | Cloudflare publishes a mirror on cdnjs and warns of the supply-chain risk of the new ownership. | Cloudflare |
| From 2024-06-08 | Cloudflare later finds matches for the published indicators from this date in its page-script monitoring. | Cloudflare (2024-06-26) |
| 2024-06-25 | Sansec reports that the domain injects malware into embedding sites; Google starts blocking ads for e-commerce sites that use it; CVE-2024-38526 is published for pdoc. | Sansec; CVE record |
| 2024-06-26 | Cloudflare starts rewriting polyfill.io links on sites it proxies, on by default for free plans. | Cloudflare |
| 2024-06-27 | Namecheap places the domain on hold. | Sansec |
| 2024-06-28 | Sansec links other domains to the same operator, including `bootcdn.net`, `bootcss.com` and `staticfile.net`. | Sansec |
| 2025-05-29 | The US Treasury sanctions Funnull Technology; its release says that in 2024 Funnull bought a code repository used by web developers and altered it to redirect visitors of legitimate sites, without naming polyfill.io. | US Treasury |

## What did the injected code do?

According to Sansec, the injected code redirected mobile users to a sports betting site through a domain posing as Google Analytics. It was selective by design: it activated only on certain mobile devices and at certain hours, stayed inactive when it found an admin cookie, and delayed itself when it detected a web analytics service, so site owners testing their own pages, and their analytics, were unlikely to see anything.

Redirects to scam sites were what was observed, but the capability was larger. A script loaded into a page runs with the page's full authority: it can read what users type, including card numbers and passwords on checkout and login pages, and send it anywhere the browser allows. That is why the response treated every embedding site as running untrusted code, whatever the payload of the day.

## Why couldn't Subresource Integrity protect these sites?

Subresource Integrity (SRI) lets a page say which exact bytes it expects from a script: the `integrity` attribute carries a hash, and the browser refuses a file that does not match. It is the standard defense against a CDN serving altered code, and it would have blocked this attack for a fixed file.

polyfill.io was not a fixed file. The service built a different bundle for each browser from the `User-Agent` header, so no single hash could match every visitor, and sites using it could not adopt SRI. That made the risk structural: the only safe options were to stop depending on the service or to replace it with fixed files you control. Many sites no longer needed it at all, because the browsers they support already implement the features it polyfilled.

## Find references in code, builds and live pages

Script tags hide in many places: page templates, CMS theme settings, tag managers, documentation generators such as pdoc, and third-party widgets that load further scripts. Search the source and the built output, and check what live pages actually load, because a tag manager or a widget can add a script that appears in neither.

```bash
# Source, templates and built output: references to polyfill.io and the
# other domains Sansec tied to the same operator on 2024-06-28
rg -n --hidden -e 'polyfill\.io' -e 'bootcdn\.net' -e 'bootcss\.com' -e 'staticfile\.net' .

# Every third-party script origin in the built site, with a count
grep -rhoE '<script[^>]+src="https?://[^/"]+' dist/ |
  sed -E 's/.*src="//' | sort | uniq -c | sort -rn

# A live page, as a browser receives it
curl -s https://www.example.com/checkout | grep -oE "https?://[^\"' ]*polyfill\.io[^\"' ]*"

```

## Replace dynamic third-party scripts with pinned ones

Where you keep a third-party library, serve a fixed file, either self-hosted or from a versioned CDN URL, with an `integrity` hash and `crossorigin="anonymous"`, and restrict where scripts may load from with a Content Security Policy. /domains/appsec/browser-security/security-headers/ and /domains/appsec/browser-security/cors-csp-browser-boundaries/ cover CSP in depth. A strict `script-src` would not have stopped a site that explicitly allowed polyfill.io, but it stops scripts that a compromised third party pulls in from further domains, and it makes the list of trusted origins explicit and reviewable.

```bash
# Self-host a fixed file and pin it: compute the SRI hash of the exact bytes you serve
curl -fsSLo vendor/lib.min.js https://cdnjs.cloudflare.com/ajax/libs/lib/1.2.3/lib.min.js
echo "sha384-$(openssl dgst -sha384 -binary vendor/lib.min.js | openssl base64 -A)"

# Reference it with the hash; the browser refuses the file if a single byte changes:
#   <script src="/vendor/lib.min.js"
#           integrity="sha384-<hash printed above>" crossorigin="anonymous"></script>

# And allow scripts only from origins you control:
#   Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'none'

```

## Which controls would have helped?

A script tag is a dependency like any package, but it is resolved by every visitor's browser at run time, with no lockfile and no review. The controls bring it back under the same discipline.

| Control | Practice | Effect | Guide |
| --- | --- | --- | --- |
| Inventory every third-party script with an owner and a reason | SSDF PW.4 (reuse well-secured software); PCI DSS 6.4.3 | The February warning would have reached someone who knew polyfill.io was in use. | /frameworks/pci-dss-requirement-6/ |
| Remove scripts you no longer need | SSDF PW.4 | Most sites did not need polyfills for their supported browsers. | /domains/supply-chain/dependencies/project-health-and-intake/ |
| Self-host or pin fixed files with SRI | SSDF PW.4; PCI DSS 6.4.3 integrity | A changed file is refused by the browser. | /domains/appsec/browser-security/security-headers/ |
| Strict CSP `script-src` without wildcards | Defense in depth | Limits which origins can run code; blocks follow-on loads from new domains. | /domains/appsec/browser-security/cors-csp-browser-boundaries/ |
| Watch ownership and reputation of script origins | SSDF PW.4; supplier review | A domain sale is a supplier change; review it as one. | /frameworks/supplier-software-assurance/ |
| Change and tamper detection on payment pages | PCI DSS 11.6.1 | Detects new or modified scripts and headers as browsers receive them. | /frameworks/pci-dss-requirement-6/ |

## Lessons that still apply



- Every external script tag delegates control of your page to whoever owns that domain, now and after any future sale.
- Treat a change of ownership of a dependency, a domain or a maintainer account as a security event, as in /domains/supply-chain/dependencies/case-study-event-stream/.
- Prefer fixed, versioned files you can hash over services that generate code per request.
- Warnings arrived four months early; an inventory with owners is what turns a warning into a removal.
- Selective payloads evade owners' own testing: check pages from the outside, as different devices see them.

## What the public evidence does not establish

The public record does not establish how many sites served malicious code to visitors, as opposed to embedding the script, or how many visitors were redirected; estimates of affected sites ranged widely and changed as researchers rescanned. Whether the injected code did anything beyond redirects, such as collecting form data, is not documented in the primary reports. The US Treasury's description of Funnull altering a code repository in 2024 matches polyfill.io but does not name it.

## Guided practice

It is 2024-06-26. You are responsible for four web properties. Decide whether each is exposed to the polyfill.io takeover and what to change. Treat the facts as true.

### Supplied material

```text
W1: Marketing site; template includes <script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=default">; no CSP; served directly, not through Cloudflare.
W2: Checkout page; loads polyfill.io through a tag manager container; CSP script-src includes https://*.googletagmanager.com and https://cdn.polyfill.io; PCI DSS in scope.
W3: Internal docs generated with pdoc 14.4.0 --math, hosted on an intranet.
W4: Web app; self-hosts core-js 3.37.1 from /vendor with an integrity attribute; CSP script-src 'self'.
```

### 1. Assess W1

Is W1 exposed, and what is the fix?

Worked answer: Yes: every visitor's browser runs whatever `cdn.polyfill.io` returns. Remove the tag, or, if older browsers still need polyfills, self-host a fixed build with SRI. Add a CSP so a future third-party script cannot be loaded without review.

### 2. Assess W2

Why is W2 the most serious, and what is required?

Worked answer: It is a payment page, so injected code could read card data, and the CSP explicitly allows `cdn.polyfill.io`. Remove it from the tag manager and the CSP at once, review the page's script inventory and integrity controls under PCI DSS 6.4.3, and check 11.6.1 change-detection alerts and logs for unexpected scripts since February.

### 3. Assess W3

What about the pdoc output?

Worked answer: pdoc before 14.5.1 with `--math` linked polyfill.io (CVE-2024-38526), so readers' browsers loaded it. Upgrade pdoc to 14.5.1 or later and regenerate the documentation; being on an intranet does not help, because the browser fetches the script from the internet.

### 4. Assess W4

Is W4 exposed?

Worked answer: No: the polyfill code is a fixed, self-hosted file with an integrity hash, and the CSP allows only same-origin scripts. Keep core-js updated through normal dependency management.

### 5. Prevent the next one

Name the process change that would have acted on the February warning.

Worked answer: Keep an inventory of every third-party script origin with an owner, subscribe owners to advisories and ownership changes for those origins, and require review before a new origin is added to a page or a CSP.

Deliverable: A table for W1 to W4 (exposed or not, why, action) and a one-paragraph third-party script policy.

Scope: The sites are invented. Real exposure depends on whether a page actually loaded the script during the malicious period, which only browser-side monitoring or server logs of script references can show; the payload was selective, so absence of a redirect in your own test proves little.

Articles and teaching material: CC BY 4.0. Original executable code: MIT. Attribution: Software Security.

## Sources

- [Sansec: Polyfill supply chain attack hits 100K+ sites (2024-06-25, with updates)](https://sansec.io/research/polyfill-supply-chain-attack)
- [Cloudflare: Polyfill.io now available on cdnjs, reduce your supply chain risk (2024-02-29)](https://blog.cloudflare.com/polyfill-io-now-available-on-cdnjs-reduce-your-supply-chain-risk/)
- [Cloudflare: Automatically replacing polyfill.io links with Cloudflare's mirror (2024-06-26)](https://blog.cloudflare.com/automatically-replacing-polyfill-io-links-with-cloudflares-mirror-for-a-safer-internet/)
- [polyfill-service README before the sale: the service reads the User-Agent header of each request](https://raw.githubusercontent.com/polyfillpolyfill/polyfill-service/0b850d54be987029775a0cd9b0fc5b5821803db2/README.md). Commit 0b850d5 (20 October 2023).
- [GitHub Advisory GHSA-5vgj-ggm4-fg62: pdoc embeds link to malicious CDN (CVE-2024-38526)](https://github.com/advisories/GHSA-5vgj-ggm4-fg62). Published 25 June 2024.
- [US Treasury: Treasury takes action against major cyber scam facilitator (2025-05-29)](https://home.treasury.gov/news/press-releases/sb0149)
- [MDN: Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity)
- [MDN: Content-Security-Policy script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/script-src)