auth, headers: the examples are refused, HTTPS is insisted on

The server no longer starts with WORKSPACE_SECRET or WORKSPACE_PASSWORD
left at the value .env.example ships with. The secret was only checked
for length, and the public placeholder passed: anyone who had read the
repository could sign a session cookie for themselves. A secret under
32 characters is warned about.

With WORKSPACE_SECURE_COOKIE=1 the cookie is named __Host-diary_session,
which the browser keeps to this host and to HTTPS and which no sibling
subdomain can plant; turning it on logs everyone out once. Responses
then carry Strict-Transport-Security for a year. The traefik example
turns it on.

The password is compared as an HMAC under the secret, so the time a
comparison takes no longer depends on the configured password's length.

Every response also carries Cross-Origin-Opener-Policy and
Cross-Origin-Resource-Policy same-origin and a Permissions-Policy that
turns camera, microphone, location, payment and USB off; API answers
are no-store unless they say otherwise.
This commit is contained in:
nvrl committed 2026-10-20 11:25:16 +02:00
1 parent 6bf88e7ffd
commit f450883ac0
6 files changed
+128 -19

No files matched your search

+14 -5
View File
@@ -582,11 +582,20 @@ sealed or not.
Put a TLS-terminating reverse proxy in front of it, point it at `WORKSPACE_BIND`,
and set `WORKSPACE_SECURE_COOKIE=1`. Nothing else is needed: no database server, no
runtime dependencies, no build tools on the host.
runtime dependencies, no build tools on the host. The server will not start
while `WORKSPACE_SECRET` or `WORKSPACE_PASSWORD` is still the example from
`.env.example`: with the example secret anyone could sign themselves a session.
With `WORKSPACE_SECURE_COOKIE=1` the session cookie is `Secure` and named
`__Host-diary_session`, so the browser keeps it to this host and HTTPS, and every
response carries `Strict-Transport-Security` for a year. Turning it on logs
everyone out once.
The app sends its own `Content-Security-Policy`, `Referrer-Policy: no-referrer`,
`X-Content-Type-Options` and `X-Frame-Options`; a proxy that adds its own should
not weaken them. `no-referrer` matters in particular, because a share token
`X-Content-Type-Options`, `X-Frame-Options`, `Cross-Origin-Opener-Policy`,
`Cross-Origin-Resource-Policy` and a `Permissions-Policy` that turns off camera,
microphone and location; API answers are `Cache-Control: no-store` unless they
say otherwise. A proxy that adds its own should not weaken them. `no-referrer` matters in particular, because a share token
lives in the URL and would otherwise leak to any site a shared entry links to.
Share links minted before the key existed are revoked by migration `0005`;
re-run `:share` on those entries to publish them again.
@@ -630,5 +639,5 @@ volume under a new name and find an empty one.
By default the port is published on `127.0.0.1:4242` only. To serve it through
traefik instead, copy `compose.override.yml.example` to `compose.override.yml`
and adjust the host — it joins the external `narl` network, drops the published
port, and routes to container port 4242. Set `WORKSPACE_SECURE_COOKIE=1` in `.env`
once it is served over HTTPS.
port, routes to container port 4242, and sets `WORKSPACE_SECURE_COOKIE=1`, since
traefik serves it over HTTPS.