frontend / security / 05_sri_hsts_security_headers.md

Subresource Integrity, HSTS, Security Headers

5 min read source

Subresource Integrity, HSTS, Security Headers

TL;DR

The “set these headers and you’ve solved 80% of low-hanging frontend security issues” set. HSTS forces HTTPS; SRI verifies third-party script integrity; X-Frame-Options / frame-ancestors block clickjacking; Referrer-Policy controls what URL goes out; Permissions-Policy locks down browser APIs. Use securityheaders.com or observatory.mozilla.org to check your site’s score in 60 seconds.

Interview Q&A

Q: Strict-Transport-Security (HSTS) — what it does.

A: Tells the browser “this site is HTTPS only — for the next N seconds, don’t even try HTTP.”

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Effect:

  • For one year (31536000 seconds), browser refuses HTTP requests to this domain — even if user types http://, browser silently upgrades.
  • includeSubDomains — applies to all subdomains.
  • preload — opts into the HSTS preload list (browser ships knowing your domain is HTTPS-only, no first-request HTTP vulnerability).

Without HSTS: a man-in-the-middle attacker can intercept the first HTTP request, prevent the redirect to HTTPS, serve attacker content. With HSTS: browser refuses HTTP, attacker can’t downgrade.

Always set on production. preload requires meeting criteria + submission to hstspreload.org.

Q: Subresource Integrity (SRI).

A: Verifies that a third-party script/stylesheet hasn’t been tampered with:

<script src="https://cdn.example.com/lib.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
        crossorigin="anonymous"></script>

The browser:

  1. Fetches the script.
  2. Computes its SHA-384 hash.
  3. Compares to the integrity value.
  4. If mismatch — refuses to execute.

Use for any third-party script you load from a CDN. Without SRI, a CDN compromise (or attacker swapping the file) injects code into your origin.

crossorigin="anonymous" is required for SRI on cross-origin requests.

Generate hashes: openssl dgst -sha384 -binary lib.js | openssl base64 -A or via webpack-subresource-integrity plugin / Vite plugins.

Q: X-Frame-Options and frame-ancestors — clickjacking.

A: Both control who can frame your page (anti-clickjacking).

X-Frame-Options: DENY                  # legacy, single value
# or
Content-Security-Policy: frame-ancestors 'none'   # modern, more granular

frame-ancestors is part of CSP, more flexible: frame-ancestors 'self' https://trusted-partner.com.

If both are set, modern browsers prefer frame-ancestors. Set both for max compatibility.

Without either: an attacker iframes your page, overlays misleading buttons, tricks the user into clicking your real button thinking it’s something else (clickjacking). See 08_clickjacking_and_supply_chain.md.

Q: Referrer-Policy — what URL leaks.

A: Controls what the Referer header contains when the user navigates away or loads a subresource.

Referrer-Policy: strict-origin-when-cross-origin     # modern default

Common values:

Value What
no-referrer never send Referer
same-origin send full URL on same-origin, nothing cross
origin send only origin (https://example.com)
strict-origin origin only, but only over HTTPS
origin-when-cross-origin full URL same-origin, origin only cross-origin
strict-origin-when-cross-origin (default in Chrome 85+) like above, but downgrade safe
unsafe-url always send full URL

Why care: full URL (/users/12345/private-doc?token=...) leaking to third-party assets exposes sensitive data. The modern default is sane; don’t change unless you specifically need different behavior.

Q: Permissions-Policy (formerly Feature-Policy).

A: Disables browser APIs you don’t use, reducing attack surface.

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()

Empty parens = “no origins may use this API.” Even if XSS gets in, it can’t trigger getUserMedia because the policy denies.

Common deny list for typical apps: camera, microphone, geolocation, payment, usb, serial, bluetooth. Keep what you actually use.

Q: X-Content-Type-Options: nosniff.

A: Disables MIME-type sniffing — browser respects the Content-Type you sent.

X-Content-Type-Options: nosniff

Without it, browsers may “guess” content type and treat a text/plain response with JS-looking content as a script (historical IE issue, partly modernized). With nosniff, this can’t happen.

Always set. Trivial, removes a class of attack.

Q: Cross-Origin-Opener-Policy (COOP) and Cross-Origin-Embedder-Policy (COEP).

A: Newer isolation headers, mostly relevant for Spectre defense + enabling SharedArrayBuffer:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
  • COOP: prevents window.open’d page from interacting with the opener (and vice versa) across origins.
  • COEP: subresources must opt in via Cross-Origin-Resource-Policy header. Required for sites using shared memory or high-resolution timers.

For most apps: set COOP at minimum (same-origin). COEP only if you need SharedArrayBuffer (multithreaded WASM, advanced workers).

Q: Cross-Origin-Resource-Policy (CORP).

A: Server declaration: who can embed me as a subresource.

Cross-Origin-Resource-Policy: same-origin

Prevents cross-origin pages from loading this resource via <img>/<script>/etc. Useful for preventing unauthorized embeds of your assets.

Q: A senior’s baseline header set.

A:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; report-to csp-endpoint
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Cross-Origin-Opener-Policy: same-origin
X-Frame-Options: DENY                                    # backward-compat for frame-ancestors

Set at the edge (CDN, reverse proxy, framework middleware) so they apply uniformly. Frameworks (Next.js, Nuxt) have config to emit these.

// next.config.js
const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  // ...
];

module.exports = {
  async headers() {
    return [{ source: "/(.*)", headers: securityHeaders }];
  },
};

Q: How do you audit?

A: Tools:

  • securityheaders.com — scan, grade A+ to F, explain each missing header.
  • Mozilla Observatory (observatory.mozilla.org) — more comprehensive.
  • OWASP ZAP — automated security testing.
  • Lighthouse — has a “Best practices” audit that catches a subset.
  • CSP Evaluator (csp-evaluator.withgoogle.com) — analyzes your CSP for weaknesses.

Aim for A+ on securityheaders.com. Quick wins for most projects.

Gotchas / edge cases

  • HSTS + dev environments — local HTTPS dev with self-signed cert can cause localhost HSTS to stick. Use chrome://net-internals/#hsts to clear.
  • SRI breaks on minor library updates — every release has a different hash. Pin a version + hash; don’t auto-bump.
  • SRI + CORS — the CDN must serve with CORS headers; otherwise SRI check fails.
  • Referrer-Policy: no-referrer breaks analytics that depend on referrer. Test.
  • Permissions-Policy syntax changed from Feature-Policy — semicolons vs commas, etc. Use a generator.
  • COOP + analytics popups — third-party popups can’t talk to opener; may break OAuth or share dialogs. Use same-origin-allow-popups.
  • Headers vs <meta> — some headers (X-Frame-Options, HSTS) only work as headers, not meta tags. Always prefer headers.

What a senior is expected to say

  • “HSTS forces HTTPS-only; preload makes it permanent in browser distribution. SRI verifies third-party scripts haven’t been tampered with — essential for CDN-loaded libraries.”
  • “CSP frame-ancestors 'none' is the modern anti-clickjacking; X-Frame-Options: DENY is the legacy. Set both for compat.”
  • Referrer-Policy: strict-origin-when-cross-origin is the sane default — prevents leaking full URLs cross-origin.”
  • Permissions-Policy denies browser APIs you don’t use — reduces attack surface even if XSS gets in.”
  • “Baseline header set: HSTS, CSP, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP. Set at the edge, audit with securityheaders.com (target A+).”
  • “SRI requires crossorigin='anonymous' for cross-origin; the CDN must serve CORS headers.”

Cross-references

Further reading