Home/Blog/Can I Use SRI With Dynamic Content or Inline Scripts?
Web Security

Can I Use SRI With Dynamic Content or Inline Scripts?

Learn about Subresource Integrity limitations with dynamic content, inline scripts, and practical approaches to securing dynamic resources.

By Inventive HQ Team
Can I Use SRI With Dynamic Content or Inline Scripts?

Understanding SRI Limitations With Dynamic Content

Subresource Integrity (SRI) provides a powerful security mechanism by allowing you to specify cryptographic hashes for external resources. However, SRI has fundamental limitations when applied to dynamic content—content that changes between requests. Understanding these limitations and working within them is crucial for using SRI effectively in modern applications.

How SRI Works: The Hash-Based Verification

SRI works by computing a hash of a resource's content when it's served from a CDN or external source. You embed this hash in your HTML as an integrity attribute:

<script src="https://cdn.example.com/script.js" integrity="sha256-abc123..."></script>

When the browser downloads this script, it computes the hash and verifies it matches the integrity attribute. If they don't match (because someone modified the script), the browser rejects it.

This hash-based approach is secure and reliable—but only if the content is static. If the content changes, the hash changes, and the integrity verification fails.

Why Dynamic Content Breaks SRI

Dynamic content is any resource that changes between requests. Examples include:

Server-generated JavaScript - A script that changes based on server-side state:

window.API_KEY = "dynamically-generated-key";
window.USER_ID = {{ current_user.id }};

Each user might get different content, or the same user might get different content on different visits. The hash changes with each response, making SRI impossible.

Template-rendered Content - Resources that include dynamic template variables:

<script src="/api/config.js?version={{ app_version }}"></script>

The query parameter might change, making the content different each time.

Negotiated Content - Resources served in different formats based on Accept headers or other request characteristics. The content might differ between requests in ways that change the hash.

The Core Problem: You Can't Hash Dynamic Content

The fundamental issue is that SRI requires you to know the hash of the content before specifying it in your HTML. With dynamic content, you can't know what the hash will be until the content is generated, and by then it's too late to embed it in the HTML's integrity attribute.

This is why SRI is specifically designed for static external resources: JavaScript libraries from CDNs, CSS frameworks, fonts, and similar content that doesn't change between requests.

Alternative Approaches for Dynamic Scripts

If your application requires dynamic scripts, consider these alternatives:

Use Content-Security-Policy (CSP) instead of SRI - CSP can restrict which external resources can be loaded and what inline scripts can execute. While not hash-based like SRI, CSP provides security against malicious script injection:

<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://trusted-cdn.example.com">

This allows scripts only from your own origin and specific trusted CDNs, preventing injection of malicious scripts from unexpected sources.

Generate SRI Hashes at Build Time - If your dynamic content is actually static-generated-with-variables, you can compute hashes during your build process:

// build-script.js
const fs = require('fs');
const crypto = require('crypto');

function computeSRI(content) {
  const hash = crypto.createHash('sha256');
  hash.update(content);
  return `sha256-${hash.digest('base64')}`;
}

const configScript = `window.API_KEY = "${process.env.API_KEY}";`;
const integrity = computeSRI(configScript);
console.log(`<script integrity="${integrity}">${configScript}</script>`);

This approach works if your dynamic content is generated during your build process rather than at request time.

Use Subresources with Query Strings Carefully - If you must use query strings in resource URLs:

<script src="/config.js?version=abc123" integrity="sha256-..."></script>

You can compute the integrity hash based on the full URL with query string, but you must recompute and update your HTML whenever the query parameter changes.

Inline Scripts and SRI

SRI doesn't directly protect inline scripts (scripts embedded directly in HTML):

<!-- This inline script cannot use integrity attribute -->
<script>
  console.log('inline script');
</script>

Inline scripts don't have a src attribute, so you can't add an integrity attribute to them. However, you can:

Use CSP nonces for inline scripts - Include a nonce (a random value) in your CSP and embed it in your inline script:

<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-abc123'">
<script nonce="abc123">
  console.log('This inline script is allowed');
</script>

<script>
  console.log('This inline script is blocked - no nonce');
</script>

The nonce is generated per request and embedded in both the CSP header and the script tag. Since attackers can't predict the nonce, they can't inject malicious inline scripts.

Use CSP hashes for static inline scripts - If your inline script content never changes, you can use a hash in CSP:

<meta http-equiv="Content-Security-Policy" content="script-src 'sha256-abc123'">
<script>
  const fixed = 'content that never changes';
</script>

This is secure for truly static inline code, but not for content that changes.

Move inline scripts to external files - The best approach is avoiding inline scripts altogether by externalizing them and using SRI:

<!-- Move the inline script to an external file -->
<script src="/scripts/my-script.js" integrity="sha256-..."></script>

This allows using SRI properly and is generally considered a security best practice.

Practical Patterns for Hybrid Static-Dynamic Content

Some applications have resources that are mostly static but include small dynamic sections. Consider these patterns:

Pattern 1: Separate Static and Dynamic

<!-- Static external script with SRI -->
<script src="/lib/app.js" integrity="sha256-..."></script>

<!-- Dynamic configuration inline (without SRI, secured by CSP nonce) -->
<script nonce="abc123">
  window.CONFIG = {{ dynamic_config | safe }};
</script>

Pattern 2: Load Dynamic Config Separately

<!-- Static app script with SRI -->
<script src="/app.js" integrity="sha256-..."></script>

<!-- Dynamic config fetched separately -->
<script>
  fetch('/api/config')
    .then(r => r.json())
    .then(config => window.CONFIG = config);
</script>

The static app code is protected by SRI, and dynamic config is loaded separately without SRI.

Pattern 3: Server-Side Computed Hashes

<!-- Template computes hash of dynamic content -->
<script src="/config.js" integrity="sha256-{{ config_hash }}"></script>

Your server computes the hash of the generated config.js and embeds it. This works if you regenerate the hash whenever content changes.

SRI With Service Workers and Caching

If your application uses service workers, they can affect SRI verification:

Service workers can intercept requests and return cached content. SRI verification still works—the browser verifies the content the service worker returns, not the original resource.

Ensure cached content is fresh - If a service worker caches content that later changes, the integrity hash becomes invalid. Your caching strategy must account for this.

Use versioning with SRI - Include version identifiers in your URLs to bust caches when content changes:

<script src="/script-v2.js" integrity="sha256-..."></script>

When you update the script, increment the version number, change the URL, and update the integrity hash.

Real-World Considerations

APIs that return JavaScript - If your API returns dynamically-generated JavaScript:

GET /api/user-config.js?user=123
Content: window.USER_ID = 123;

You cannot use SRI on this endpoint because the content changes per user/request.

Server-Side Template Rendering - If your templates render JavaScript:

<script>
  var userId = <%= current_user.id %>;
</script>

This cannot use SRI because content varies per request.

Frameworks That Auto-Generate Hashes - Some frameworks can compute and embed SRI hashes automatically:

<!-- Webpack/build tool output -->
<script src="/app.hash123.js" integrity="sha256-abc..."></script>

Frameworks like Next.js can compute hashes during build and embed them automatically.

Best Practices for SRI With Dynamic Applications

  1. Use SRI for truly static external resources - CDN-hosted libraries, frameworks, fonts
  2. Use CSP for dynamic or inline scripts - Nonces or hashes depending on your needs
  3. Separate static from dynamic - Keep statically served content in files you can hash
  4. Automate hash computation - Don't manually manage SRI hashes; use build tools
  5. Combine SRI and CSP - Use both for comprehensive protection
  6. Document your approach - Note which resources use SRI and why dynamic ones don't
  7. Test thoroughly - Verify that your security controls actually work in your application

Conclusion: SRI Works Best With Static Content

SRI is a powerful security tool, but its hash-based approach fundamentally requires static content. Dynamic content is incompatible with SRI because you can't know the hash in advance. For dynamic resources, use CSP with nonces or other security strategies. In modern applications, the ideal approach combines SRI for static external resources and CSP for anything dynamic or inline, creating comprehensive protection against script injection attacks.

Need Expert IT & Security Guidance?

Our team is ready to help protect and optimize your business technology infrastructure.