cicadel

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

HeaderValueControlled by
X-Content-Type-Optionsnosniffalways
Referrer-Policysame-originalways
X-Frame-OptionsDENYalways
Content-Security-Policyyour policyContentSecurityPolicy
Strict-Transport-Securitymax-age=N on HTTPS responsesHSTSSeconds, 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:

Go
s := settings.Defaults()
s.HSTSSeconds = settings.HSTSYear
s.ContentSecurityPolicy = settings.DefaultContentSecurityPolicy
return settings.Load(s)
FieldEnvironment variableDefaultPurpose
HSTSSecondsCICADEL_HSTS_SECONDS0 (generated: one year)Tell browsers to use HTTPS only. Sent only on HTTPS responses with debug off.
SecureRedirectCICADEL_SECURE_REDIRECTfalseRedirect plain-HTTP requests to HTTPS with 308, after the host check, debug off only.
TrustedProxiesCICADEL_TRUSTED_PROXIESnoneComma-separated IPs or CIDR prefixes of your reverse proxies.
ContentSecurityPolicyCICADEL_CONTENT_SECURITY_POLICYempty (generated: default policy)Policy for responses that set none. Empty sends no header.

settings.DefaultContentSecurityPolicy is:

Text
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:

Terminal
CICADEL_TRUSTED_PROXIES=10.0.0.0/8

For a request whose direct peer is trusted:

  • X-Forwarded-Proto: https (last value) marks it as HTTPS, so HSTS is sent and SecureRedirect does not redirect it;
  • the client address in r.RemoteAddr becomes the rightmost X-Forwarded-For entry 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.

Type to search this version.