Security headers
After this you can answer: What do the security headers protect against, and how do you check them?
Picture it
Section titled “Picture it”flowchart TB W["web page answer"] --> WH["CSP: nonce + strict-dynamic<br/>frame denial; nosniff<br/>HSTS: later HTTPS visits, 1 day"] A["API answer"] --> AH["CSP: default-src 'none'<br/>frame denial; nosniff"] A --> M["bad Origin or cross-site<br/>mutation metadata: 403"]How it works
Section titled “How it works”- Script trust. The web proxy makes a nonce per response. In browsers supporting
strict-dynamic, scripts with that nonce can also load scripts without it. This blocks injected script tags without that trust; it does not fix every cross-site scripting (XSS) bug. - Framing.
X-Frame-Options: DENYandframe-ancestors 'none'stop other sites from showing our pages in a frame (clickjacking). - Sniffing and referrer.
X-Content-Type-Options: nosniffprevents guessing a script or style’s MIME type.Referrer-Policy: strict-origin-when-cross-originsends only the origin to another HTTPS site, and no referrer when downgrading from HTTPS to HTTP. - HTTP Strict Transport Security (HSTS).
Strict-Transport-Security: max-age=86400tells the browser to use only HTTPS for a day after receiving this header over HTTPS. Later typedhttp://visits upgrade before any HTTP request; the first visit is not protected by this header.
In MiniTickets
Section titled “In MiniTickets”The web policy is built in apps/web/src/lib/security-policy.ts → securityPolicy
and set per request in apps/web/src/proxy.ts → proxy, which also sends a
Permissions-Policy that turns off camera, microphone and geolocation. HSTS is sent only by a
production build, only over HTTPS, and never for localhost (apps/web/next.config.ts).
The API uses default-src 'none' in its CSP and sends the same framing and sniffing headers.
For every request that is not GET, HEAD or OPTIONS (every tRPC
mutation is a POST), it requires the exact web Origin and rejects Sec-Fetch-Site: cross-site,
answering 403 on failure
(apps/api/src/http/security.ts → mutationAllowed). That blocks cross-site
request forgery (CSRF): unwanted changes from another site, alongside the SameSite cookie.
See it in AWS
Section titled “See it in AWS”Not an AWS setting: the apps send these headers. Open the browser’s developer tools → Network → a page → Response headers.
curl -sI https://minitickets.theversehub.in/ prints the response headers.
Break it
Section titled “Break it”| Symptom | Cause | Where to look |
|---|---|---|
| A page loads without styles; console shows a CSP error | The CSP does not allow that source | security-policy.ts; browser console |
| A POST from a script returns 403 | Missing/wrong Origin, or Sec-Fetch-Site: cross-site | mutationAllowed |
| No HSTS header in CloudFront mode | The web app never sees HTTPS there | next.config.ts comment |
Say it in an interview
Section titled “Say it in an interview”Follow-ups
Section titled “Follow-ups”Why is HSTS only one day?
If the certificate expires, browsers with an active HSTS policy refuse to bypass that certificate error. Restoring valid HTTPS fixes access. A one-day policy limits how long that restriction lasts if HTTPS stays broken; the lifetime can grow later.
Why a nonce instead of listing allowed script URLs?
Next.js adds inline scripts. A nonce authorizes those scripts for this response, and
strict-dynamic lets them load their own chunks, without allowing every inline script.
What is the trade-off of a fresh nonce per response?
Sharing cached HTML would reuse its nonce. To keep a fresh nonce per response, MiniTickets renders
per request and sets private, no-store. That costs more rendering work than shared HTML caching.
Advanced: Why HSTS skips localhost and CloudFront mode
HSTS is per host name for all ports, so an entry for localhost would force HTTPS on every local
project in that browser. In CloudFront mode CloudFront talks HTTP to the ALB, so the web app never
sees x-forwarded-proto: https and sends no HSTS; that would be CloudFront’s job (a response headers
policy, not built). Development adds unsafe-eval and local connections to the CSP for hot reload.
Browser behavior: CSP script trust
and HSTS.
Checkpoint
Section titled “Checkpoint”Answer these without scrolling up. If one feels hard, reread that section.
- What does the nonce allow, and what extra trust does
strict-dynamicadd? - Which two headers stop clickjacking?
- What does HSTS do that the ALB’s 301 redirect cannot?
- What does the API check on every POST, and what does it answer if it fails?
- Why does the API’s CSP say
default-src 'none'?
Redraw it from memory: the web answer's headers and the API answer's headers, side by side
Try this: run curl -sI against the local web app and the local API and compare their headers
Read this book online: https://book.theversehub.in/