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 · 0 comments
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.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#271