Skip to content

fix: scope session cookie name per instance to prevent CSRF 422s#364

Merged
MatthewSuttles merged 1 commit into
mainfrom
fix/per-instance-session-cookie
Jun 23, 2026
Merged

fix: scope session cookie name per instance to prevent CSRF 422s#364
MatthewSuttles merged 1 commit into
mainfrom
fix/per-instance-session-cookie

Conversation

@MatthewSuttles

Copy link
Copy Markdown
Collaborator

Problem

A second Hivemind instance created via hivemind new (#363) cannot sign in. Devise returns HTTP 422 "Can't verify CSRF token authenticity" on POST /users/sign_in.

Root cause

It's a session-cookie collision, not a real CSRF attack:

  1. Same cookie name across instances. There was no config/initializers/session_store.rb, so Rails derived the default session key from the app module (module Hivemind) → _hivemind_session. Both the primary and hivemind new instances used that exact name.
  2. Different SECRET_KEY_BASE per instance. bin/hivemind correctly generates fresh secrets for each new instance, so the two instances sign/encrypt their session cookies with different keys.
  3. Browser cookies are scoped by host, not port. localhost:8080 (primary) and localhost:8081 (new instance) share one cookie jar, so both write a cookie literally named _hivemind_session and overwrite each other.

Result: loading one instance stomps the other's session cookie. On POST, the form's authenticity token no longer matches the (now-foreign, undecryptable) cookie → 422. This is why it's always the most recently loaded instance that fails.

Fix

Add config/initializers/session_store.rb that keys the session cookie on COMPOSE_PROJECT_NAME, which is already unique per instance (hivemind vs hivemind-<name>) and passed into the Rails container via env_file: .env:

Rails.application.config.session_store :cookie_store,
  key: "_#{ENV.fetch('COMPOSE_PROJECT_NAME', 'hivemind')}_session"

The primary keeps _hivemind_session via the default fallback, so existing sessions are not invalidated. New instances get _hivemind-<name>_session, isolating cookies even on a shared localhost.

Testing

  • Confirmed no code hardcodes the old _hivemind_session cookie name (Action Cable, mobile views, etc. all read from the session abstraction).
  • Manual: sign into both the primary and a hivemind new instance in the same browser — both now hold independent sessions.

🤖 Generated with Claude Code

Multiple Hivemind instances on one host (via `hivemind new`) run on
different localhost ports. Browsers share cookies across ports for the
same host, and every instance used the default session cookie name
`_hivemind_session`. Combined with each instance having its own
SECRET_KEY_BASE, the instances overwrote each other's session cookie,
so the authenticity token in a rendered form no longer matched the
cookie on POST — Devise sign-in failed with "Can't verify CSRF token
authenticity" (HTTP 422).

Key the session cookie on COMPOSE_PROJECT_NAME (unique per instance,
already passed into the container via env_file). The primary keeps
`_hivemind_session` via the default fallback, so existing sessions are
not invalidated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MatthewSuttles MatthewSuttles merged commit 598ba52 into main Jun 23, 2026
2 of 3 checks passed
@MatthewSuttles MatthewSuttles deleted the fix/per-instance-session-cookie branch June 23, 2026 15:57
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.

1 participant