Subresource Integrity in Practice: Securing CDN Scripts Against Tampering
AI generated
JS
() =>
JavaScript · Web Security · Supply Chain
Subresource Integrity in Practice
securing CDN scripts against silent tampering

Anyone embedding JavaScript or CSS from an external CDN is trusting that the provider will never be compromised. Subresource Integrity replaces that blind trust with a cryptographic checksum the browser verifies against the actual file content before execution, making silent supply chain attacks technically visible.

16 min read integrity · crossorigin · SHA-384 All modern browsers

1. Why embedding a CDN is a supply chain risk

Subresource Integrity solves a problem many teams underestimate: as soon as an application embeds a script from a foreign CDN, for example <script src="https://cdn.example.com/lib.js">, the operator of that CDN has full control over the code that gets executed. If the CDN is compromised, whether through a stolen API key, a vulnerability in the vendor's build system, or a malicious employee, every website embedding that script can suddenly execute tampered code, without a single line of its own source code being changed.

Such incidents are not theoretical: several large supply chain attacks in recent years ran exactly through this path, a compromised npm package or a tampered CDN script infected thousands of downstream websites simultaneously. Subresource Integrity counters this risk with a simple principle: the browser still loads the script from the CDN, but only executes it if the computed hash exactly matches the hash stated in the HTML. Any tampering, even a single altered byte, results in an immediate block.

For shop operators this risk is especially relevant, because checkout pages frequently load several third party scripts at once, for example for payment processing, fraud detection and tracking. A single compromised CDN script in that chain is enough to capture every customer's card data, without the shop operator ever having changed a single line of their own code.

2. How the integrity attribute works technically

For developers working with Subresource Integrity for the first time, it is worth a brief look at the underlying specification from the W3C Web Application Security Working Group, which authoritatively defines the exact verification algorithm and supported hash methods.

The integrity attribute is set on script and link elements and contains a base64 encoded cryptographic hash of the expected file, prefixed with the algorithm used. Supported are sha256, sha384 and sha512, with sha384 being the common standard in practice, because it offers a good tradeoff between cryptographic strength and hash length in the HTML.

Once the browser has downloaded the resource, it computes the hash itself over the received byte stream and compares it against the value in the integrity attribute. If they match, the script executes or the stylesheet applies. If they diverge, the browser discards the resource entirely and reports an error in the console, without even a fragment of the tampered code being executed. This mechanism works entirely client side and requires no server configuration on the CDN's part.


<!-- SRI-protected CDN script with crossorigin and integrity hash -->
<script
  src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"
  integrity="sha384-6e6dNn0hPzP0X8qb9y1L8qBk8G3vJm3lJt2R6t9E5Qx1Zz3lQxq2s9jY7v3H5rV1"
  crossorigin="anonymous"
></script>

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.min.css"
  integrity="sha384-4d3P6q2X9y5t7Z8k1J0nQ2wR5o3Vc7Tt6y8B9x2K1L4M0N7P3q6R9t2Y5w8Z1a3"
  crossorigin="anonymous"
>

3. Generating hashes: openssl, npm tools and build integration

The correct hash can be computed with standard tools via openssl, by first downloading the file with curl and then hashing it. It is important that the hash is computed over exactly the bytes the browser actually receives, including any compression or encoding differences some CDNs deliver depending on the Accept-Encoding header.

For npm based projects, specialized packages like ssri exist that generate the hash directly from locally installed node_modules files, ensuring the version in the hash exactly matches the version in the package.json lockfile. This automation matters because a manually computed hash goes stale quickly once a package update lands, and a forgotten hash update means the application completely breaks after every dependency update.


# Compute SRI hash manually with openssl and base64
curl -s https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js \
  | openssl dgst -sha384 -binary \
  | openssl base64 -A

# Or with the ssri npm package against a local file
npx ssri generate node_modules/chart.js/dist/chart.umd.min.js --algorithms sha384

# Verify an existing hash against the currently published CDN file
curl -s https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js \
  | openssl dgst -sha384 -binary | openssl base64 -A

A recurring verification step in the CI pipeline, checking the hash stored in the HTML against a freshly downloaded copy of the file, reliably reveals if a CDN provider has unexpectedly swapped the content behind a versioned URL, something that should never happen but occasionally does in practice.

4. The crossorigin attribute and CORS prerequisites

A frequently overlooked building block of Subresource Integrity is the crossorigin attribute, which must be set whenever the resource is loaded from a different origin. Without crossorigin="anonymous", the browser denies JavaScript access to the response data needed for hash computation, even if the server is technically reachable, and the integrity check fails outright.

The CDN server must therefore deliver matching CORS headers such as Access-Control-Allow-Origin: *, otherwise the browser already refuses to load the resource in combination with crossorigin. Most large CDNs like jsDelivr, cdnjs or unpkg deliver these headers by default, but a self hosted internal CDN must explicitly configure this, otherwise Subresource Integrity does not work for internal resources.

5. Versioning: why SRI prevents auto-updates

A central effect of Subresource Integrity, often misunderstood as a downside, is that it completely blocks automatic updates through URLs without a fixed version, for example cdn.example.com/lib/latest/app.js. Since the hash matches exactly one file version, every new version of the CDN script requires an explicit new hash in the HTML. That is intentional: without this binding, the CDN operator could swap the file behind the same URL at any time, and Subresource Integrity would only protect the first version.

For projects that rely on fast security updates of third party libraries, that means an additional maintenance step: every update of a CDN dependency requires recomputing the hash in the same commit. This coupling forces a deliberate, reviewable update decision instead of a silent, uncontrolled swap in the background, which significantly improves the traceability of third party code over time.

6. Fallback strategies for failed integrity checks

If the integrity check fails, the browser does not load the resource, but it also runs no automatic fallback, the application itself must catch that via the onerror event. A robust strategy loads a locally hosted copy of the same version when a CDN script fails, so the application does not completely break just because an external CDN is temporarily unreachable or compromised.

It is important not to view the fallback purely as an availability measure, but also as a safety net: a locally hosted script coming from the same trusted build pipeline is not subject to the same supply chain risks as the external CDN. For critical applications, such as checkout flows in an online shop, a local fallback with no external dependency is often the more robust baseline decision, with CDN embedding used only as a performance optimization through caching.


// Fallback to a locally hosted copy if the SRI-protected CDN script fails to load
function loadWithFallback(cdnSrc, cdnIntegrity, localSrc) {
  const script = document.createElement('script');
  script.src = cdnSrc;
  script.integrity = cdnIntegrity;
  script.crossOrigin = 'anonymous';
  script.onerror = () => {
    console.warn('CDN script blocked or unreachable, loading local fallback');
    const fallback = document.createElement('script');
    fallback.src = localSrc; // no integrity needed for same-origin trusted build
    document.head.appendChild(fallback);
  };
  document.head.appendChild(script);
}

// Usage: checkout page loads a payment SDK with automatic local fallback
loadWithFallback(
  'https://js.paymentprovider.example/v3/sdk.js',
  'sha384-9y1P0X8qb9y1L8qBk8G3vJm3lJt2R6t9E5Qx1Zz3lQxq2s9jY7v3H5rV1Z8k1J0n',
  '/assets/vendor/payment-sdk-fallback.js'
);

For checkout critical scripts, it is advisable to place the fallback call as early as possible in the load process and only start the rest of the checkout flow after either the CDN script or the local copy has loaded successfully. A timeout of a few seconds prevents customers from waiting unnecessarily long for a response when a CDN is slow but not fully down.

7. Maintaining SRI automatically in build pipelines

Manually maintained hashes go stale quickly in practice, so productive setups integrate hash computation directly into the build process. Webpack plugins like webpack-subresource-integrity automatically compute hashes for every bundled asset and write them into the generated HTML files, with no manual intervention needed on every deployment.

For projects that still reference external CDN scripts, a dedicated build script that automatically pulls new hashes on every dependency update and opens a pull request with the change is worth the investment. This automation prevents the most common practical mistake with Subresource Integrity: a stale hash that breaks the application completely after a CDN side update, because nobody refreshed the hash in time.

8. Common mistakes when introducing SRI

The most common mistake is forgetting the crossorigin attribute, which makes the integrity check fail outright regardless of a correct hash. A second mistake is using latest or similarly unversioned URLs together with a static hash, which is guaranteed to fail after the next CDN side update, as soon as the file content behind the same URL changes.

A third, more subtle mistake concerns computing the hash over a compressed rather than the uncompressed transfer. Some CDNs deliver differently compressed variants depending on Accept-Encoding, but the hash must exactly match the byte stream the given client actually receives. In practice it is advisable to pull the hash directly from the package's official distribution, for example via ssri against the local node_modules file, rather than computing it manually against a live fetched CDN URL.

9. SRI compared to other CDN protections

Alongside Subresource Integrity, other approaches exist to limit CDN risk. The following table compares them directly.

Measure Protects against Effort Limits
Subresource Integrity Tampered file content at the CDN Hash maintenance per version No protection against an entirely new, malicious URL
Content Security Policy (script-src) Scripts from disallowed domains Header maintenance, domain list Does not protect against tampering of the allowed domain itself
Self hosted copy Every external CDN risk entirely Own hosting and update responsibility No more automatic CDN caching

In practice, combining Subresource Integrity with a restrictive Content Security Policy is the most robust solution: the Content Security Policy limits allowed domains, Subresource Integrity ensures that even an allowed domain cannot serve tampered content without the browser noticing.

Mironsoft

Supply chain security, CDN hardening and build pipeline automation

Secure CDN scripts against silent tampering?

We review existing CDN embeds, add missing integrity hashes and automate hash maintenance directly in your build pipeline, including a fallback strategy for critical application parts.

CDN audit

Analysis of all externally embedded scripts and stylesheets

Hash automation

Build pipeline integration for automatic hash updates

Fallback design

Local fallback strategy for critical checkout and login flows

For Magento and Hyvä shops with several externally embedded payment and tracking scripts, a central module that maintains all SRI hashes in a single place in the layout XML, rather than scattering them across various templates, is worth the investment. It simplifies later audits and makes visible which external dependencies are actually active in the checkout.

10. Summary

Subresource Integrity closes a gap many teams only take seriously after a concrete incident: blind trust in external CDN providers. Through the integrity attribute with a SHA-384 hash and the mandatory crossorigin attribute, the browser ensures that only exactly the expected file content is executed, any tampering at the CDN becomes technically visible and gets blocked.

The version bound nature of Subresource Integrity forces deliberate, reviewable updates instead of silent background changes, justifying the additional maintenance effort. Build pipeline integration through tools like webpack-subresource-integrity or ssri reduces that effort to a minimum and makes Subresource Integrity one of the few security measures that can be almost fully automated without slowing down development speed.

Subresource Integrity in Practice — The Essentials at a Glance

Four building blocks that add up to resilient protection against tampered CDN scripts in production.

integrity attribute

A SHA-384 hash in the integrity attribute blocks tampered files before they execute.

crossorigin required

Without crossorigin="anonymous" the integrity check fails outright for a foreign origin.

Versioning

SRI prevents auto-updates via unversioned URLs, every new version needs a new hash.

Automation

Build pipeline tools like webpack-subresource-integrity or ssri maintain hashes with no manual effort.

11. FAQ: Subresource Integrity in Practice

1What is Subresource Integrity?
A hash in the HTML that the browser checks against the downloaded file, blocking execution on mismatch.
2Why is crossorigin required?
Without it, the browser denies access to response data, causing the check to fail.
3Which hash algorithm?
SHA-384 is standard, SHA-256 and SHA-512 are also supported.
4Does SRI work with latest-style URLs?
No, unversioned URLs regularly invalidate the hash. Fixed versions are required.
5How do I generate the hash?
With openssl against the file, or ssri against the local node_modules file.
6What happens on a failed check?
The browser blocks the resource; implement a fallback via onerror.
7How do I automate SRI?
With Webpack plugins like webpack-subresource-integrity in the build pipeline.
8Does SRI protect against new malicious URLs?
No, a Content Security Policy with script-src handles that instead.
9Do I need SRI for self hosted assets?
Not strictly, but useful as extra protection for hosting infrastructure.
10How often must the hash be updated?
On every update of the referenced library.