Chapter 69: Subresource Integrity (SRI) & Referrer Policy

SRI for Dynamically Injected Scripts

Programmatic subresource integrity, DOM script element creation, Fetch API integrity checks, and resilient multi-CDN runtime fallbacks.

LEARNING OBJECTIVES
  • Programmatically create <script> and <link> elements with integrity and crossOrigin properties.
  • Understand the strict order-of-operations required when configuring DOM elements before appending to the document.
  • Leverage the Fetch API's native integrity option for verified dynamic asset fetching.
  • Build a production-grade multi-CDN fallback loader that traps SRI mismatches and attempts secondary mirror CDNs.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

Imagine a modern automated fulfillment center. Most standard packages enter through the main front gate (static HTML <script> tags written in the HTML template).

However, during peak traffic hours, the warehouse manager dynamically dispatches specialized courier drones (JavaScript dynamic loaders) to fetch extra tools on-demand.

+---------------------------------------------------------------------------------------+
|                          DYNAMIC RUNTIME DISPATCH INSPECTION                          |
+---------------------------------------------------------------------------------------+
|                                                                                       |
|   1. Create Drone               2. Attach Security Seal         3. Launch Drone       |
|   const script =                script.integrity = 'sha384..'   document.head         |
|   document.createElement()      script.crossOrigin = 'anon..'   .appendChild(script)  |
|                                                                                       |
|            🤖                             🏷️                            🚀            |
|       [New Script]                [Cryptographic Hash]               [To Network]     |
|                                                                                       |
|   ⚠️ CRITICAL RULE: You must attach the security seal BEFORE sending the drone       |
|      out the door. If you launch the drone before attaching the seal, the            |
|      inspection checkpoint at the gate will reject it immediately.                    |
|                                                                                       |
+---------------------------------------------------------------------------------------+

When creating scripts programmatically in JavaScript, the browser networking engine begins processing attributes immediately upon network dispatch. If you assign the source URL or append the node to the DOM before specifying the integrity and crossOrigin properties, the request may initiate as an unverified or non-CORS fetch, triggering an immediate security violation.


Technical Deep Dive & Specifications

1. Programmatic DOM <script> Injection Mechanics

When creating script elements dynamically via document.createElement('script'), you must configure the DOM properties in the correct sequence.

// Step 1: Instantiate the element
const script = document.createElement('script');

// Step 2: Configure type, integrity, and CORS BEFORE initiating fetch
script.type = 'text/javascript';
script.integrity = 'sha384-m6t5i17z/aP29Z19F4sN+eA4zR8nC9lP7qY1vX6mZ8bC2xD3eE4fG5hH6iI7jJ8k=';
script.crossOrigin = 'anonymous'; // Note property camelCase in JavaScript DOM

// Step 3: Attach lifecycle event listeners
script.onload = () => console.log('Script loaded and verified successfully');
script.onerror = (err) => console.error('Script blocked by SRI or network error', err);

// Step 4: Set source URL and append to DOM
script.src = 'https://cdn.example.com/analytics.min.js';
document.head.appendChild(script);

DOM Property Mapping Matrix:

HTML Attribute JavaScript DOM Property Type / Expected Value
integrity="..." element.integrity string (e.g. 'sha384-...')
crossorigin="..." element.crossOrigin string ('anonymous' or 'use-credentials')
referrerpolicy="..." element.referrerPolicy string (e.g. 'no-referrer')

⚠️ CamelCase Warning: In HTML, attributes are lowercase (crossorigin, referrerpolicy). In JavaScript DOM properties, they are camelCased (crossOrigin, referrerPolicy). Assigning script.crossorigin = 'anonymous' will fail silently without setting the actual property.


2. Fetch API Native integrity Option

The modern window.fetch() API natively supports the integrity option in RequestInit.

async function loadSecureJsonData(url, expectedHash) {
  try {
    const response = await fetch(url, {
      method: 'GET',
      mode: 'cors',
      integrity: expectedHash, // Native SRI verification in Fetch API
    });

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    // If the hash fails, fetch() rejects with a TypeError
    console.error('Fetch integrity check failed:', error);
    throw error;
  }
}

If the downloaded byte stream fails hash verification, fetch() immediately rejects the Promise with a TypeError: Failed to fetch.


3. Dynamic ES Modules (import()) & SRI

As of current browser specifications, dynamic ECMAScript module imports (import('https://cdn.example.com/module.js')) do not natively accept an inline hash argument.

To enforce SRI on ES Modules today, engineers use one of two primary strategies:

  1. <link rel="modulepreload"> with integrity:
    <link rel="modulepreload" href="https://cdn.example.com/module.js" integrity="sha384-..." crossorigin="anonymous">
    
    The browser pre-validates and caches the module. When code later calls import('https://cdn.example.com/module.js'), the browser resolves it from the verified cache.
  2. Fetch-and-Blob Execution: The application fetches the module script via fetch(url, { integrity: '...' }), converts the verified text to an Object URL (URL.createObjectURL(blob)), and dynamically imports the local Blob URL.

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL example.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

💻 Interactive Code Playground

Starter Code: Resilient Multi-CDN Fallback Loader

Line-by-Line Code Breakdown

  • Lines 38–41 (tryLoad()): Instantiates the <script> element and sets script.integrity and script.crossOrigin = 'anonymous' prior to setting the src.
  • Lines 44–47 (script.onload): Resolves the Promise once the browser verifies the cryptographic hash against the incoming byte stream.
  • Lines 49–58 (script.onerror): Intercepts the network error or SRI mismatch event. It cleans up the failed DOM node and transparently attempts the fallback CDN URL with the same integrity constraint.
  • Lines 60–61 (script.src = url; document.head.appendChild(script)): Initiates the network fetch and mounts the element into the DOM tree.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...
> System ready. Click button to begin secure dynamic fetch...
> Starting secure dynamic injection pipeline...
> Attempting load from PRIMARY source: https://invalid-tampered-cdn.example.com/luxon.min.js
> 🛑 FAILED: Integrity or network error on https://invalid-tampered-cdn.example.com/luxon.min.js
> 🔄 Initiating automatic switchover to fallback mirror...
> Attempting load from FALLBACK source: https://cdnjs.cloudflare.com/ajax/libs/luxon/3.4.4/luxon.min.js

🏋️ Hands-On Exercise

🎯 The Challenge: Build a Promise-Based Fetch Integrity Asset Wrapper

Instructions:

  1. Implement a generic async function fetchWithIntegrity(url, integrityHash) using the native Fetch API.
  2. The function must:
    • Request the resource in cors mode.
    • Attach the provided integrity hash.
    • Return the response body as plain text (response.text()) if verification succeeds.
    • Catch any TypeError (hash mismatch or network error) and re-throw a custom SecurityException with the message "CRITICAL: Resource integrity check failed for <url>".

🏁 Starter Code Sandbox

⚠️ Common Pitfalls

  1. Setting script.src Before script.integrity: In some browser engines and web worker environments, setting src immediately triggers the resource loader. If integrity has not been set yet, the resource may load unverified. Always configure integrity and crossOrigin before assigning src.
  2. Using Lowercase script.crossorigin in JavaScript: In JavaScript DOM scripting, the property is script.crossOrigin (capital 'O'). Setting script.crossorigin = 'anonymous' sets an arbitrary custom property on the object without configuring the DOM reflection.
  3. Leaking Memory on Failed Injections: When a dynamic script fails SRI verification, remember to call document.head.removeChild(script) or let garbage collection clean up the rejected node before retrying with a fallback mirror.

💡 Pro Tips

  1. Pair Dynamic SRI with Service Workers: If your application uses a Service Worker for offline caching, you can validate SRI inside the Service Worker's fetch event handler using fetch(event.request, { integrity: knownHash }) before storing the response in the Cache API.
  2. Integrity Map Pattern: Store all vendor asset URLs and their corresponding SRI hashes in a frozen configuration object (const ASSET_REGISTRY = Object.freeze({ ... })) at the root of your application to prevent tampering by unauthorized runtime code.

📌 Key Takeaways

  • Dynamic script injection requires setting script.integrity and script.crossOrigin before assigning script.src or appending to the DOM.
  • The JavaScript DOM property for CORS is element.crossOrigin (camelCase).
  • The native Fetch API supports SRI directly via the integrity option in RequestInit.
  • If an SRI hash mismatch occurs in fetch(), the browser rejects the Promise with a TypeError.
  • Robust frontend architectures implement multi-CDN fallback loaders to survive both CDN outages and upstream integrity failures.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Which of the following code snippets correctly configures a dynamically created <script> element with SRI?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

What error does window.fetch('https://cdn.example.com/lib.js', { integrity: 'sha384-WRONG' }) throw when the hash fails verification?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

How can you enforce SRI verification on ECMAScript Modules loaded via dynamic import() in modern browsers?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP