Implement HTTP Strict Transport Security (HSTS)
Introduction
HSTS is a header that forces the browser to always use HTTPS for a domain, for a defined duration — even if the user types http:// or clicks an old unencrypted link.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadThis page documents the fleet-wide audit tracked in issue #326 (September 2026).
The problem it solves: downgrade / SSL-stripping
Even if a site only ever serves HTTPS, there’s still a gap: the very first request. If a user types example.com without https://, or clicks an old http:// link, the browser sends that first request unencrypted — before the server gets a chance to redirect it to HTTPS.
On a shared network (public wifi, a compromised router), an attacker can intercept that first HTTP request and read or redirect it before the HTTPS redirect ever happens. This is a downgrade attack (SSL-stripping): the attacker keeps the conversation on unprotected HTTP while the user believes they’re safe.
HSTS closes this gap: the first time the browser visits the site over HTTPS, it remembers this header for max-age seconds. After that, it rewrites http:// to https:// locally, before sending anything on the network — so there is nothing left to intercept.
Different protection scope than CSP: CSP protects what happens inside the page once it’s loaded (scripts, styles). HSTS protects the connection itself, before the page starts loading at all.
The 3 directives
| Directive | Meaning |
|---|---|
max-age=63072000 | Duration in seconds — 2 years. The browser enforces the rule for this long after the last visit. |
includeSubDomains | Also protects every subdomain (dashboard.phpr.link, login.phpr.link, …), not just the exact domain. |
preload | Submits the domain to a list built into browsers (Chrome, Firefox, …) via hstspreload.org . Without it, the browser needs one HTTPS visit to “learn” the rule — with it, protection exists from the very first visit, ever. |
preload has a cost: once a domain is accepted into the browsers’ preload list, it’s slow and hard to remove (can take months to propagate through browser updates). Only enable it once HTTPS is genuinely stable and permanent across the domain and all its subdomains.
How to add it
Unlike CSP, HSTS’s value never changes between requests — no nonce, nothing dynamic. next.config.js’s headers() is enough; no middleware.ts needed.
// next.config.js
const nextConfig = {
async headers() {
return [
{
source: "/(.*)", // apply to all routes
headers: [
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
],
},
];
},
};Some apps gate it behind process.env.NODE_ENV === "production" — reasonable, since forcing HTTPS on localhost during local dev gets in the way.
How to verify: DevTools → Network tab → select the document request → Response Headers → look for Strict-Transport-Security.
Rollout status (September 2026 audit)
| App | Status |
|---|---|
| Dashboard, Account, Doc, Login, Inventory, Report | Already present |
| POS | Already present, but was missing preload — added for consistency with the rest of the fleet |
| Feedback, Print, Email, Punch, CRUDv2, Account CRUD v2, Configuration CRUD v2, Ticket CRUD v2 | Added — had no headers() at all before |
The tracking issue’s checklist wasn’t fully up to date — Inventory was already implemented but unchecked. Verify the actual next.config.js rather than trusting the checklist alone.
Security checklist