# 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`:

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

```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.
- That 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.

## 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`:

```sh
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. HSTS is sent and
  `SecureRedirect` does not redirect it.
- `r.RemoteAddr` becomes the rightmost `X-Forwarded-For` entry 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.

> [!NOTE]
> `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:

1. Add the two settings lines above to `project/settings/settings.go`.
2. Set `CICADEL_TRUSTED_PROXIES`.
3. In production, set `CICADEL_SECURE_REDIRECT=true`, unless your proxy already
   redirects HTTP.
4. Run `go run manage.go check --deploy`.
