HSTS is a hardcoded 2-year includeSubDomains preload commitment with no opt-out and no documentation of its value #271

Open
opened 2026-08-24 02:11:58 +02:00 by clawbot · 1 comment
Collaborator

Found by a TLS deployment audit running real nginx in front of the app.

Every response carries Strict-Transport-Security: max-age=63072000; includeSubDomains; preload, unconditionally and with no configuration option. The README names HSTS twice and never states the value.

An operator deploying at hooks.example.com therefore silently commits every subdomain of example.com to HTTPS-only for two years, and preload invites permanent inclusion in browser preload lists. That is a decision with consequences well beyond this service, made on the operator's behalf without telling them.

Verified it is also emitted over plaintext direct to the app (curl http://127.0.0.1:19001/pages/login returns the header). Browsers ignore HSTS over plaintext, so that part is harmless — noting it only for completeness.

Nothing in the app broke under HSTS during testing; this is about the commitment being invisible, not about it malfunctioning.

Not milestoned: the header is correct and desirable for the intended deployment, and the risk is limited to operators who own a parent domain with non-HTTPS subdomains.

Definition of done:

  • The max-age, includeSubDomains and preload values are documented in README where HSTS is mentioned, stating plainly that includeSubDomains affects the whole parent domain and that preload is effectively irreversible on browser timescales.
  • HSTS is configurable — at minimum the ability to drop includeSubDomains and preload, since those are the two that reach beyond this service. Keep the current values as the default; this is about an escape hatch, not a weakening.
  • If made configurable, the iron rule applies: a set-but-unparseable value must abort startup, never silently fall back.
Found by a TLS deployment audit running real nginx in front of the app. Every response carries `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`, unconditionally and with no configuration option. The README names HSTS twice and never states the value. An operator deploying at `hooks.example.com` therefore silently commits **every subdomain of `example.com`** to HTTPS-only for two years, and `preload` invites permanent inclusion in browser preload lists. That is a decision with consequences well beyond this service, made on the operator's behalf without telling them. Verified it is also emitted over plaintext direct to the app (`curl http://127.0.0.1:19001/pages/login` returns the header). Browsers ignore HSTS over plaintext, so that part is harmless — noting it only for completeness. Nothing in the app broke under HSTS during testing; this is about the commitment being invisible, not about it malfunctioning. Not milestoned: the header is correct and desirable for the intended deployment, and the risk is limited to operators who own a parent domain with non-HTTPS subdomains. Definition of done: - The `max-age`, `includeSubDomains` and `preload` values are documented in README where HSTS is mentioned, stating plainly that `includeSubDomains` affects the whole parent domain and that `preload` is effectively irreversible on browser timescales. - HSTS is configurable — at minimum the ability to drop `includeSubDomains` and `preload`, since those are the two that reach beyond this service. Keep the current values as the default; this is about an escape hatch, not a weakening. - If made configurable, the iron rule applies: a set-but-unparseable value must abort startup, never silently fall back.
Author
Collaborator

Question for the owner. The README now documents the header and its value ("HSTS is always sent, and is not configurable"), so the documentation half of this is done. What remains is whether the value keeps preload.

  • Keep as is: max-age=63072000; includeSubDomains; preload. Any host name this runs under, and every subdomain of it, is HTTPS-only for two years, and browsers may bake that into their preload lists permanently.
  • Drop preload (recommended): max-age=63072000; includeSubDomains. Still meets REPO_POLICIES.md (one year minimum, includeSubDomains), and stops the app from inviting a permanent, browser-side commitment on the operator's behalf. Preloading is a decision for whoever owns the domain, made at hstspreload.org, not by a service that happens to run under it.

An opt-out variable is not proposed: the policy says the header is always sent.

If you want it dropped, reply here and it becomes a one-line change plus the README sentence; if you want it kept, this issue closes as documented.

Model: fable-5-1

Question for the owner. The README now documents the header and its value ("HSTS is always sent, and is not configurable"), so the documentation half of this is done. What remains is whether the value keeps `preload`. - Keep as is: `max-age=63072000; includeSubDomains; preload`. Any host name this runs under, and every subdomain of it, is HTTPS-only for two years, and browsers may bake that into their preload lists permanently. - Drop `preload` (recommended): `max-age=63072000; includeSubDomains`. Still meets `REPO_POLICIES.md` (one year minimum, `includeSubDomains`), and stops the app from inviting a permanent, browser-side commitment on the operator's behalf. Preloading is a decision for whoever owns the domain, made at hstspreload.org, not by a service that happens to run under it. An opt-out variable is not proposed: the policy says the header is always sent. If you want it dropped, reply here and it becomes a one-line change plus the README sentence; if you want it kept, this issue closes as documented. Model: fable-5-1
sneak was assigned by clawbot 2026-09-21 09:20:32 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#271