Service Workers, TLS,
Security Headers & SRI

The platform infrastructure layer — config and runtime that protects every user before your app code ever runs.

1 TLS / HTTPS — the transport trust layer

TLS does exactly three things: confidentiality (encrypt in transit), integrity (detect tampering), and authenticity (prove you're talking to the claimed server via a certificate). All three are required — encrypting the wrong server is useless.

TLS protects
✓ Payload content (encrypted)
✓ Tamper detection (MAC)
✓ Server identity (cert chain)
✓ Replay attacks (session keys)
TLS does NOT protect
✗ Who you talk to (IP/DNS visible)
✗ Traffic volume / timing
✗ Server-side code bugs
✗ Client-side XSS (L19)

HSTS — HTTP Strict Transport Security

Once a browser sees Strict-Transport-Security: max-age=63072000; includeSubDomains; preload it never sends http:// for that origin — even if the user types it. The first visit is still vulnerable (TOFU: trust on first use). The preload list solves this: browsers ship with a hardcoded list of HSTS domains so even the first request is HTTPS. Submit via hstspreload.org — preload is irreversible short-term, you need includeSubDomains, and all your subdomains must be HTTPS-ready first.

Mixed content

Active mixed content (scripts, iframes, stylesheets over HTTP on an HTTPS page) is always blocked — the network context would be compromised. Passive mixed content (images, media) was historically shown with a warning but Chrome now auto-upgrades or blocks it. Fix: use protocol-relative URLs (//cdn.example.com/...) or HTTPS everywhere. The upgrade-insecure-requests CSP directive automatically upgrades passive HTTP requests.

One-liner

TLS proves you're on an encrypted channel to the right server — it can't stop your own XSS or server bugs. Set HSTS with long max-age and preload once your entire subdomain estate is HTTPS-ready. That's a platform decision, not a per-page one.

2 Security headers — set once, protect everywhere

These go in your nginx/CDN config (or middleware), not in every HTML file. L19 covered Content-Security-Policy and frame-ancestors. The remaining headers complete the hardening set.

HeaderWhat it doesRecommended value
X-Content-Type-Options Stops browsers from MIME sniffing — executing a response as a different type than its Content-Type (e.g., serving JS as text/plain but browser runs it) nosniff
Referrer-Policy Controls what URL is sent in the Referer header on navigation/requests. Sensitive: booking URLs, auth tokens in query strings strict-origin-when-cross-origin (full URL same-origin, only origin cross-origin)
Permissions-Policy Disables browser APIs for the page and its embedded iframes. Defense against malicious third-party scripts requesting geolocation/camera geolocation=(), camera=(), microphone=() — disable what you don't need
Strict-Transport-Security HSTS (§1 above) max-age=63072000; includeSubDomains
X-Frame-Options Legacy clickjacking defense (L19). Superseded by CSP frame-ancestors SAMEORIGIN (for browsers that don't support CSP)
One-liner

Security headers are infra config — set them in your CDN or server and they apply to every response. A Lead governs these at the platform level rather than trusting each team to add them per-route. Run securityheaders.com on your domain to audit coverage.

Permissions-Policy gotcha: Permissions-Policy: interest-cohort=() was commonly used to opt out of FLoC (Google's ad targeting API, now deprecated). Its replacement — the Privacy Sandbox Topics API — requires its own opt-out. These headers need to be revisited as browser APIs evolve, not set once and forgotten.

3 Service Workers & the Cache API

A Service Worker is a JavaScript proxy running in a background thread between your page and the network. It can intercept every fetch request, serve from a local cache, or even respond when offline. It does not run on the main thread — no DOM access — and it persists across page loads.

Source: web.dev — The Service Worker Lifecycle · Chrome for Developers — Workbox

Lifecycle: install → activate → fetch

Parsed
SW script downloaded
Installing
open cache, precache assets
Installed
waiting for old SW to leave
Activating
delete old caches
Active
intercepts fetch events
// sw.js — install: cache the app shell
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('v1').then(cache =>
      cache.addAll(['/', '/app.js', '/styles.css'])
    )
  );
  self.skipWaiting(); // take over immediately — see pitfall below
});

// activate: clean up old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(keys.filter(k => k !== 'v1').map(k => caches.delete(k)))
    )
  );
  self.clients.claim(); // take control of existing pages immediately
});

The three cache strategies

Cache-first (offline-first)
Return from cache; fall back to network; update cache in background
Use for: app shell (HTML, JS, CSS with content-hashed filenames), fonts, vendor assets — things that change rarely and are identified by a new URL when they do.
Trade-off: user sees stale content until cache is busted by a version change.
Network-first
Try network first with a timeout; fall back to cache on failure
Use for: API responses where freshness matters (hotel availability, prices, user data). Cache serves as a safety net for offline, not the primary source.
Trade-off: adds network latency; if the network is slow but not dead, users wait.
Stale-while-revalidate
Return cache immediately; trigger a network fetch in background to update cache
Use for: non-hashed assets that update occasionally (avatars, feature configs, non-critical API data). Fast AND eventually fresh — L08's SWR principle applied at the network layer.
Trade-off: one load behind (user always sees last-known-good, not always latest).
// fetch: cache-first for JS/CSS, network-first for API
self.addEventListener('fetch', event => {
  const { request } = event;

  if (request.url.includes('/api/')) {
    // network-first for API
    event.respondWith(
      fetch(request)
        .then(res => { /* cache and return */ return res; })
        .catch(() => caches.match(request))
    );
  } else {
    // cache-first for static assets
    event.respondWith(
      caches.match(request).then(cached => cached || fetch(request))
    );
  }
});

The update lifecycle pitfall — the most dangerous SW trap

A new SW version (even a 1-byte change) enters "waiting" state — it won't activate until all tabs using the old SW are closed. This means users on long-lived sessions never get the update.

Common "fix" and its trap: calling skipWaiting() in install forces the new SW to activate immediately. But now the new SW is serving requests for a page that was loaded with the old SW's assets. If the new and old versions of assets are incompatible, the page breaks silently.

The skipWaiting trade-off:
One-liner

A Service Worker is a client-side CDN you control — cache-first for app shell, network-first for live data. The hidden trap is the update lifecycle: a new SW waits for all tabs to close. Ship content-hashed assets so old and new coexist safely, then give users a reload prompt.

4 Subresource Integrity & supply-chain security

SRI — locking external files

Subresource Integrity lets you pin a specific external file to a cryptographic hash. The browser computes the hash of what it fetches and refuses to execute it if it doesn't match.

<!-- If the CDN is compromised and the file changes, browser blocks it -->
<script
  src="https://cdn.example.com/react.production.min.js"
  integrity="sha384-Znh5+iBGuvijOqLzOG+Lzxjbii7bJIU6MkHMoJ3B5j+Qra/A2SmMaSCWzxLQKA=="
  crossorigin="anonymous"></script>

crossorigin="anonymous" is required — SRI performs a CORS fetch, so the CDN must send CORS headers and you must set the attribute.

SRI protects

CDN compromise

If an attacker modifies a file on the CDN (or poisons the cache), the hash mismatch blocks it. Protects third-party libraries loaded from external CDNs.

SRI does NOT protect

Your bundled npm deps

Dependencies bundled into your own JS are already inside your code — SRI can't help. You need lockfiles + audit for those.

npm supply chain risk

Your node_modules tree is a supply chain attack surface. Real incidents: event-stream (2018, injected crypto mining), colors/faker (2022, maintainer sabotage), ua-parser-js (2021, account hijack). Attack vectors:

Defenses at each layer

LayerDefense
Lockfile Commit package-lock.json/yarn.lock. Exact versions + hashes. npm ci (not npm install) in CI — fails on lockfile mismatch.
Audit npm audit + Dependabot/Snyk in CI — gate merges on high-severity CVEs.
Private registry Proxy npm through Artifactory/Verdaccio. Allowlist — only pre-approved packages pass through. Fixes dependency confusion (private packages resolve via your registry first).
SBOM Software Bill of Materials — generate a manifest of every dependency + version for compliance and incident response.
External scripts SRI + crossorigin for CDN-loaded libs. Self-host critical third-party scripts when possible (breaks SRI auto-update, so weigh the trade-off).
CSP script-src allowlist limits where scripts can load from (L19). Combined with SRI: belt + braces.
One-liner

SRI pins a specific external file to a hash — great for CDN-loaded libs. Your bundled npm tree is a different surface: lockfiles + npm ci + Dependabot + private registry. A Lead treats the supply chain as an attack surface and builds automated gates, not manual reviews.

Self-hosting vs SRI trade-off: Self-hosting a third-party library removes CDN-compromise risk and eliminates the SRI breakage problem (you update the hash when you update the file). But it also means you're responsible for updates — security patches require a deployment from you, not the CDN operator. For critical security libraries (analytics with auth context, payment widgets), self-host. For read-only libs, SRI + CDN is fine.

5 The complete security headers checklist

Headers you should be able to recite for the round, grouped by threat:

ThreatHeader(s)Lesson
XSS executionContent-Security-Policy (script-src, nonce, strict-dynamic)L19
ClickjackingContent-Security-Policy: frame-ancestors, X-Frame-OptionsL19
MIME sniffingX-Content-Type-Options: nosniffL20
Protocol downgradeStrict-Transport-SecurityL20
Referrer leakageReferrer-Policy: strict-origin-when-cross-originL20
API abuse / fingerprintingPermissions-PolicyL20
CDN compromiseintegrity attribute (SRI)L20

6Check yourself — scenario quiz

0 / 8 correct

1. A user visits your site via HTTP, gets redirected to HTTPS, sees your HSTS header (max-age=2 years). Three months later they type "yoursite.com" in the browser. What happens?
2. You ship a new Service Worker. The user has an existing tab open from the old SW. What happens by default (no skipWaiting)?
3. the hotel listing page shows real-time prices that change every few minutes. Which Service Worker caching strategy fits?
4. A developer adds skipWaiting() to force the new SW to activate instantly. What is the main risk?
5. Your page loads a script from a CDN with an SRI hash. An attacker compromises the CDN and modifies the file. What happens?
6. Which header prevents a browser from executing a JavaScript file served with Content-Type: text/plain?
7. A payment flow URL contains a token: https://example.com/checkout?session=abc123. After the user clicks an affiliate link on the checkout page, the affiliate site logs the Referer header and steals the token. Which header prevents the full URL from leaking?
8. A developer installs a popular npm package. Unknown to them, an attacker had pushed a malicious version to npm 2 weeks ago. The team uses package-lock.json but runs npm install (not npm ci). Are they protected?
Out-loud drill — do this before next session

"Walk me through every HTTP response header you'd set on a high-traffic HTTPS booking page to harden it against the most common attacks. Explain what each one stops."

Target: cover CSP (script-src + nonce + frame-ancestors), HSTS, nosniff, Referrer-Policy, Permissions-Policy, X-Frame-Options fallback. Should take ~2 minutes. No notes.

Good follow-up topics:

SW + Next.js — how does this interact with SSR? Workbox strategies in depth Certificate Transparency logs How would the platform use SWs for offline hotel search? TLS 1.3 vs 1.2 handshake diff Permissions-Policy real-world examples SBOM generation in CI Mixed content: upgrade-insecure-requests