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
- Open your website
- Open DevTools (
F12) - Navigate to Lighthouse tab
- Click "Analyze page load"
- 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
- View page source (
Ctrl+UorCmd+U) - Search for
<script src=and<link rel="stylesheet" - Identify external resources (different domain)
- Check for
integrityattribute
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
- Visit SecurityHeaders.com
- Enter your domain
- Review the report
- 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
- Visit SRI Hash Generator
- Enter the CDN URL
- Copy the complete
<script>tag with integrity hash - 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:
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
Test script functionality:
- All features work as expected
- No broken widgets or integrations
- Analytics tracking still functions
- Forms and interactions work
Verify with Lighthouse:
- Run Lighthouse audit
- Check Best Practices score
- Should see improved security score
- No SRI-related warnings
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
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
- Forgetting crossorigin attribute - SRI requires
crossorigin="anonymous" - Using wrong hash algorithm - Use sha384 or sha512, not sha256 or md5
- Hash doesn't match file - File changed, hash needs updating
- Mixing HTTP and HTTPS - SRI only works over HTTPS
- Not pinning versions - Using latest/default version breaks when CDN updates
- Self-hosting without updating - SRI hash outdated for local files
- Breaking analytics by adding SRI - Analytics scripts update frequently
- No fallback for failed SRI - Site breaks if CDN integrity fails
- Copy-paste errors - Incorrect or truncated hashes
- Testing only in one browser - Different browsers may handle SRI differently
When NOT to Use SRI
Dynamic/Frequently Updated Scripts:
- Google Analytics (gtag.js, analytics.js)
- Google Tag Manager (gtm.js)
- Meta Pixel (fbevents.js)
- Ad networks
- A/B testing tools
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