Secure headers and HTTPS
Cicadel adds security headers to every response and enforces an HTTPS policy you
control from settings. If you know Django, this is the job of its
SecurityMiddleware.
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. |
Warning
HSTS is hard to undo. Browsers remember it for max-age seconds. If you are not
sure your whole domain serves HTTPS, start with a short value such as 300.
Cicadel never adds includeSubDomains or preload.
#Content security policy
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.
- That fits the starter pages, Tailwind output and self-hosted htmx.
- Inline
<script>andstyle="…"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.
#Behind a reverse proxy
Most deployments terminate TLS at a proxy or load balancer. Cicadel learns that a
request used HTTPS, and the real client address, only 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. HSTS is sent andSecureRedirectdoes not redirect it.r.RemoteAddrbecomes the rightmostX-Forwarded-Forentry that is not itself a trusted proxy. Clients cannot spoof it by prepending values.
Requests from any other peer keep their real address, and their forwarded headers are ignored.
Good to know
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. To upgrade:
- Add the two settings lines above to
project/settings/settings.go. - Set
CICADEL_TRUSTED_PROXIES. - In production, set
CICADEL_SECURE_REDIRECT=true, unless your proxy already redirects HTTP. - Run
go run manage.go check --deploy.