Missing Subresource Integrity (SRI) | Blue Frog Docs

Missing Subresource Integrity (SRI)

Protect your site from compromised CDN scripts by implementing SRI hashes on third-party resources

Missing Subresource Integrity (SRI)

What This Means

Subresource Integrity (SRI) is a security feature that allows browsers to verify that files loaded from third-party sources (like CDNs) haven't been tampered with. Without SRI hashes, if a CDN is compromised or serves malicious code, your website will unknowingly execute that code, potentially exposing your users to attacks.

How SRI Works

Without SRI:

  • Browser loads script from CDN blindly
  • No verification of file contents
  • Malicious code executes if CDN is compromised
  • Your site becomes attack vector

With SRI:

  • Browser downloads script from CDN
  • Calculates hash of received file
  • Compares with hash in integrity attribute
  • Only executes if hashes match
  • Blocks and reports mismatches

Impact on Your Business

Security Risks:

  • CDN compromise - If CDN is hacked, malicious code executes on your site
  • Supply chain attacks - Third-party scripts modified without your knowledge
  • Data theft - Compromised scripts can steal user data, credentials, payment info
  • Malware distribution - Your site unknowingly serves malware to users
  • Session hijacking - Attackers steal session cookies and impersonate users

Trust and Compliance:

  • Users expect protection from third-party risks
  • Security audits flag missing SRI
  • PCI DSS and compliance standards recommend SRI
  • Professional sites implement defense-in-depth

Common Vulnerable Scripts:

  • Google Analytics, GTM (analytics code modified)
  • jQuery, Bootstrap from CDN (library compromised)
  • Payment SDKs (credit card data stolen)
  • Chat widgets (conversations intercepted)
  • Font libraries (tracking injected)

How to Diagnose

Method 1: Browser DevTools Audit

  1. Open your website
  2. Open DevTools (F12)
  3. Navigate to Lighthouse tab
  4. Click "Analyze page load"
  5. Review Best Practices section

What to Look For:

  • "Includes front-end JavaScript libraries with known security vulnerabilities"
  • "Does not use passive listeners to improve scrolling performance"
  • Warnings about CDN scripts without integrity checks

Method 2: Manual Script Review

  1. View page source (Ctrl+U or Cmd+U)
  2. Search for <script src= and <link rel="stylesheet"
  3. Identify external resources (different domain)
  4. Check for integrity attribute

What to Look For:

<!-- VULNERABLE - No SRI -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>

<!-- PROTECTED - Has SRI -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
        integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
        crossorigin="anonymous"></script>

Method 3: Security Headers Checker

  1. Visit SecurityHeaders.com
  2. Enter your domain
  3. Review the report
  4. Check for SRI recommendations

What to Look For:

  • Recommendations to add integrity checks
  • List of external scripts without SRI
  • Overall security grade impact

Method 4: CSP Reporting

Add CSP require-sri-for directive to detect missing SRI:

<meta http-equiv="Content-Security-Policy"
      content="require-sri-for script style;">

Browser will block and report any script/style without SRI.

Method 5: Browser Console Check

Run this in browser console to find vulnerable scripts:

// Find scripts without integrity attribute
const scriptsWithoutSRI = Array.from(document.querySelectorAll('script[src]'))
  .filter(script => {
    const isExternal = script.src && !script.src.startsWith(window.location.origin);
    const hasIntegrity = script.hasAttribute('integrity');
    return isExternal && !hasIntegrity;
  })
  .map(script => script.src);

console.log('Scripts without SRI:', scriptsWithoutSRI);

// Find stylesheets without integrity attribute
const stylesWithoutSRI = Array.from(document.querySelectorAll('link[rel="stylesheet"][href]'))
  .filter(link => {
    const isExternal = link.href && !link.href.startsWith(window.location.origin);
    const hasIntegrity = link.hasAttribute('integrity');
    return isExternal && !hasIntegrity;
  })
  .map(link => link.href);

console.log('Stylesheets without SRI:', stylesWithoutSRI);

General Fixes

Fix 1: Generate SRI Hashes for Existing Scripts

Method A: Use SRI Hash Generator Tool

  1. Visit SRI Hash Generator
  2. Enter the CDN URL
  3. Copy the complete <script> tag with integrity hash
  4. Replace your existing tag

Method B: Generate Hash Manually

# Download the file
curl https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js > jquery.min.js

# Generate SHA384 hash
cat jquery.min.js | openssl dgst -sha384 -binary | openssl base64 -A

# Output: vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK

Method C: Use npm sri-tools

# Install
npm install -g sri-toolbox

# Generate hash
sri-toolbox generate https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js

Method D: Node.js Script

const crypto = require('crypto');
const https = require('https');

function generateSRI(url) {
  https.get(url, (res) => {
    const hash = crypto.createHash('sha384');
    res.on('data', (chunk) => hash.update(chunk));
    res.on('end', () => {
      const integrity = `sha384-${hash.digest('base64')}`;
      console.log(`integrity="${integrity}"`);
    });
  });
}

generateSRI('https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js');

Fix 2: Add Integrity Attributes to Scripts

Before:

<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>

After:

<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
        integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
        crossorigin="anonymous"></script>

Important: Always include crossorigin="anonymous" attribute for SRI to work with CORS.

Fix 3: Common CDN Scripts with SRI

jQuery:

<!-- jQuery 3.6.0 -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
        integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
        crossorigin="anonymous"></script>

Bootstrap:

<!-- Bootstrap 5.3.0 CSS -->
<link rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
      integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
      crossorigin="anonymous">

<!-- Bootstrap 5.3.0 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
        crossorigin="anonymous"></script>

Font Awesome:

<link rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
      integrity="sha384-iw3OoTErCYJJB9mCa8iGbH7vs5LYXJ1j7YqJl3KJhB7YqJl3KJhB7YqJl3KJhB7Y"
      crossorigin="anonymous">

Google Fonts (Note: Google Fonts doesn't support SRI):

<!-- Self-host fonts instead, or accept the risk -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">

Fix 4: Handle Dynamic Scripts

For scripts that change frequently (like analytics), consider:

Option A: Self-host the script

<!-- Download and host locally -->
<script src="/js/analytics.js"></script>

Option B: Version pin and monitor

<!-- Pin to specific version, update manually -->
<script src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX&v=2.4.0"
        integrity="sha384-[HASH-FOR-VERSION-2.4.0]"
        crossorigin="anonymous"></script>

Option C: Use CDN with SRI support

<!-- Use jsDelivr which provides SRI hashes -->
<script src="https://cdn.jsdelivr.net/npm/package@version/file.js"
        integrity="sha384-..."
        crossorigin="anonymous"></script>

Option D: CSP fallback

<!-- Use CSP to whitelist domains when SRI isn't possible -->
<meta http-equiv="Content-Security-Policy"
      content="script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com;">

Fix 5: Automated SRI Management

Webpack Plugin:

// webpack.config.js
const SriPlugin = require('webpack-subresource-integrity');

module.exports = {
  output: {
    crossOriginLoading: 'anonymous',
  },
  plugins: [
    new SriPlugin({
      hashFuncNames: ['sha384'],
      enabled: process.env.NODE_ENV === 'production',
    }),
  ],
};

Gulp Task:

const gulp = require('gulp');
const sri = require('gulp-sri-hash');

gulp.task('sri', () => {
  return gulp.src('./dist/index.html')
    .pipe(sri())
    .pipe(gulp.dest('./dist'));
});

HTML Build Script:

// add-sri.js
const cheerio = require('cheerio');
const crypto = require('crypto');
const https = require('https');
const fs = require('fs');

async function addSRI(htmlFile) {
  const html = fs.readFileSync(htmlFile, 'utf8');
  const $ = cheerio.load(html);

  const externalScripts = $('script[src]').filter((i, el) => {
    const src = $(el).attr('src');
    return src && src.startsWith('http');
  });

  for (let el of externalScripts) {
    const src = $(el).attr('src');
    const hash = await generateHash(src);
    $(el).attr('integrity', hash);
    $(el).attr('crossorigin', 'anonymous');
  }

  fs.writeFileSync(htmlFile, $.html());
}

function generateHash(url) {
  return new Promise((resolve, reject) => {
    https.get(url, (res) => {
      const hash = crypto.createHash('sha384');
      res.on('data', (chunk) => hash.update(chunk));
      res.on('end', () => resolve(`sha384-${hash.digest('base64')}`));
      res.on('error', reject);
    });
  });
}

addSRI('./dist/index.html');

Fix 6: Fallback for Broken SRI

Handle cases where CDN fails SRI check:

<!-- Primary CDN with SRI -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
        integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
        crossorigin="anonymous"
        onerror="loadFallback()"></script>

<!-- Fallback script -->
<script>
function loadFallback() {
  console.warn('Primary CDN failed, loading fallback');
  const script = document.createElement('script');
  script.src = '/js/jquery.min.js'; // Local fallback
  document.head.appendChild(script);
}
</script>

Or use multiple CDNs:

<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
        integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
        crossorigin="anonymous"></script>
<script>
if (!window.jQuery) {
  document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-[DIFFERENT-HASH]" crossorigin="anonymous"><\/script>');
}
</script>

Platform-Specific Guides

Detailed implementation instructions for your specific platform:

Platform Troubleshooting Guide
Shopify Shopify SRI Guide
WordPress WordPress SRI Guide
Wix Wix SRI Guide
Squarespace Squarespace SRI Guide
Webflow Webflow SRI Guide

Verification

After adding SRI:

  1. Check browser console:

    • Reload page with DevTools open
    • No SRI-related errors
    • All scripts load successfully
    • Check for "Failed to find a valid digest" errors
  2. Test script functionality:

    • All features work as expected
    • No broken widgets or integrations
    • Analytics tracking still functions
    • Forms and interactions work
  3. Verify with Lighthouse:

    • Run Lighthouse audit
    • Check Best Practices score
    • Should see improved security score
    • No SRI-related warnings
  4. Test integrity verification:

    • Modify integrity hash slightly
    • Reload page
    • Should see error in console: "Failed to find a valid digest in the 'integrity' attribute"
    • Script should be blocked
    • Restore correct hash
  5. Monitor for updates:

    • Document all SRI hashes used
    • Set up monitoring for CDN updates
    • Update hashes when libraries update
    • Test thoroughly after changes

Common Mistakes

  1. Forgetting crossorigin attribute - SRI requires crossorigin="anonymous"
  2. Using wrong hash algorithm - Use sha384 or sha512, not sha256 or md5
  3. Hash doesn't match file - File changed, hash needs updating
  4. Mixing HTTP and HTTPS - SRI only works over HTTPS
  5. Not pinning versions - Using latest/default version breaks when CDN updates
  6. Self-hosting without updating - SRI hash outdated for local files
  7. Breaking analytics by adding SRI - Analytics scripts update frequently
  8. No fallback for failed SRI - Site breaks if CDN integrity fails
  9. Copy-paste errors - Incorrect or truncated hashes
  10. Testing only in one browser - Different browsers may handle SRI differently

When NOT to Use SRI

Dynamic/Frequently Updated Scripts:

Reason: These scripts update automatically for bug fixes and features. SRI will break them on every update.

Alternative: Use CSP script-src directive to whitelist trusted domains:

<meta http-equiv="Content-Security-Policy"
      content="script-src 'self' https://www.googletagmanager.com https://www.google-analytics.com;">

Good Candidates for SRI:

  • jQuery, React, Vue, Angular (versioned libraries)
  • Bootstrap, Tailwind CSS
  • Font Awesome
  • Chart.js, D3.js
  • Any versioned CDN library you control updates for

Security Best Practices

Defense in Depth

Combine SRI with other security measures:

<!-- 1. SRI for integrity verification -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"
        integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
        crossorigin="anonymous"></script>

<!-- 2. CSP to restrict script sources -->
<meta http-equiv="Content-Security-Policy"
      content="script-src 'self' https://cdn.jsdelivr.net;">

<!-- 3. HTTPS for all connections -->
<!-- 4. Regular audits and updates -->

SRI Hash Management

Document your hashes:

// sri-hashes.json
{
  "jquery": {
    "version": "3.6.0",
    "url": "https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js",
    "integrity": "sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK",
    "updated": "2024-01-15"
  },
  "bootstrap": {
    "version": "5.3.0",
    "url": "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js",
    "integrity": "sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz",
    "updated": "2024-01-15"
  }
}

Set up alerts:

  • Monitor CDN libraries for updates
  • Get notifications when new versions released
  • Test and update SRI hashes promptly
  • Document update process

Additional Resources

// SYS.FOOTER