LEARNING OBJECTIVES ⌵
- Programmatically create
<script>and<link>elements withintegrityandcrossOriginproperties. - Understand the strict order-of-operations required when configuring DOM elements before appending to the document.
- Leverage the Fetch API's native
integrityoption for verified dynamic asset fetching. - Build a production-grade multi-CDN fallback loader that traps SRI mismatches and attempts secondary mirror CDNs.
📖 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). Assigningscript.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:
<link rel="modulepreload">withintegrity:
The browser pre-validates and caches the module. When code later calls<link rel="modulepreload" href="https://cdn.example.com/module.js" integrity="sha384-..." crossorigin="anonymous">import('https://cdn.example.com/module.js'), the browser resolves it from the verified cache.- Fetch-and-Blob Execution:
The application fetches the module script via
fetch(url, { integrity: '...' }), converts the verified text to anObject URL(URL.createObjectURL(blob)), and dynamically imports the local Blob URL.
💻 Interactive Code Playground
Starter Code: Resilient Multi-CDN Fallback Loader
Line-by-Line Code Breakdown
- Lines 38–41 (
tryLoad()): Instantiates the<script>element and setsscript.integrityandscript.crossOrigin = 'anonymous'prior to setting thesrc. - 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
> 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:
- Implement a generic async function
fetchWithIntegrity(url, integrityHash)using the native Fetch API. - The function must:
- Request the resource in
corsmode. - Attach the provided
integrityhash. - Return the response body as plain text (
response.text()) if verification succeeds. - Catch any
TypeError(hash mismatch or network error) and re-throw a customSecurityExceptionwith the message"CRITICAL: Resource integrity check failed for <url>".
- Request the resource in
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Setting
script.srcBeforescript.integrity: In some browser engines and web worker environments, settingsrcimmediately triggers the resource loader. Ifintegrityhas not been set yet, the resource may load unverified. Always configureintegrityandcrossOriginbefore assigningsrc. - Using Lowercase
script.crossoriginin JavaScript: In JavaScript DOM scripting, the property isscript.crossOrigin(capital 'O'). Settingscript.crossorigin = 'anonymous'sets an arbitrary custom property on the object without configuring the DOM reflection. - 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
- 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
fetchevent handler usingfetch(event.request, { integrity: knownHash })before storing the response in the Cache API. - 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.integrityandscript.crossOriginbefore assigningscript.srcor appending to the DOM. - The JavaScript DOM property for CORS is
element.crossOrigin(camelCase). - The native Fetch API supports SRI directly via the
integrityoption inRequestInit. - If an SRI hash mismatch occurs in
fetch(), the browser rejects the Promise with aTypeError. - Robust frontend architectures implement multi-CDN fallback loaders to survive both CDN outages and upstream integrity failures.
- --