@@ -365,6 +365,31 @@ def decode(str); @calls << :decode; JSON.parse(str); end
365365 response . body . must_equal ( { "counter" => 1 } . to_s )
366366 end
367367
368+ it 'rejects a forged plain Base64::Marshal cookie when secrets: is configured' do
369+ # Construct a forged cookie without knowing the secret: plain Base64-encoded
370+ # Marshal payload, identical to what an attacker would send.
371+ forged_payload = { 'session_id' => 'attacker-fixed' , 'counter' => 999 }
372+ forged_cookie = "rack.session=#{ Base64 . strict_encode64 ( Marshal . dump ( forged_payload ) ) } "
373+
374+ app = [ incrementor , { secrets : @secret } ]
375+
376+ # The forged cookie must be rejected; session starts fresh (counter = 1).
377+ response = response_for ( app : app , cookie : forged_cookie )
378+ response . body . must_equal ( { "counter" => 1 } . to_s )
379+ end
380+
381+ it 'rejects a forged plain Base64::Marshal cookie when secrets: and serialize_json: true are configured' do
382+ # serialize_json: true only affects the encryptor serializer; the coder
383+ # fallback must also be suppressed when encryptors are configured.
384+ forged_payload = { 'session_id' => 'attacker-fixed' , 'counter' => 999 }
385+ forged_cookie = "rack.session=#{ Base64 . strict_encode64 ( Marshal . dump ( forged_payload ) ) } "
386+
387+ app = [ incrementor , { secrets : @secret , serialize_json : true } ]
388+
389+ response = response_for ( app : app , cookie : forged_cookie )
390+ response . body . must_equal ( { "counter" => 1 } . to_s )
391+ end
392+
368393 it 'rejects session cookie with different purpose' do
369394 app = [ incrementor , { secrets : @secrets } ]
370395 other_app = [ incrementor , { secrets : @secrets , key : 'other' } ]
0 commit comments