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.

Giao thức liên quan

Thuật ngữ liên quan

Thêm trong Security & Authentication