Contents
Introduction
The cooldown, Hardened Mode, and CodeQL from the supply chain series all address dependencies entering through the npm registry. But the npm registry is not the only channel through which a modern web app pulls in external code.
<script src="https://cdn.example.com/widget.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/...">
A single <script> or <link> tag like this completely bypasses npm’s gates. The large-scale attack in 2024 that exploited this channel was the Polyfill.io incident. This article covers that incident and Subresource Integrity (SRI), the browser-level defense mechanism against it.
The Risk: CDN as a Single Point of Trust
The moment <script src="https://cdn.example.com/widget.js"> appears on a page, the following assumptions are in play:
- The
cdn.example.comdomain is under the control of an operator we trust - That operator’s infrastructure is not compromised
- That operator does not intentionally insert malicious code
If any one of these assumptions breaks, externally controlled code runs in the browser of every user who visits our page. And these assumptions break more often than most people expect.
- Domain sale or acquisition: The original operator sells the domain; the new owner runs it maliciously
- DNS/CDN compromise: The domain stays the same but the content served is tampered with
- Maintainer sabotage: Malicious code is intentionally inserted
- CDN infrastructure compromise: Content is tampered with without the operator’s knowledge
The most dangerous aspect — unlike npm — is that CDN responses are fetched on every request, so a time-based defense like cooldown does not apply. From the moment content is tampered with, every user is affected immediately.
Real-World Case: Polyfill.io (2024)
Polyfill.io was once a de facto standard polyfill CDN. Over 100,000 websites used the service with a single line: <script src="https://cdn.polyfill.io/v3/polyfill.min.js">.
Here is how the incident unfolded:
- February 2024: Chinese company Funnull acquires the
polyfill.iodomain and GitHub account - June 24, 2024: Sansec discloses that cdn.polyfill.io is injecting malicious code onto users’ mobile devices
- The malicious code acted selectively — it went dormant on some devices, time zones, and admin pages, and only activated for regular users (to evade detection)
- Over 100,000 sites affected — including JSTOR, Intuit, Booking.com, and others
- Cloudflare rewrote cdn.polyfill.io in real time to point to a clean version; Namecheap placed the domain on hold
The essence of the incident is simple:
The operator had changed, but
<script>tags were still pointing to the same URL.
Sites that had not touched a line of their own code were infected all at once. What the incident demonstrated is that “the trustworthiness of an external host can change over time, and we have no way to automatically detect that.”
How Subresource Integrity Works
Subresource Integrity (SRI) is a W3C standard that addresses this problem at the browser level.
The principle is straightforward. Include a hash of the expected content in the <script> or <link> tag, and the browser will only execute or apply the resource if the downloaded content’s hash matches.
<script
src="https://cdn.example.com/widget.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"
></script>
integrity: Hash of the legitimate content (sha256, sha384, or sha512)crossorigin: CORS mode required for SRI verification
If cdn.example.com is tampered with and returns different content, the browser detects the hash mismatch and refuses to execute the script. A security error appears in the console and none of the script’s code runs.
What happened to sites that had SRI applied when the Polyfill.io incident occurred? Their hashes did not match the tampered content, so the script was automatically blocked. The site lost polyfill functionality, but user data was safe.
How to Apply It
1. Generate the Hash
Compute the hash like this:
curl -s https://cdn.example.com/widget.js \
| openssl dgst -sha384 -binary \
| openssl base64 -A
Or use an online tool like srihash.org.
2. Apply to HTML Tags
- <script src="https://cdn.example.com/widget.js"></script>
+ <script
+ src="https://cdn.example.com/widget.js"
+ integrity="sha384-<hash>"
+ crossorigin="anonymous"
+ ></script>
3. Automate with a Vite/webpack Plugin
If external assets are determined by a bundler, there are plugins that automatically insert SRI:
- Vite:
vite-plugin-sri3 - webpack:
webpack-subresource-integrity
With these plugins, hashes are computed at build time and injected into the HTML automatically. External CDN assets can also be downloaded once at build time to compute their hash.
When SRI Cannot Be Used: Google Fonts and Dynamic CSS
Google Fonts CSS is tricky to protect with SRI. The reason:
Google Fonts CSS responds dynamically based on the requesting browser’s User-Agent. The same URL returns different
@font-facerules to Chrome versus Safari.
A hash must be a fixed value, but the response changes with every request — applying SRI would cause verification failures every time.
Alternative: Self-Host the Fonts
The cleanest solution is to include the fonts in your bundle.
yarn add @fontsource/noto-sans-jp
// main.tsx
import '@fontsource/noto-sans-jp/400.css'
import '@fontsource/noto-sans-jp/700.css'
This approach brings several benefits:
- SRI is no longer needed (the external host is gone)
- The GDPR issue of users’ IPs being sent to Google is resolved
- Font loading is faster (removes the extra handshake between CDN and your domain)
- Works in offline environments
The same pattern is available as npm packages for virtually every Google Font — @fontsource/inter, @fontsource/roboto, and so on.
Limitations
- SRI does not prevent content from being tampered with. When content changes, the browser refuses to execute it — but the legitimate content stops working too. It is a trade-off: integrity at the cost of availability.
- Hashes must be updated with every version upgrade. If an external asset is updated, its hash must be updated as well. Without build-time automation, this becomes an operational burden.
- Cannot be applied to dynamic content. Google Fonts CSS, Tag Manager, advertising SDKs, and other assets that return different responses every time cannot be protected with SRI. Self-hosting is the only answer for these.
- SRI guards against CDN-level security incidents, but other trust assumptions tied to the CDN (such as cookie exposure through the domain) must be addressed separately.
Conclusion
SRI is a one-line change that neutralizes one of the largest categories of external code attack — CDN tampering — and delivers exceptional return on investment. The difference in impact between sites that had SRI applied and those that did not during the Polyfill.io incident was decisive.
Recommended steps:
- Audit all assets currently loaded from external hosts —
<script src="https://...">,<link href="https://..."> - Add SRI hashes to static content — can be automated at build time
- Switch dynamic content (Google Fonts, etc.) to self-hosting — fontsource or your own static hosting
- Automate with a bundler plugin — hashes update automatically whenever an external asset is updated
Whether user data is protected the next time a Polyfill.io-scale incident happens comes down to whether this one line was added today.
References
- Subresource Integrity (MDN)
- Polyfill.io CVE-2024-38526
- Sansec’s Polyfill.io incident analysis
- SRI Hash Generator (srihash.org)
- Fontsource — self-hosted font npm packages
Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!
App promotion
Deku.Deku created the applications with Flutter.If you have interested, please try to download them for free.