Secure headers and HTTPS
Cicadel adds security headers to every response and enforces an HTTPS policy
that you control from settings. Generated projects start with secure values;
go run manage.go check --deploy warns about anything left off.
#Ask your agent
Configure HSTS, the HTTPS redirect, trusted proxies and the content security
policy for my Cicadel project, then make check --deploy pass without warnings.
#What every response gets
| Header | Value | Controlled by |
|---|---|---|
X-Content-Type-Options | nosniff | always |
Referrer-Policy | same-origin | always |
X-Frame-Options | DENY | always |
Content-Security-Policy | your policy | ContentSecurityPolicy |
Strict-Transport-Security | max-age=N on HTTPS responses | HSTSSeconds, never in debug |
A handler can set its own Content-Security-Policy; it replaces the default for
that response. The account pages do this with a stricter policy.
#Settings
Generated projects set these in project/settings/settings.go:
s := settings.Defaults()
s.HSTSSeconds = settings.HSTSYear
s.ContentSecurityPolicy = settings.DefaultContentSecurityPolicy
return settings.Load(s)| Field | Environment variable | Default | Purpose |
|---|---|---|---|
HSTSSeconds | CICADEL_HSTS_SECONDS | 0 (generated: one year) | Tell browsers to use HTTPS only. Sent only on HTTPS responses with debug off. |
SecureRedirect | CICADEL_SECURE_REDIRECT | false | Redirect plain-HTTP requests to HTTPS with 308, after the host check, debug off only. |
TrustedProxies | CICADEL_TRUSTED_PROXIES | none | Comma-separated IPs or CIDR prefixes of your reverse proxies. |
ContentSecurityPolicy | CICADEL_CONTENT_SECURITY_POLICY | empty (generated: default policy) | Policy for responses that set none. Empty sends no header. |
settings.DefaultContentSecurityPolicy is:
default-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'It allows scripts, styles, images, fonts and requests from your own origin only,
which fits the starter pages, Tailwind output and self-hosted htmx. Inline
<script> and style="…" attributes are blocked. If your static files come from
a CDN (CICADEL_STATIC_URL=https://cdn.example.net/…), add its origin, for
example default-src 'self' https://cdn.example.net; …; check --deploy warns
when it is missing.
HSTS is hard to undo: browsers remember it for max-age seconds. Start with a
short value such as 300 if you are unsure your whole domain serves HTTPS. Cicadel
never adds includeSubDomains or preload.
#Behind a reverse proxy
Most deployments terminate TLS at a proxy or load balancer. Cicadel only learns
that a request used HTTPS, and the real client address, from proxies listed in
TrustedProxies:
CICADEL_TRUSTED_PROXIES=10.0.0.0/8For a request whose direct peer is trusted:
X-Forwarded-Proto: https(last value) marks it as HTTPS, so HSTS is sent andSecureRedirectdoes not redirect it;- the client address in
r.RemoteAddrbecomes the rightmostX-Forwarded-Forentry that is not itself a trusted proxy, so clients cannot spoof it by prepending values.
Requests from any other peer keep their real address, and their forwarded
headers are ignored. Forwarded and X-Forwarded-Host are never used; the
host check always applies to the request’s own Host.
Check the scheme in handlers with security.IsHTTPS(r).
#Existing projects
Projects generated before v0.2.1 keep their behavior. Add the two lines above to
project/settings/settings.go, set CICADEL_TRUSTED_PROXIES and, unless your proxy
already redirects HTTP, CICADEL_SECURE_REDIRECT=true in production. Then run
go run manage.go check --deploy.