SRI Hash Third-Party Scripts Without Breaking Checkout

SRI Hash Third-Party Scripts Without Breaking Checkout

An SRI hash on a third-party script does one thing. It tells the browser to compare the bytes it just downloaded against a digest you supplied, and to refuse to execute the file if they do not match. It is not monitoring. It does not watch the script for changes, it does not alert anybody, and it does not make a tag safe to keep on a card form. Used well on a payment page it is one of the cheapest controls available. Used carelessly it is an outage waiting for a Friday afternoon.

Most guides on this subject show the same jQuery CDN snippet and stop there. That example is harmless because nothing breaks when a decorative library fails. A checkout is a different problem, and the differences are the whole job.

What an SRI hash on a third-party script actually guarantees

The mechanics take one paragraph. You add an integrity attribute containing an algorithm prefix and a base64 digest, plus crossorigin=”anonymous” so the response is fetched with CORS. The browser downloads the file, decompresses it, hashes the result, and compares. Match, and the script runs. No match, and the script is discarded before a single line executes. Subresource Integrity works on script elements and on stylesheet link elements, nothing else.

The guarantee is narrow and worth stating precisely: this exact file, at this exact URL, is byte-identical to the version you approved. That is all.

What it never covers is longer than what it does. Inline script blocks cannot carry an integrity attribute. First-party code injected server-side into your own templates has no external file to hash. Anything the pinned script fetches after it runs is outside the check entirely, because SRI applies to the element you wrote, not to the network activity that follows. An iframe cannot be pinned. A font file pulled in by a pinned stylesheet is not pinned either; the stylesheet is, the font is not.

Fail-closed behaviour is the reason this control matters more on a payment page than anywhere else, and also the reason it is dangerous there. On a marketing page a digest mismatch means a missing carousel. On a payment page, if the pinned script is the one that renders or validates the card fields, a mismatch means customers see a dead form. The server is fine. The logs are clean. Orders simply stop. The deeper explanation of the mechanism sits in our guide to pinning the scripts you cannot control; what follows here is the operational layer on top of it.

Generating the hash correctly, without guesswork

Hash the file you are actually loading, from the URL you are actually loading it from. Not the file in the vendor’s repository, not the one in the documentation.

The one-liner most developers can run without installing anything:

curl -s https://cdn.example.com/sdk/2.14.1/sdk.js | openssl dgst -sha384 -binary | openssl base64 -A

If you already have the file locally, drop the curl and feed it the path instead. Where you cannot download the file at all, because it sits behind a signed or session-bound URL, open the checkout in a browser and compute the digest in the console with the Web Crypto API: fetch the URL, take the response as an array buffer, pass it to crypto.subtle.digest(‘SHA-384’, buf), and base64-encode the result. Web generators are fine as a cross-check. They are not fine as the only check, because you have no idea which region or which edge node served them their copy. Generate your own, compare, and if the two disagree find out why before you ship anything.

On algorithm choice: SHA-256, SHA-384 and SHA-512 are all accepted. SHA-384 is the sensible default because it is what nearly every vendor publishes and what the examples in the MDN documentation on Subresource Integrity use. None of the three is a weak point in this system. Do not spend a meeting on it.

The traps are all about byte-exactness. A CDN that minifies on the fly, injects a build or version comment, rewrites asset paths per region, or serves a geo-variant bundle will hand different visitors different bytes, and your digest will match for some of them and not others. That failure mode is worse than a straightforward mismatch, because it looks like an intermittent checkout bug.

Compression is not the problem people expect. Gzip and Brotli are irrelevant, because the browser hashes the decompressed payload. Edge transformation and per-region delivery are the real hazards. Test from more than one network before you trust a pin.

Which checkout scripts are worth pinning, and which are not

Good candidates share one property: the URL is versioned and the file at it never changes.

  • Immutable library URLs that carry a version number in the path.
  • Vendor bundles you have taken a copy of and now serve yourself.
  • Stylesheets on a fixed path that you control.

Poor candidates are everything that updates itself by design. Tag managers. Analytics and consent loaders. Chat widgets. Most payment SDKs published on a floating latest or unversioned path. Pinning those produces a digest that is correct for about as long as it takes the supplier to deploy.

Then there is the loader problem, which generic guides almost never mention. A large share of the scripts a checkout loads are not the functional code at all. They are a two or three kilobyte bootstrap whose only job is to work out which real payload to fetch at runtime. Pin the bootstrap and you have pinned the wrapper. The second request, the one carrying the behaviour that actually touches your page, is unhashed and unverified. Recording that as integrity assurance in a PCI script inventory is not an accounting error, it is a false statement about your own controls.

Ticketmaster UK in June 2018 is the instructive case on both counts. The skimmer arrived through a modified chat support script from a supplier, running on pages where customers entered card details. A pinned, versioned copy of that file would have failed to execute rather than harvesting cards. A self-updating loader, which is what most chat widgets ship, would not have been pinnable in the first place. The pattern repeats across the major incidents of the last decade.

For the unpinnable ones, the honest answers are not technical. Take them off the payment page. Move the card inputs into hosted payment fields or a provider iframe so the fields are not in your page’s scripting context at all. Constrain whatever genuinely must remain with a tight allowlist, as set out in our working Content Security Policy for a checkout page. A chat widget has no business on the page where card numbers are typed.

One more limit, because it decides where pinning belongs in a defence plan. SRI has nothing to offer against injected first-party code. In the Magento 1 mass compromise of September 2020, which Sansec reported as affecting roughly 2,000 stores over a single weekend, skimmer code was written into the merchants’ own pages server-side. There was no external file to hash. Pinning sits alongside platform patching, admin access control and payment-page change detection. It does not replace any of them.

Surviving supplier updates without a dead checkout

The integrity attribute accepts more than one digest. Separate them with spaces and the browser will accept content matching any of them, which gives you a clean rotation window:

integrity=”sha384-OLDHASH sha384-NEWHASH”crossorigin=”anonymous”

Hold both for the length of the supplier’s rollout, then trim back to one. Two points of detail that catch people out. Keep both digests on the same algorithm: where a browser is offered hashes of differing strength it selects the strongest algorithm present and ignores the rest, so mixing a sha256 and a sha384 entry does not give you two accepted versions. And do not leave the stale hash in place indefinitely, because an accepted old version is an accepted old version.

Pin to a versioned URL rather than a floating one wherever the vendor offers it. That single decision converts supplier updates from a surprise into a change you schedule.

Plan for the failure you cannot see. SRI violations do not arrive at your CSP reporting endpoint in most browsers. They appear as console errors in the visitor’s browser, where nobody at the merchant is looking. Front-end error capture, or a scheduled synthetic journey that completes a real test transaction through the live payment flow, is what turns a mismatch into an alert instead of a quiet revenue hole.

Process hygiene closes the gap. Find out who at the supplier announces a release, get on that list, and agree how much notice you need. Then decide how long your staging verification takes, and write that number down, because it determines whether you can hash and test before their change reaches production or only after.

The mistakes that take a payment page down

Four of them, in rough order of how often they occur.

A missing Access-Control-Allow-Origin header on the supplier’s host. This is the most common cause of “SRI broke our site”, and the hash is usually perfect. The integrity attribute requires a CORS-enabled response and crossorigin=”anonymous”; if the vendor serves the file without the header, the browser blocks the script regardless of your digest. Check the response headers before you write a hash, not after the phones start.

Pinning the script that renders or validates the card form, with no fallback and no alerting. If you do nothing else from this article, do not do that.

Copying a hash from documentation, a blog post or a Stack Overflow answer. Vendor docs go stale, and the file at the URL may have moved on since the page was written.

Treating SRI as finished. Pins age. Inventories rot. Marketing adds a tag through the tag manager in week three and nobody re-checks the payment page until an assessor asks. Our walkthrough of how to tell whether a checkout has been skimmed exists largely because of that last pattern.

Where SRI sits inside PCI DSS 4.0 requirements 6.4.3 and 11.6.1

Requirement 6.4.3 asks that every script loaded and executed on the payment page is authorised, that its necessity is justified in writing, and that its integrity is assured. Hash-pinning is one credible way to evidence the integrity element, for the scripts where pinning genuinely applies. It says nothing about authorisation or justification, and it does nothing at all for the loaders discussed above.

Requirement 11.6.1 is a different control: a change and tamper detection mechanism that alerts on unauthorised modification to the HTTP headers and the content of the payment page, evaluated at least every seven days or on a frequency set by a targeted risk analysis. SRI does not do this. It fails a request in a visitor’s browser; it does not tell you anything happened. Pair it with payment-page monitoring, and read the detail of both requirements in the plain-language breakdown of 6.4.3 and 11.6.1 or in the standard itself, published by the PCI Security Standards Council.

Both have been mandatory since 31 March 2025. That date is why the instruction to pin your scripts usually arrives from an assessor or a scan report rather than from engineering.

A realistic combined setup for a small merchant looks like this: cut the payment-page script inventory to the minimum, move card entry into hosted fields, apply SRI to the handful of files that are genuinely versioned and immutable, run CSP in report-only mode first to find out what you are actually loading, and keep a short manual audit on a weekly cadence. None of that requires a product purchase.

A maintenance routine you can keep up

Keep one record per script: file, host, owner, written business justification, pinning status, digest, last verified date. A spreadsheet is adequate. What matters is that the owner column names a person, not a department.

A cadence that survives contact with a release schedule: verify the pins on every deployment, review the whole payment-page inventory once a month, and re-hash on any supplier release notice. Before a hash change ships, put the new digest through staging and complete a test transaction on the real payment flow. A card form that renders is not evidence that a payment completes.

And know when to stop. If a tag cannot be pinned and cannot be justified in one sentence, delete it from checkout. Removal costs an afternoon. Engineering around it costs for ever.

The short version

SRI guarantees one file at one URL is byte-identical to the copy you approved, and it enforces that by refusing to run anything else. Generate the digest yourself with openssl against the URL you load, default to SHA-384, and confirm the host sends CORS headers. Pin versioned immutable files; take the self-updating loaders off the payment page instead of pretending a hash covers them. Use two space-separated digests of the same algorithm during a supplier rollout. Monitor a real test transaction, because a failed pin is silent. Then record it honestly against 6.4.3 and pair it with change detection for 11.6.1.

Frequently Asked Questions

How do I generate an SRI hash for a third-party script?

Hash the delivered file, not a copy from the vendor’s documentation. Run curl -s [url] | openssl dgst -sha384 -binary | openssl base64 -A and prefix the output with sha384- in the integrity attribute. If a web generator gives you a different value, stop and work out which bytes each of you hashed before shipping either.

Why did adding an integrity attribute stop my script loading?

Usually because the response lacks an Access-Control-Allow-Origin header, not because the digest is wrong. Integrity checks require crossorigin=”anonymous” and a CORS-enabled response, so a correct hash still fails if the supplier serves the file without CORS. The other common cause is an edge that transforms or regionalises the file, so your digest matches some visitors and not others.

Can you use an SRI hash on a script that updates frequently?

Only if the vendor publishes versioned URLs, so each update becomes a deliberate change on your side. For files served from a floating latest path, pinning will break on the supplier’s next deploy. During a planned rollout you can accept both versions by placing two space-separated digests of the same algorithm in one integrity attribute, then removing the old one.

Does SRI satisfy PCI DSS requirements 6.4.3 and 11.6.1?

Not on its own. Pinning can evidence the integrity assurance part of 6.4.3 for scripts that are genuinely pinnable, but it does not cover authorisation or written justification, and it offers nothing for bootstrap loaders. Requirement 11.6.1 asks for change detection on payment page headers and content with alerting, which SRI does not provide, so page monitoring is needed alongside it.

Should I pin my payment provider’s script with an SRI hash?

Only if the provider publishes an immutable versioned URL and a digest, and only with synthetic monitoring on a real test transaction behind it. Many payment SDKs are deliberately self-updating loaders, and an SRI hash on a third-party script of that kind protects a wrapper while the real payload goes unchecked. Where the provider offers hosted payment fields, moving the card inputs out of your page removes more risk than any pin will.