Repository navigation
feat(sinatra): cookie session store (enable :sessions / session helper) - #169
Merged
Merged
Conversation
Implement the Sinatra session seam PR #157 deferred: `enable :sessions` plus a working `session` helper backed by a signed cookie, matching MRI Sinatra's Rack::Session::Cookie semantics. On dispatch the session is loaded from the request's "rack.session" cookie (base64(Marshal.dump)--HMAC-SHA1(session_secret)); a missing, malformed or tampered cookie yields a fresh empty session. The `session` helper returns the live Hash so session[:k]=v and session.clear persist, and after the handler runs the (possibly mutated) session is committed back into a signed, HttpOnly, path=/ Set-Cookie. `set :session_secret, "…"` sets the key; without one a per-VM random default is used (like MRI's warning path). Serialization reuses the bound go-ruby-marshal engine, so the payload is byte-identical to MRI's default Base64::Marshal coder; base64 + HMAC-SHA1 match Rack's, making the cookie byte-exact to Rack::Session::Cookie given the same secret (proven by TestSinatraSessionCookieByteExact). The through-binary proof is the functional round-trip + tamper rejection (webapp stage 5 and the internal round-trip/tamper/clear tests), which an MRI Sinatra app reproduces observably. 100% coverage on the new binding code; go build/vet/test + conformance green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… map Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the Sinatra session-store seam PR #157 deferred (templating landed via #162/#166):
enable :sessions+ a workingsessionhelper persisted across requests via a signed cookie, matching MRI Sinatra'sRack::Session::Cookie.Mechanism (load / save / sign)
sinatra_session.go): parse the request'srack.sessioncookie (Rack::Utils.parse_cookiesvia go-ruby-rack), split"<data>--<digest>", verify HMAC-SHA1 overdatawithsession_secret(constant-timehmac.Equal), thenbase64.StdDecode→Marshal.load→ RubyHash. A missing / malformed / tampered cookie (bad HMAC) yields a fresh empty session.sessionhelper: returns the live mutableHash, sosession[:k]=v,session[:k]||=…andsession.clearpersist for the request. Falls back to the oldrack.sessionenv seam when sessions are disabled (backward compatible).Set-Cookie(Rack commits every response).set :session_secret, "…"sets the key; without one a per-VM random default is generated once (like MRI's warning path).Serialization reuses the already-bound go-ruby-marshal engine (
toMarshalValue/fromMarshalValue), and base64/HMAC use Go stdlib.Byte-exact vs functional
session_secret: the payloadbase64(Marshal.dump(session))and theHMAC-SHA1hex digest are byte-identical toRack::Session::Cookie's defaultBase64::Marshalcoder + SHA1. Proven byTestSinatraSessionCookieByteExact: for{count: 1}/ secret"secret"the emitted cookie is exactlyrack.session=BAh7BjoKY291bnRpBg%3D%3D--41a93a4ae53bfb87c6064bd9a5f48f28e70c844f; path=/; httponly(constants computed fromMarshal.dump({count:1})andOpenSSL::HMAC.hexdigest).Rack::Protectionstack would inject (session_id/csrf) — out of scope.Through-binary proof
scripts/conformance/webapp/apps/stage5_sinatra_session.rbdrives its own request sequence through the rbgo#calladapter, threading eachSet-Cookie→Cookie:/incincrementssession[:count](1→2),/readreads it on a different route (persists), a tampered cookie resets the counter to 1, andsession.clearempties it.TestSinatraSessionStoreasserts the full transcript (an MRI-portable functional golden). InternalTestSinatraSession{RoundTrip,TamperRejected,Clear}cover the same through the DSL.Verification
GOWORK=off go build/vet/testgreen; conformance webapp suite green (stages 1–5).sinatra_session.gofuncs,sinatraCall/buildSinatraApp/sinatraMergedSettings), including thecrypto/randfailure fallback (via asinatraRandReadseam) and every decoder failure arm.Deferred surface
Session expiry (
expire_after/ max-age), alternate stores (Rack::Session::Pool/memcache/redis), andsession_id/CSRF injection are follow-ups; this ships the default cookie store.🤖 Generated with Claude Code