Context
Modulo currently sets no security-related response headers on outgoing responses. Migrating from Apache exposes a real gap: existing Apache vhost configs typically include at minimum an HSTS header (Strict-Transport-Security), and modulo doesn't reproduce it. Sites that had HSTS yesterday don't have it today.
This issue is filed early — well before implementation — so that the shape of the per-Site config for security headers is decided before iteration 3 lands a config schema. Once the config format is in use by operators, changing it is expensive.
What we want to expose
HSTS is the headline. A few others are commonly bundled with it at the proxy layer. Most other security headers (CSP, Permissions-Policy, etc.) are app concerns and should NOT live in modulo.
HSTS — first-class
site:
hsts:
enabled: true # default true
max_age: 31536000 # 1 year, default
include_subdomains: false # default; operator opts in
preload: false # default; operator opts in
Notes:
- Default
max_age of 31536000 (1 year) matches current Mozilla/Google guidance.
includeSubDomains is opt-in because applying it to a site that doesn't control all subdomains is a foot-gun.
preload is opt-in because once you submit to the HSTS preload list, removal is hard.
- Disabling HSTS per-Site needs to be possible (e.g. for a site in mid-migration that isn't yet always-HTTPS).
Other security headers — opt-in, simple values
These are useful enough at the proxy that operators reasonably want to set them centrally rather than rely on every app to do so. They should be optional and simple.
site:
security_headers:
x_content_type_options: nosniff # opt-in
x_frame_options: SAMEORIGIN # opt-in; or DENY
referrer_policy: strict-origin-when-cross-origin # opt-in
No support for Content-Security-Policy at the proxy level — too tied to what the app actually serves, and bad defaults will silently break things. CSP belongs in the app.
Permissions-Policy likewise — app concern.
Explicit non-goals
- Cipher suite configuration,
SSLProtocol enable/disable, SSLHonorCipherOrder — modern JDK + Jetty defaults are already correct (TLS 1.2 + 1.3, AEAD-preferred ciphers, server cipher order). Exposing these as config invites operators to write outdated lists, which is worse than library defaults. The library is the right place to trust here. (This conclusion may need revisiting if FIPS-mode or formal compliance audits ever become a use case.)
- OCSP stapling configuration. It's an implementation detail — either modulo does it when the cert's CA supports it, or it doesn't. Not a per-Site config knob. (Also: Let's Encrypt has sunsetted OCSP as of 2025, so for our deployments it's effectively moot.)
Shape implications for iteration 3
The Site config schema needs to accommodate a hsts sub-object (or equivalent — exact format depends on whether we end up with HOCON, YAML, TOML, etc.) with the fields above, and a security_headers sub-object. Implementation can come later; the shape needs to be settled when the schema is.
Related
Context
Modulo currently sets no security-related response headers on outgoing responses. Migrating from Apache exposes a real gap: existing Apache vhost configs typically include at minimum an HSTS header (
Strict-Transport-Security), and modulo doesn't reproduce it. Sites that had HSTS yesterday don't have it today.This issue is filed early — well before implementation — so that the shape of the per-Site config for security headers is decided before iteration 3 lands a config schema. Once the config format is in use by operators, changing it is expensive.
What we want to expose
HSTS is the headline. A few others are commonly bundled with it at the proxy layer. Most other security headers (CSP, Permissions-Policy, etc.) are app concerns and should NOT live in modulo.
HSTS — first-class
Notes:
max_ageof 31536000 (1 year) matches current Mozilla/Google guidance.includeSubDomainsis opt-in because applying it to a site that doesn't control all subdomains is a foot-gun.preloadis opt-in because once you submit to the HSTS preload list, removal is hard.Other security headers — opt-in, simple values
These are useful enough at the proxy that operators reasonably want to set them centrally rather than rely on every app to do so. They should be optional and simple.
No support for
Content-Security-Policyat the proxy level — too tied to what the app actually serves, and bad defaults will silently break things. CSP belongs in the app.Permissions-Policylikewise — app concern.Explicit non-goals
SSLProtocolenable/disable,SSLHonorCipherOrder— modern JDK + Jetty defaults are already correct (TLS 1.2 + 1.3, AEAD-preferred ciphers, server cipher order). Exposing these as config invites operators to write outdated lists, which is worse than library defaults. The library is the right place to trust here. (This conclusion may need revisiting if FIPS-mode or formal compliance audits ever become a use case.)Shape implications for iteration 3
The Site config schema needs to accommodate a
hstssub-object (or equivalent — exact format depends on whether we end up with HOCON, YAML, TOML, etc.) with the fields above, and asecurity_headerssub-object. Implementation can come later; the shape needs to be settled when the schema is.Related