Security & Authentication

Subresource Integrity (SRI): Protecting CDN-Hosted Scripts

How SRI hashes prevent tampered CDN scripts from executing, implementation with integrity attributes, and handling hash updates in CI/CD.

The CDN Supply Chain Risk

Many websites load JavaScript and CSS from Content Delivery Networks (CDNs). CDNs reduce latency and shift bandwidth costs, but they introduce a trust problem: you are executing code from a server you don't control.

If a CDN is compromised, every site using it becomes vulnerable. This is not theoretical:

  • Magecart attacks: attackers inject credit card skimming code into CDN-hosted JavaScript used by e-commerce sites. British Airways (2018, ~500K customers), Ticketmaster (2018), and many others were breached this way.
  • event-stream incident (2018): a malicious contributor added cryptocurrency theft code to a popular npm package. Downstream CDNs served the malicious version before detection.

Subresource Integrity (SRI) is the browser-native defense against these attacks.

How SRI Works

SRI adds a cryptographic hash to your <script> and <link> tags. The browser downloads the resource, computes its hash, and only executes/applies it if the hash matches.

<!-- Without SRI: executes whatever cdn.example.com returns -->
<script src="https://cdn.example.com/jquery-3.7.1.min.js"></script>

<!-- With SRI: only executes if hash matches -->
<script
  src="https://cdn.example.com/jquery-3.7.1.min.js"
  integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs"
  crossorigin="anonymous"
></script>

If the CDN serves a tampered file, the hash won't match and the script is blocked. The browser logs a console error and continues without executing the malicious code.

The crossorigin Attribute

The crossorigin="anonymous" attribute is required for SRI to work on cross-origin resources. It tells the browser to make a CORS request without credentials (no cookies). The CDN must respond with Access-Control-Allow-Origin: * or the resource load will fail.

If you omit crossorigin, the browser cannot access the response body to verify the hash — SRI silently fails to work.

Hash Algorithm Selection

AlgorithmStatusRecommendation
SHA-256SupportedMinimum acceptable
SHA-384SupportedRecommended default
SHA-512SupportedMaximum security
SHA-1, MD5Not supportedNot accepted by browsers

Use SHA-384. It provides strong security without the marginal overhead of SHA-512.

You can include multiple hashes for the same resource — useful during algorithm migration or when supporting multiple CDN copies:

<script
  src="..."
  integrity="sha384-<hash1> sha512-<hash2>"
  crossorigin="anonymous"
></script>

The browser accepts the resource if any of the listed hashes match.

Generating SRI Hashes

With openssl

# Download the file first
curl -sL https://cdn.example.com/library.min.js -o library.min.js

# Generate SHA-384 hash in the SRI format
openssl dgst -sha384 -binary library.min.js | openssl base64 -A
# Prepend 'sha384-' to the output

# One-liner
echo "sha384-$(openssl dgst -sha384 -binary library.min.js | openssl base64 -A)"

With shasum (built into macOS/Linux)

cat library.min.js | openssl dgst -sha384 -binary | openssl enc -base64 -A

srihash.org

For quick lookups, paste a CDN URL into srihash.org and it generates the correct integrity attribute for you.

CI/CD Integration

Automate hash generation as part of your build pipeline. If your project pulls CDN versions from a config file, verify hashes in CI:

# scripts/verify_sri_hashes.py
import hashlib
import base64
import urllib.request

ASSETS = [
    {
        'url': 'https://cdn.example.com/library-3.7.1.min.js',
        'expected': 'sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs',
    },
]

for asset in ASSETS:
    with urllib.request.urlopen(asset['url']) as f:
        content = f.read()
    digest = hashlib.sha384(content).digest()
    computed = 'sha384-' + base64.b64encode(digest).decode()
    if computed != asset['expected']:
        print(f'MISMATCH: {asset["url"]}')
        print(f'  Expected:  {asset["expected"]}')
        print(f'  Computed:  {computed}')
        exit(1)
    else:
        print(f'OK: {asset["url"]}')

Fallback Strategies

If the CDN resource fails the SRI check — or if the CDN is unavailable — you need a fallback to your own hosted copy:

<script
  src="https://cdn.example.com/library.min.js"
  integrity="sha384-<hash>"
  crossorigin="anonymous"
  onerror="loadFallback()"
></script>
<script>
function loadFallback() {
  var s = document.createElement('script');
  s.src = '/static/vendor/library.min.js';
  document.head.appendChild(s);
}
</script>

The onerror fires both on network failure and SRI mismatch. The fallback loads your self-hosted copy, which you should keep in sync with the CDN version.

Note: If you're using CSP with SRI, ensure your script-src allows both the CDN host and 'self' so the fallback is permitted.

When Not to Use Fallback

If SRI fails because the CDN served a tampered file, a fallback to a local copy is the right response. But if you're serving the same file from your own origin anyway, skip the CDN entirely — the performance benefit disappears when the CDN and your origin are in the same region, and you eliminate the supply chain risk.

Related Protocols

Related Glossary Terms

More in Security & Authentication