From 6126b5055b7b82770c500c52b0a9669240a44ffe Mon Sep 17 00:00:00 2001 From: Roman Priesol Date: Wed, 16 Sep 2026 16:11:01 +0200 Subject: [PATCH] Disable Rack::Protection::SessionHijacking, it silently logs everyone out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sinatra turns this on by default once sessions are enabled: it pins a session to the User-Agent header seen on whichever request first wrote to it, and empties the session — no exception, nothing logged — on any later request where that header doesn't match. That's exactly what production hit behind its reverse proxy: OAuth login set session[:user] fine, then the very next page load (even a plain refresh) came back logged out with nothing in the logs to explain why. Its own docs concede every header it checks is spoofable anyway ("will not prevent determined hijacking attempts"), so losing it is a minor tradeoff for a login that actually stays logged in. Every other Rack::Protection check stays on. --- app.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app.rb b/app.rb index f087b58..0845779 100644 --- a/app.rb +++ b/app.rb @@ -34,6 +34,21 @@ module Maptool set :sessions, httponly: true, secure: production?, same_site: :lax, secret: ENV.fetch("SESSION_SECRET") { SecureRandom.hex(32) } + # Sinatra enables Rack::Protection::SessionHijacking by default once + # sessions are on: it fingerprints a session to the User-Agent header + # seen on the request that first wrote to it, and *silently* empties + # the session (no exception, nothing logged) on any later request + # where that header doesn't match — which is exactly what a login + # behind production's reverse proxy hit: the OAuth callback set + # session[:user] fine, but the very next ordinary page load (or even a + # plain refresh) came back logged out with nothing in the logs to + # explain it. Its own docs already admit every header it can check is + # spoofable anyway ("will not prevent determined hijacking attempts") + # — losing it is a minor tradeoff for a login that actually stays + # logged in. Every other Rack::Protection check (XSS header, path + # traversal, JSON CSRF, frame options, ...) stays on. + set :protection, except: [:session_hijacking] + # Cache-busts /js and /css requests: browsers (and a caching proxy in # front of production, see docs/deployment.adoc) hang onto static # assets for hours, so without this a deploy that changes app.js/css