Root Cause
rack-session 2.1.2 introduced a new V2 encryptor (AES-256-GCM) that uses Base64.strict_encode64 for cookies — standard base64 which produces +, /, and = characters.
The problem: Rack's parse_cookies_header (in rack/utils.rb) applies URI.decode_www_form_component to every cookie value, which converts + → space (a form-encoding convention that has no place in cookie parsing).
The flow:
- Server creates V2-encrypted session cookie with
+ in its base64 value
- Browser sends it back unchanged (browsers don't re-encode
+)
Rack::Utils.parse_cookies_header decodes + → space, corrupting the value
Base64.strict_decode64 fails or produces garbage → guess_decryptor raises InvalidMessage, 'invalid message'
- Logged as:
Session cookie encryptor error: invalid message
The test above shows 90% of V2 cookies will be affected. V1 cookies are immune because they use Base64.urlsafe_encode64 (- and _ instead of + and /), which URI.decode_www_form_component leaves untouched.
In 2.1.1, the single encryptor always used V1 (URL-safe base64), so this was never an issue.
✅ test
Verify Rack corrupts V2 standard base64 cookies
require 'uri'
require 'base64'
require 'openssl'
puts '=== Does Rack corrupt V2 cookies with + in base64? ==='
puts
# Generate several random payloads to see how often + appears
plus_count = 0
100.times do
sample = OpenSSL::Random.random_bytes(80)
v2_encoded = Base64.strict_encode64(sample)
after_rack = URI.decode_www_form_component(v2_encoded)
plus_count += 1 if after_rack != v2_encoded
end
puts \"Out of 100 V2 cookies, #{plus_count} would be corrupted by Rack's URI unescape (+ -> space)\"
puts
puts '=== V1 (urlsafe_encode64) - safe from Rack unescape? ==='
plus_count_v1 = 0
100.times do
sample = OpenSSL::Random.random_bytes(80)
v1_encoded = Base64.urlsafe_encode64(sample)
after_rack = URI.decode_www_form_component(v1_encoded)
plus_count_v1 += 1 if after_rack != v1_encoded
end
puts \"Out of 100 V1 cookies, #{plus_count_v1} would be corrupted by Rack's URI unescape\"
Root Cause
rack-session 2.1.2 introduced a new V2 encryptor (AES-256-GCM) that uses
Base64.strict_encode64for cookies — standard base64 which produces+,/, and=characters.The problem: Rack's
parse_cookies_header(inrack/utils.rb) appliesURI.decode_www_form_componentto every cookie value, which converts+→ space (a form-encoding convention that has no place in cookie parsing).The flow:
+in its base64 value+)Rack::Utils.parse_cookies_headerdecodes+→ space, corrupting the valueBase64.strict_decode64fails or produces garbage →guess_decryptorraisesInvalidMessage, 'invalid message'Session cookie encryptor error: invalid messageThe test above shows 90% of V2 cookies will be affected. V1 cookies are immune because they use
Base64.urlsafe_encode64(-and_instead of+and/), whichURI.decode_www_form_componentleaves untouched.In 2.1.1, the single encryptor always used V1 (URL-safe base64), so this was never an issue.
✅
testVerify Rack corrupts V2 standard base64 cookies