Chapter 69: Subresource Integrity (SRI) & Referrer Policy

Generating SRI Hashes

Mastering hash generation via OpenSSL, shasum, Node.js crypto, and automated bundler plugins in Webpack and Vite.

LEARNING OBJECTIVES
  • Generate cryptographic SRI hashes using CLI tools (openssl, shasum).
  • Programmatically compute SRI metadata using Node.js crypto.
  • Automate SRI hash injection into production builds via Webpack and Vite plugins.
  • Prevent hash mismatch bugs caused by line-ending conversions (CRLF vs. LF) and post-build transformations.
🎬 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 pharmaceutical company manufacturing tamper-evident medicine bottles on an automated assembly line.

Before a bottle leaves the factory floor, a robotic scanner analyzes its chemical composition, computes a unique cryptographic barcode, prints that barcode on the shipping manifest (the HTML document), and seals the bottle for transport.

+---------------------------------------------------------------------------------------+
|                         THE AUTOMATED HASH GENERATION PIPELINE                        |
+---------------------------------------------------------------------------------------+
|                                                                                       |
|   Source Code               Bundler (Vite/Webpack)               Production Assets    |
|   +---------------+         +------------------------+          +------------------+  |
|   | src/app.js    | ------> | Minify & Tree Shake    | -------> | dist/app.a1b2.js |  |
|   +---------------+         +------------------------+          +------------------+  |
|                                         |                                 |           |
|                                         v                                 v           |
|                             [ Compute SHA-384 Digest ] ---------> [ Base64 Digest ]   |
|                                         |                                             |
|                                         v                                             |
|                             [ Inject into HTML ]                                      |
|                                         |                                             |
|                                         v                                             |
|                             +------------------------+                                |
|                             | dist/index.html        |                                |
|                             | <script src="app.js"   |                                |
|                             | integrity="sha384-.."> |                                |
|                             +------------------------+                                |
+---------------------------------------------------------------------------------------+

Manually calculating hashes with a calculator for every code change would be impossible in modern continuous deployment. Every time you fix a single typo or update a dependency, the file's binary bytes change, generating an entirely new hash.

Therefore, production engineering requires both command-line fluency for one-off third-party vendor assets and automated build plugins for first-party bundles.


Technical Deep Dive & Specifications

1. Generating Hashes via Command Line

Method A: Using OpenSSL (Universal Standard)

The OpenSSL command computes the binary digest and pipes the raw bytes directly into the Base64 encoder:

# General Syntax for SHA-384:
openssl dgst -sha384 -binary bundle.min.js | openssl base64 -A

# Output Example:
# 4Lz5vI3Yn5FzM8P1Q2R3S4T5U6V7W8X9Y0Z1A2B3C4D5E6F7G8H9I0J1K2L3M4N==

To format it directly as an HTML-ready SRI string:

echo "sha384-$(openssl dgst -sha384 -binary bundle.min.js | openssl base64 -A)"

⚠️ Critical Flag -binary: Without the -binary flag, openssl dgst outputs a hex-encoded ASCII string. Base64-encoding a hex string will produce an invalid SRI hash. Always pass -binary. The -A flag in openssl base64 prevents newline wrapping.

Method B: Using shasum & xxd (macOS / Linux)

shasum -b -a 384 bundle.min.js | awk '{ print $1 }' | xxd -r -p | base64

2. Programmatic Generation with Node.js crypto

In Node.js scripts, tools, and build hooks, use the native node:crypto module:

import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';

/**
 * Computes the W3C SRI string for a given file buffer.
 * @param {Buffer|string} content - Raw file bytes or string
 * @param {'sha256'|'sha384'|'sha512'} algorithm - Hash algorithm
 * @returns {string} SRI metadata string formatted as "algorithm-base64"
 */
export function generateSRI(content, algorithm = 'sha384') {
  const hash = createHash(algorithm)
    .update(content)
    .digest('base64');
  
  return `${algorithm}-${hash}`;
}

// Usage:
const fileBuffer = readFileSync('./dist/assets/vendor.min.js');
console.log(generateSRI(fileBuffer, 'sha384'));
// Output: sha384-m6t5i17z/aP29Z19F4sN+eA4zR8nC9lP7qY1vX6mZ8bC2xD3eE4fG5hH6iI7jJ8k=

3. Automated Bundler Integrations

Webpack: webpack-subresource-integrity

In Webpack projects, the webpack-subresource-integrity plugin hooks into Webpack's asset compilation pipeline to calculate hashes for all output chunks and inject integrity and crossorigin attributes into HtmlWebpackPlugin.

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { SubresourceIntegrityPlugin } = require('webpack-subresource-integrity');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].[contenthash].js',
    publicPath: 'https://cdn.mycompany.com/assets/',
    crossOriginLoading: 'anonymous', // Mandatory for Webpack dynamic chunk loading with SRI
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
    new SubresourceIntegrityPlugin({
      hashFuncNames: ['sha384'],
      enabled: process.env.NODE_ENV === 'production',
    }),
  ],
};

Vite: vite-plugin-sri

In Vite applications, use community or custom Rollup plugins during the build lifecycle:

// vite.config.ts
import { defineConfig } from 'vite';
import sri from '@stewilond/vite-plugin-sri'; // or custom build hook

export default defineConfig({
  plugins: [
    sri({
      algorithms: ['sha384'],
    }),
  ],
  build: {
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js',
        chunkFileNames: 'assets/[name].[hash].js',
        assetFileNames: 'assets/[name].[hash].[ext]',
      },
    },
  },
});

💻 Interactive Code Playground

Starter Code: Custom Node.js SRI Build Hook

Line-by-Line Code Breakdown

  • Lines 8–13 (mockBundleJs): Represents the minified JavaScript asset produced by your build tool.
  • Lines 16–19 (createHash('sha384')...): Generates the SHA-384 binary hash of the raw string content and converts it directly into a standard Base64 string.
  • Lines 21–22 (sriAttributeValue): Formats the string with the required sha384- prefix.
  • Lines 44–45 (htmlTemplate.replace(...)): Injects the complete <script> tag containing both integrity and crossorigin="anonymous" into the production HTML distribution directory.

Expected Browser Render Output


// scripts/sri-generator.mjs
import { createHash } from 'node:crypto';
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';

// 1. Mock asset content to simulate a production build
const mockBundleJs = `
  (function() {
    console.log("Enterprise Core v2.4.0 Initialized");
    window.__SECURITY_VERIFIED__ = true;
  })();
`;

// 2. Compute SHA-384 digest
const sha384Digest = createHash('sha384')
  .update(mockBundleJs, 'utf8')
  .digest('base64');

const sriAttributeValue = `sha384-${sha384Digest}`;
console.log('Calculated SRI:', sriAttributeValue);

// 3. Simulated HTML template
const htmlTemplate = `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Automated SRI Build</title>
  <!-- SCRIPT_INJECTION_POINT -->
</head>
<body>
  <h1>SRI CI/CD Pipeline Verification</h1>
  <p id="status">Verifying runtime execution...</p>
  <script>
    if (window.__SECURITY_VERIFIED__) {
      document.getElementById('status').textContent = '✅ Script integrity verified!';
      document.getElementById('status').style.color = '#059669';
    }
  </script>
</body>
</html>`;

// 4. Inject script tag with SRI into HTML
const scriptTag = `<script src="dist/app.bundle.js" integrity="${sriAttributeValue}" crossorigin="anonymous"></script>`;
const finalHtml = htmlTemplate.replace('<!-- SCRIPT_INJECTION_POINT -->', scriptTag);

console.log('\n--- Generated Production HTML ---');
console.log(finalHtml);
SRI CI/CD Pipeline Verification
✅ Script integrity verified!

🏋️ Hands-On Exercise

🎯 The Challenge: Build an SRI CLI Verification Tool in Node.js

Instructions:

  1. Write a Node.js utility function generateHtmlScriptTag(url, fileContent, algorithm) that:
    • Accepts an asset URL, the raw file content, and an optional algorithm (defaulting to 'sha384').
    • Calculates the cryptographic hash using Node.js crypto.
    • Returns a complete, valid HTML <script> tag string formatted with src, integrity, and crossorigin="anonymous".
  2. Ensure that empty or invalid contents throw an informative Error.
  3. Test your function with a sample CSS stylesheet string and ensure it generates an equivalent <link rel="stylesheet"> tag.

🏁 Starter Code Sandbox

⚠️ Common Pitfalls

  1. CRLF vs. LF Line-Ending Mismatch: On Windows systems, Git or editors may convert Line Feed (\n) characters to Carriage Return + Line Feed (\r\n). Because cryptographic hashes operate on raw binary bytes, even a single \r character change results in a completely different hash, breaking SRI in production. Configure .gitattributes to enforce * text eol=lf.
  2. Hashing Unminified Code in Development: If you generate an SRI hash from unminified development code, but your production CDN serves minified or gzipped/brotli code, the SRI check will fail if the server alters the decompressed bytes. Note: Gzip/Brotli transfer encoding does NOT alter the hash because the browser hashes the decompressed payload bytes.
  3. Missing output.crossOriginLoading in Webpack: When Webpack code-splits dynamic import() chunks, Webpack generates script tags on the fly. If output.crossOriginLoading: 'anonymous' is missing, Webpack generates dynamic script tags without CORS, causing dynamic chunks to fail SRI verification.

💡 Pro Tips

  1. Transfer Encoding Transparency: HTTP compression (like gzip, brotli, or zstd) is transparent to SRI. The browser applies the hash check to the decoded response body payload, not the compressed wire bytes.
  2. Automated CI/CD Parity Testing: In your CI/CD deployment pipeline, run a post-deployment verification job that fetches your production HTML, extracts all integrity attributes, fetches the corresponding live CDN files, and asserts that calculated hashes match live values before switching production traffic.

📌 Key Takeaways

  • Generate SRI hashes via OpenSSL CLI using openssl dgst -sha384 -binary file.js | openssl base64 -A.
  • The -binary flag is required in OpenSSL to avoid base64-encoding a hex string.
  • In Node.js, compute hashes with crypto.createHash('sha384').update(buffer).digest('base64').
  • Automated build plugins (such as webpack-subresource-integrity and vite-plugin-sri) inject hashes automatically into HTML templates during production builds.
  • SRI operates on the decompressed resource bytes, meaning gzip and brotli HTTP transfer encodings do not break verification.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Why does the command openssl dgst -sha384 script.js | openssl base64 produce an invalid SRI hash if the -binary flag is omitted?

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

How does HTTP Content-Encoding: gzip or br (Brotli compression) affect browser Subresource Integrity validation?

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

In Webpack applications using webpack-subresource-integrity, what setting is mandatory to ensure dynamically split code chunks load securely with SRI?

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