Frontend / Security / 05_sri_hsts_security_headers.md

Subresource Integrity, HSTS, Security Headers

Updated 6 interview angles 6 min read source
On this page14
  1. Strict-Transport-Security (HSTS) — what it does.
  2. Subresource Integrity (SRI).
  3. X-Frame-Options and frame-ancestors — clickjacking.
  4. Referrer-Policy — what URL leaks.
  5. Permissions-Policy (formerly Feature-Policy).
  6. X-Content-Type-Options: nosniff.
  7. Cross-Origin-Opener-Policy (COOP) and Cross-Origin-Embedder-Policy (COEP).
  8. Cross-Origin-Resource-Policy (CORP).
  9. A senior’s baseline header set.
  10. How do you audit?
  11. Gotchas
  12. Related
  13. Further reading
  14. Interview angle

Subresource Integrity, HSTS, Security Headers

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.

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

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

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.

Subresource Integrity (SRI).

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

html
<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.

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

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

http
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 Clickjacking and Supply Chain Attacks.

Referrer-Policy — what URL leaks.

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

http
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.

Permissions-Policy (formerly Feature-Policy).

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

http
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.

X-Content-Type-Options: nosniff.

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

http
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.

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

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

http
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).

Cross-Origin-Resource-Policy (CORP).

Server declaration: who can embed me as a subresource.

http
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.

A senior’s baseline header set.

http
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
# backward-compat for frame-ancestors
X-Frame-Options: DENY

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

ts
// 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 }];
  },
};

How do you audit?

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

  • 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.

Further reading

Interview angle 6

  • “What does HSTS do that a redirect does not?” - it makes the browser refuse plain HTTP for your domain before it sends anything, so the very first request cannot be intercepted. A redirect from HTTP still involves one unprotected request, which is the one an attacker on the network wants. Preloading ships that rule with the browser, so even a first-ever visit is covered - and it is genuinely hard to undo.
  • “What is Subresource Integrity for?” - pinning the content of a third-party script by hash, so a CDN serving different bytes than the ones you reviewed fails closed. It needs crossorigin="anonymous" and the host must send CORS headers, or the browser cannot read the response to hash it.
  • “How do you stop your site being framed?” - frame-ancestors 'none' in CSP, which is the modern mechanism and supports an allowlist. X-Frame-Options: DENY alongside it for older clients. It is worth setting both, since the cost is one header.
  • “What referrer policy would you set?” - strict-origin-when-cross-origin, which sends the full URL within your own origin and only the origin externally. The default leaks paths and query strings - including tokens and internal identifiers - to every third party you load a resource from.
  • “What does Permissions-Policy buy you?” - it denies browser APIs you do not use, so camera, microphone, geolocation and payment are unavailable even to injected code. It shrinks what a successful XSS can reach, which is defence in depth rather than prevention.
  • “What is the baseline set?” - HSTS, CSP, X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy and Cross-Origin-Opener-Policy, set at the edge so every response gets them rather than depending on application code remembering.