Repository navigation
Session cookie: not Secure over plain http on a local-network host - #672
Conversation
The session cookie was always Secure unless SESSION_SECURE=false, and a browser silently drops a Secure cookie over plain http anywhere but localhost. On a home server reached as http://192.168.1.20:7000 nobody could get past the setup code or log in, with nothing saying why. The attribute is now decided per request. SESSION_SECURE=true or false is final. Otherwise the cookie stays Secure except for a plain-http request whose host can only be on a local network: an IP address, a bare name, or a private-use suffix. A public host name stays Secure even over what looks like plain http, since that is also what a TLS-terminating proxy that forwards no headers looks like. Closes #665. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It changes security-sensitive session-cookie Secure handling for authentication, and the author notes it was not verified in a real browser against a LAN address, so human review is warranted.
Review effort: Balanced
Findings: None
What changed in this PR
This PR fixes issue #665, where installing GoBlog over plain HTTP on a LAN address (e.g. http://192.168.1.20:7000) left the session cookie marked Secure, which browsers silently drop over non-localhost HTTP — so the setup code / login never "stuck". Instead of a static SESSION_SECURE env check, the Secure attribute is now decided per request by a new sessionCookieSecurity middleware that runs after sessions.Sessions. SESSION_SECURE=true|false still forces the flag; otherwise the cookie is Secure unless the request arrived over plain HTTP for a host that can only be local (an IP, a dotless name, or a .local/.lan/.internal/.home.arpa/.localhost suffix). Public host names stay Secure to avoid silently downgrading HTTPS sites behind header-less TLS-terminating proxies.
Changes:
- Add
cookieSecure/localNetworkHosthelpers and asessionCookieSecuritymiddleware; changesessionOptionsto take abool. - Add unit tests (
TestCookieSecure,TestSessionCookieSecurity) and an install-smoke-test assertion that the cookie is notSecureover plain HTTP to an IP. - Update README and the wizard setup-code page to describe the new local-network exception.
| File | Description |
|---|---|
| goblog.go | Adds per-request Secure decision (cookieSecure, localNetworkHost, sessionCookieSecurity); sessionOptions now takes a bool; registers the middleware after sessions.Sessions. |
| csrf_test.go | Updates TestSessionOptions for the new signature and adds table-driven + end-to-end tests for the Secure decision. |
| scripts/install-smoke-test.sh | Updates the cookie comment and asserts the session cookie is not Secure over plain HTTP to 127.0.0.1. |
| README.md | Documents the local-network exception and when SESSION_SECURE=false is still required. |
| themes/default/templates/wizard_unlock.html | Updates the setup-code hint to reflect that only public host names mark the cookie Secure. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Closes #665.
Problem
The session cookie was
SecureunlessSESSION_SECURE=false. Browsers silently drop a Secure cookie over plain http anywhere butlocalhost. On a home server opened ashttp://192.168.1.20:7000, the setup code was accepted and the same page came back, and before the setup code existed every login failed the same way.Change
The attribute is decided per request by a small middleware after
sessions.Sessions:SESSION_SECURE=true/falseX-Forwarded-Proto: https.local.lan.internal.home.arpa.localhostThe last row is deliberate. A request that looks like plain http for
blog.example.comis also what a TLS-terminating proxy that forwards no headers looks like (nginx'sproxy_passsends none by default), so loosening it there would quietly drop the flag on existing https sites. Plain http on a public name still needsSESSION_SECURE=false, as the README and the setup-code page now say.HttpOnlyandSameSite=Laxare unchanged in every case. TheHostheader is the client's to set, but a client can only change the attributes of its own cookie.Testing
TestCookieSecure: thirteen cases across the table above.TestSessionCookieSecurity: the decision reaches theSet-Cookieheader.127.0.0.1). Passes locally on sqlite;go test ./...passes.🤖 Generated with Claude Code