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

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

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

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