dnsprobe v1

HSTS, the preload list, and the irreversible commitment

· ~3 min read · dnsprobe.net/blog

HTTP Strict Transport Security is one HTTP response header (RFC 6797). It tells the browser: "the next time you connect to this hostname, do not even bother trying HTTP. Go straight to HTTPS. Trust no redirect, trust no link, trust no certificate warning. Refuse plain HTTP entirely." It's powerful, simple, and one of the very few HTTP headers you can ship that has long-lasting client-side state.

The header

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

Three directives:

  • max-age — how many seconds the browser should remember the policy. 31536000 is one year. The clock resets every time the browser sees the header.
  • includeSubDomains — apply the policy to every subdomain as well, not just the exact hostname that served the header.
  • preload — request inclusion in the browser's hard-coded HSTS preload list. This is the dangerous one.

The mechanism, slowly

A browser visiting https://example.com for the first time observes the HSTS header in the response. It writes to its internal HSTS store: "for example.com, for the next 31536000 seconds, refuse non-HTTPS connections, and if a cert error occurs, do not offer the user the option to bypass it." The next time the user types http://example.com, the browser internally rewrites that to https://example.com before even resolving DNS. The TCP SYN never goes to port 80.

The first visit is the trust-on-first-use moment. If an attacker intercepts that first request and strips the header, the policy never gets set, and a subsequent MITM can downgrade to HTTP. This is the bootstrap weakness that the preload list closes.

The preload list

The HSTS preload list is a hard-coded set of domains baked into the browser binary itself (Chrome, then synced to Firefox, Safari, Edge, every Chromium-derived browser). For domains on the list, HSTS is enforced from the very first connection, no header needed. There is no first-visit window.

To get on the list, you submit your domain at hstspreload.org. Requirements:

  1. Serve a valid HSTS header with max-age of at least 31536000.
  2. Include the includeSubDomains and preload directives.
  3. Redirect HTTP to HTTPS at the apex.
  4. Serve a valid cert with no chain issues.

If you meet the criteria, your domain is added to the list in the next Chrome stable release (typically 4-12 weeks later). It then ships to every Chromium-derived browser. Eventually it propagates to Safari and Firefox via their own mirror schedules.

The trap

Once you are on the preload list, removing yourself is a months-to-years process:

  1. Submit a removal request at the same site.
  2. It is processed manually.
  3. Even after acceptance, it ships in the next Chrome release.
  4. Then propagates over months to old browser versions.
  5. Any user whose browser does not auto-update will keep enforcing the policy until their next browser install.

If you preload a domain and then later need to host a subdomain over plain HTTP — for an embedded device, a captive portal, an internal service that can't get a cert — you have a real problem. The browser will refuse to connect to that subdomain even if the parent domain owner is the one explicitly choosing to allow it.

The pragmatic rollout

The standard playbook:

  1. Start with max-age=600 (10 minutes) and no includeSubDomains. Observe for a week. Any subdomain that should have been HTTPS but is broken on HTTP only? Find them now.
  2. Add includeSubDomains. Observe for another week.
  3. Raise max-age to 86400 (1 day). Observe.
  4. Raise max-age to 31536000 (1 year).
  5. Only then, if you are absolutely certain, add preload and submit.

Stage 5 is the irreversible step. Do not skip 1-4.

Verifying it

From the command line:

curl -sI https://example.com | grep -i strict-transport

To check whether a domain is in the preload list itself: hstspreload.org's lookup form, or programmatically via the Chrome source tree at chrome/browser/net/transport_security_state_static.json.

The HTTP-headers panel on dnscheck shows the HSTS header presence and parsed max-age in one shot. Try github.com, which has been preloaded since 2016.

Reference: RFC 6797, hstspreload.org.