Skip to content

Session cookie: not Secure over plain http on a local-network host - #672

Merged
compscidr merged 1 commit into
mainfrom
fix/session-cookie-plain-http
Oct 3, 2026
Merged

compscidr merged 1 commit into
mainfrom
fix/session-cookie-plain-http

Conversation

@compscidr

Copy link
Copy Markdown
Collaborator

Closes #665.

Problem

The session cookie was Secure unless SESSION_SECURE=false. Browsers silently drop a Secure cookie over plain http anywhere but localhost. On a home server opened as http://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:

Request Cookie
SESSION_SECURE=true / false as told, always
TLS, or X-Forwarded-Proto: https Secure
plain http, host is an IP address, a name with no dot, or ends in .local .lan .internal .home.arpa .localhost not Secure
plain http, any other host name Secure (as before)

The last row is deliberate. A request that looks like plain http for blog.example.com is also what a TLS-terminating proxy that forwards no headers looks like (nginx's proxy_pass sends none by default), so loosening it there would quietly drop the flag on existing https sites. Plain http on a public name still needs SESSION_SECURE=false, as the README and the setup-code page now say.

HttpOnly and SameSite=Lax are unchanged in every case. The Host header 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 the Set-Cookie header.
  • The install smoke test now fails if the cookie set at the setup-code step is Secure (it talks plain http to 127.0.0.1). Passes locally on sqlite; go test ./... passes.
  • Not checked in a real browser against a LAN address.

🤖 Generated with Claude Code

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>
Copilot AI balanced review requested due to automatic review settings October 3, 2026 16:12
@compscidr
compscidr merged commit 59bcbbe into main Oct 3, 2026
6 of 7 checks passed
@compscidr
compscidr deleted the fix/session-cookie-plain-http branch October 3, 2026 16:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/localNetworkHost helpers and a sessionCookieSecurity middleware; change sessionOptions to take a bool.
  • Add unit tests (TestCookieSecure, TestSessionCookieSecurity) and an install-smoke-test assertion that the cookie is not Secure over 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Install over plain http on a LAN address: the session cookie does not stick

2 participants