Polyfill.io Domain-Sale Runtime-Delivery Malicious-Redirect Supply-Chain Incident (2024-06-25)
- Name: Polyfill.io domain-sale runtime-delivery malicious-redirect supply-chain incident
- Date: 2024-06-25
- Protocol: Polyfill.io / downstream sites embedding
cdn.polyfill.io - Chain: Web / wallet-facing and checkout-facing browser front ends
- Category: software supply-chain compromise / release-provenance failure / runtime-delivery compromise / malicious redirect injection
- Loss: publicly unquantified; the observed payload redirected mobile visitors from
100,000+affected sites toward scam / betting / affiliate destinations rather than producing one clean onchain-drain total, but the incident was severe enough that Google began blocking ads on affected landing pages and Cloudflare performed emergency HTML rewrites for proxied sites - Summary: This incident did not begin with a hacked dApp domain or a poisoned npm publish. It began when the
polyfill.iodelivery domain changed hands and the same familiar script URL started serving different JavaScript than site owners thought they were embedding. In February 2024 the original Polyfill author publicly warned users to stop trusting the service after the domain moved to Funnull. By late June, Sansec caughtcdn.polyfill.ioinjecting mobile-only malware into sites that still loaded it. The observed payload fetched follow-on code from fake-Google-Analytics infrastructure such aswww.googie-anaiytics.com, avoided admins and some analytics environments, and redirected selected users toward scam / betting / affiliate endpoints includingkuurza.com/redirect?from=bitget. The preserved lesson is sharper than genericthird-party JS is risky: the real security object was no longer the open-source Polyfill project or the script URL written into source control, but the exact runtime response resolved from a domain whose ownership, CDN path, and generated output had drifted. - Trigger / entry point:
- In February 2024, ownership of the
polyfill.iodomain transitioned to Funnull. - Original author Andrew Betts warned users on
2024-02-29to removepolyfill.ioimmediately and said he had no influence over the domain sale. - Cloudflare considered the ownership transfer enough of a supply-chain risk that it launched a
cdnjsmirror on2024-02-29, before public confirmation of malicious payload injection. - On
2024-06-25, Sansec published evidence thatcdn.polyfill.iowas serving malicious JavaScript to mobile visitors on sites embedding the service. - On
2024-06-26, Cloudflare said its Page Shield telemetry had already observed the associated indicators of compromise as early as2024-06-08 15:23:51. - On
2024-06-27, Namecheap put thepolyfill.iodomain on hold, but by then the incident had already demonstrated that the same canonical URL could become attacker-controlled delivery infrastructure.
- In February 2024, ownership of the
- Root cause:
- The decisive failure was a release-provenance and runtime-delivery failure, not a smart-contract bug and not even a classic package-registry compromise.
- Sites trusted the domain name
cdn.polyfill.ioas if it were a stable security identity, but the party controlling that domain and the exact JavaScript delivered at request time had changed. - Because Polyfill responses were dynamically generated from request headers, the executed artifact was never just
the open-source repository; it was whatever code the live service chose to return for that user, browser, device class, time window, and hostname context. - This also sharpens the boundary between canonical-domain runtime-byte integrity and third-party runtime allowance and integrity pinning: the downstream sites’ own domains often stayed honest, but those domains no longer proved the exact signer-facing or checkout-facing runtime bytes because they still delegated execution to a mutable third-party service identifier.
- The preserved invariant is
a shared browser dependency fetched from a canonical third-party URL must not be able to silently change into attacker-controlled redirect or wallet-phishing logic after ownership or delivery-path drift.
- Exploit path:
- A huge number of sites continued embedding JavaScript from
cdn.polyfill.io, treating the domain and service name as the trusted identity. - After the domain changed hands, downstream sites still loaded the script automatically with no local pinning to an audited immutable artifact.
- The service returned dynamically generated JavaScript instead of a single immutable bundle, letting the operator vary payloads by request context.
- The observed malicious branch targeted mobile visitors, skipped some admin cookies and analytics-detection contexts, and fetched follow-on code from fake-analytics infrastructure such as
www.googie-anaiytics.com. - When the redirect conditions matched, the payload sent users to external destinations such as
https://kuurza.com/redirect?from=bitget. - That turned any unaffected site still embedding
cdn.polyfill.iointo a browser-side redirect / phishing surface without compromising the site’s own repository, DNS, or application servers. - Defenders responded by replacing links with safe mirrors, warning site owners, and ultimately suspending the domain, but the incident had already shown that
same script tag in source controldid not meansame runtime code in user browsers.
- A huge number of sites continued embedding JavaScript from
- What the attacker needed to be true:
- Many sites had to keep using the canonical
polyfill.ioURL instead of self-hosting or pinning a known-good artifact. - Developers had to treat domain continuity as if it guaranteed code continuity.
- The service had to remain able to generate per-request JavaScript so malicious behavior could be selective and harder to observe.
- The malicious logic needed enough environmental awareness to avoid obvious breakage, admin sessions, and some analytics instrumentation.
- Incident response had to lag behind the already-running runtime-delivery path long enough for the redirect campaign to spread across a large installed base.
- Many sites had to keep using the canonical
- Technical anatomy worth remembering:
- This is the clean
service identifier stayed the same, runtime artifact changed underneath itcousin of software supply-chain compromise. - Inside the broader supply-chain and release-provenance cluster, Polyfill.io is the clearest
same familiar runtime URL, different operator-resolved bytesexemplar: downstream sites kept their own real domains and source-controlled script tags, but the third-party service identifier they trusted stopped resolving to the same operator or the same runtime meaning. - The observed malicious code was mobile-biased, contained anti-analysis and anti-admin checks, and delayed execution when analytics tooling was present.
- Sansec documented follow-on fetches from
www.googie-anaiytics.comand redirect logic towardkuurza.com/redirect?from=bitget. - Cloudflare treated the situation as severe enough to rewrite
polyfill.ioreferences on proxied sites to a safecdnjsmirror in real time. - The incident is especially useful for the corpus because it preserves a branch where the dangerous drift sat less in package bytes than in the authority to answer a well-known runtime URL after ownership change.
- That makes it a strong adjacent exemplar for wallet-facing crypto sites: even when a dApp’s own domain and contracts stay uncompromised, any browser-fetched dependency that can still mutate at request time is part of the signing and payment security boundary.
- This is the clean
- Representative infrastructure / indicators:
- Shared runtime URL:
https://cdn.polyfill.io/... - Safe emergency mirror created by Cloudflare:
https://cdnjs.cloudflare.com/polyfill/... - Malicious follow-on infrastructure observed by Sansec / Cloudflare:
www.googie-anaiytics.com - Observed redirect endpoint:
https://kuurza.com/redirect?from=bitget - Namecheap mitigation date:
2024-06-27domain hold
- Shared runtime URL:
- Response / mitigation:
- Original Polyfill author Andrew Betts publicly told users to remove
polyfill.io. - Cloudflare launched a safer mirror on
cdnjsin February 2024 after the ownership transition raised supply-chain concerns. - After the June incident became public, Cloudflare enabled automatic HTML rewriting for proxied sites so
polyfill.ioreferences would resolve to the safe mirror instead. - Cloudflare’s emergency rewrite preserved the original query string and mapped the request to the matching
cdnjspolyfill path, but it deliberately refused to activate on pages already serving a Content Security Policy header so the safety patch would not silently break runtime policy. That is a useful operational lesson: emergency edge-side mitigation is part of the browser trust boundary, but it cannot be the only rollback plan. - Free-plan Cloudflare zones had the rewrite enabled automatically, while paid zones could turn it on manually. The meaningful recovery object was therefore the live browser fetch path, not the repository state.
- Google started blocking ads for sites that still used the compromised service.
- Namecheap later placed the
polyfill.iodomain on hold, temporarily stopping that delivery path. - The practical lesson for site owners was not merely
wait for the domain to be fixed, butremove the dependency entirely or pin a trusted mirror, because the authority boundary had already proven unstable. - This is the cleanest
same familiar URL, different runtime bytesexemplar for exact production artifact attestation and rollback discipline because the meaningful rollback proof had to live at the execution layer — what users actually fetched from that URL or its replacement — rather than at the level of source familiarity or script-tag continuity.
- Original Polyfill author Andrew Betts publicly told users to remove
Pattern tags
- software-supply-chain-compromise
- release-provenance-failure
- Same familiar runtime URL, different operator-resolved bytes:
cdn.polyfill.iostayed in source control while the authority behind that URL and its per-request responses changed underneath site owners - Dynamic browser-targeted delivery, not one immutable bad release: the service could vary code by device class, hostname, cookies, and runtime context, which made selective malicious redirects easier to hide than a single poisoned bundle
- Emergency mitigation had to happen at the live execution layer: mirrors, HTML rewrites, and domain holds mattered because the dangerous object was the runtime response users fetched, not just the repository history or script tag text
Why the exploit worked
- Site owners trusted the service identifier
cdn.polyfill.ioas if it were a stable security identity. After the domain changed hands, that trust remained in production even though the operator and live response authority underneath the same URL had changed. - Polyfill was never just one immutable artifact. Because the service generated JavaScript at request time, the operator could make malicious behavior selective by device, environment, and timing, which sharply reduced the chance that routine developer checks would see the same payload normal visitors saw.
- Downstream domains staying honest was not enough. A legitimate site could still become a phishing or redirect surface because it delegated browser execution to a mutable third-party runtime that sat inside the same trust boundary as wallet prompts, checkout logic, and session state.
- The installed base was enormous, so one compromised runtime URL turned into a many-site fanout event without any need to breach each site’s repo, CI/CD, or DNS.
- Response lag was structural: until mirrors, rewrites, and registrar action changed what browsers actually fetched,
same script tag still in source controlkept resolving to dangerous runtime behavior.
Detection and audit pivots
- Inventory every third-party script URL that can still change behavior at request time, especially services that tailor output by user agent, query string, cookies, or hostname. Those are execution authorities, not static dependencies.
- Treat domain transfer, operator change, or hosting-path drift for shared browser dependencies as a production security event even before confirmed malware appears.
- Use browser-side telemetry to watch the exact follow-on URLs and script behaviors users actually load; Cloudflare’s Page Shield seeing related IoCs from
2024-06-08shows how client-observed evidence can beat source-level assumptions. - Compare
script tag in source controlagainstruntime bytes fetched in real sessions, with special attention to mobile-only or environment-selective branches that won’t appear in ordinary desktop spot checks. - Rehearse emergency replacement paths — mirrors, HTML rewrites, CSP changes, and hard removals — for high-fanout third-party runtimes before one of them goes hostile.
Failure-prevention checklist
- Inventory every third-party runtime URL that signer-facing, wallet-facing, auth-facing, or checkout-facing pages still execute after deployment. If a remote script can still change behavior without the site owner rebuilding, that URL is already part of the custody boundary.
- Remove dynamic shared polyfill / compatibility services from sensitive routes wherever possible. Prefer baking required compatibility code into one reviewed, self-hosted artifact instead of asking browsers to fetch mutable request-tailored JavaScript from a third-party domain at runtime.
- Treat domain transfer, operator change, CDN migration, or repository handoff for shared browser dependencies as an immediate production-security event. The safe default is quarantine or replacement first, investigation second.
- Require exact runtime-identity controls for remaining third-party scripts: immutable versioned URLs, integrity pinning where technically possible, explicit allowlists, and a documented answer for what object is actually being authenticated when the browser fetches that code.
- Refuse signer-critical dependence on services whose main feature is request-time variation by user agent, headers, cookies, or hostname unless the team can explain why that mutability is acceptable inside the wallet / payment trust boundary.
- Build emergency replacement paths ahead of time: safe mirrors, HTML rewriting, CSP tightening, kill switches, and route-local removal of compromised third-party code should all be rehearsed before one shared runtime goes hostile.
- Monitor the browser execution layer directly. Follow-on fetches, secondary script domains, mobile-only branches, redirect behavior, and unexpected script-byte drift should all be observable without trusting source-control assumptions.
- Verify rollback where users execute code, not only where maintainers publish statements. A safe mirror, domain hold, or code cleanup is incomplete until multi-region / multi-device checks confirm that live sessions no longer fetch attacker-controlled bytes.
- Keep compatibility dependencies out of the signer path unless they are boring enough to attest. The closer a script sits to wallet prompts, session state, or checkout logic, the less acceptable mutable third-party execution becomes.
Audit prompts
- Which exact third-party script URLs can still change browser behavior on wallet-facing, auth-facing, or checkout-facing pages after the main app deploy is complete?
- For each remote runtime dependency, what proves that the domain name, current operator, CDN path, repository custody, and browser-fetched bytes still describe the same trusted object?
- If one of those domains changed ownership tonight, who would notice first, what telemetry would fire, and how fast could the team cut over to a pinned local artifact or safe mirror?
- Do any signer-critical routes still rely on JavaScript that is generated dynamically by request headers, user agent, cookies, geo, or hostname context, and if so why is that compatible with runtime integrity?
- Can the team compare
script tag in source controlagainstexact bytes fetched by real users on desktop and mobileacross regions, or is runtime trust still mostly inferred from repository history? - Which browser-side controls would stop or at least sharply constrain a Polyfill-style
same familiar URL, different runtime bytesevent: SRI, CSP, import-map pinning, self-hosting, route isolation, or none? - What client-observed evidence would reveal a selective mobile-only redirect campaign before customers complain: follow-on domain telemetry, unusual redirect chains, ad-platform penalties, or synthetic mobile probes?
- If an emergency rewrite or mirror is deployed, how is functional equivalence and safety verified without silently carrying forward the compromised trust model under a new hostname?
- Which pages would remain safe if every mutable third-party script were disabled right now, and which routes still break because optional convenience code has become critical execution authority?
- Does incident response end when the malicious operator loses publish control, or only when live sessions everywhere demonstrably stop executing the bad runtime and any downstream phishing / approval exposure is contained?
Lessons that should already be standard
Same script URLis not evidence ofsame runtime artifact. A familiar<script src>can outlive the operator, the CDN path, and the actual JavaScript meaning behind it.- Domain transfers for shared browser dependencies are production-security events, not procurement trivia. Ownership drift can become signer-facing execution drift without any downstream redeploy.
- Dynamic request-time JavaScript is delegated release authority. If a service can tailor bytes by user agent, cookies, or hostname, then the browser is trusting that live operator every time a page loads.
- A real site can stay honest while its executed dependency turns malicious. Downstream repo integrity, DNS integrity, and unchanged HTML do not rescue users once runtime execution is delegated to mutable third-party code.
- Compatibility middleware is not outside the threat model just because it sounds boring. If it runs in the same page as wallet prompts or checkout state, it belongs to the same security boundary.
- Browser telemetry often sees the truth sooner than source review. Polyfill’s fake-analytics follow-on domains and mobile-only redirect logic were execution-layer facts that repository familiarity could not disprove.
- Rollback means changing what users fetch, not merely publishing a warning or a clean alternative. Mirrors, rewrites, registrar holds, cache verification, and live-session checks are part of the same recovery story.
- The healthy long-run pattern is to ship one attestable production artifact for sensitive routes. The more a team depends on mutable third-party runtimes, the more it is outsourcing signer trust to whoever controls those runtimes today.
Why this incident matters
- It is one of the clearest preserved release-provenance incidents where the important compromise boundary was the live runtime-delivery service itself, not an npm package publish and not a downstream site breach.
- It sharpens the corpus’s supply-chain branch by preserving the
same URL, different operator, different bytescousin alongside Copay’s build-time dependency poisoning, Ledger’s loader/CDN drift, and Solana web3.js’s malicious package publish. - For crypto-facing software, it is a blunt reminder that any third-party script URL capable of shaping wallet prompts, checkout state, or browser execution is part of the custody boundary even if it looks like boring compatibility middleware.
- It is also the cleanest current exemplar for third-party runtime allowance and integrity pinning: the sharper design failure was not only
the runtime bytes drifted, butwallet-facing and checkout-facing pages were still allowed to execute mutable remote code from a third-party service identifier whose ownership and request-time output could change underneath the same script tag. - It is a useful boundary case for canonical-domain runtime-byte integrity too: the downstream sites’ real domains still mattered, but those domains could not authenticate the real runtime once they embedded mutable third-party execution under the same browser trust boundary.
- It is also a strong recovery-side case for exact production artifact attestation and rollback discipline: the meaningful
fixwas never justa clean upstream repository exists again, butreal users on real pages have stopped resolving attacker-controlled bytes from the once-canonical runtime path.
Internal linkages
- Core recurring pattern: software-supply-chain-compromise.
- Sharper recurring pattern: release-provenance-failure.
- Positive-design recovery companion: exact-production-artifact-attestation-and-rollback-discipline.
- Narrower browser-policy companion: third-party-runtime-allowance-and-integrity-pinning.
- Broader browser-delivery companion: canonical-domain-runtime-byte-integrity — useful when the sharper follow-up is what a downstream site’s real domain should have proved before any embedded third-party runtime on that signer-facing or checkout-facing page was trusted at all.
- Useful contrast: ledger-connect-kit-npm-supply-chain-wallet-drainer-2023-12-14 — Ledger preserved the
same real site, different loader-resolved byteswallet-drainer cousin, while Polyfill preserved thesame familiar runtime URL, different operator-resolved bytescousin. - Useful contrast: badgerdao-cloudflare-worker-injected-approval-drain-2021-12-02 — Badger preserved the single-site edge-delivery compromise cousin, while Polyfill preserved the shared third-party runtime-URL cousin.
- Useful contrast: copay-event-stream-flatmap-stream-transitive-dependency-backdoor-2018-11-26 — Copay preserved build-time artifact substitution, while Polyfill preserved live browser-delivery drift under the same familiar script identifier.
Control surface
-
The real leverage sat in domain ownership, request-time JavaScript generation, third-party script embedding, runtime artifact delivery, browser-side follow-on fetches, and the exact browser-fetched bytes users executed.
-
The decisive trust failure was not
developers reviewed the wrong repository; it wasdevelopers trusted a stable-looking URL even after the authority behind that URL and its runtime responses changed. -
The emergency controls mattered too: mirror cutovers, HTML rewrites, CSP interactions, and registrar action all became part of the live control surface because they determined what code users actually fetched while source-tree cleanup lagged.
-
In this family, remote script URLs and service identifiers are custody-adjacent infrastructure. If they can drift after deployment, then browser execution can become attacker-controlled without any redeploy from the site owner.
-
Sources:
- https://sansec.io/research/polyfill-supply-chain-attack
- https://blog.cloudflare.com/polyfill-io-now-available-on-cdnjs-reduce-your-supply-chain-risk/
- https://blog.cloudflare.com/automatically-replacing-polyfill-io-links-with-cloudflares-mirror-for-a-safer-internet/
- https://x.com/triblondon/status/1761852117579427975
-
Last reviewed: 2026-07-29 UTC