Read more

How to fix: Session hash does not get updated when using "merge!"

Arne Hartherz
March 07, 2013Software engineer at makandra GmbH

tl;dr: Do not use merge! for session hashes. Use update instead.

Outline

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

Let's assume you're modifying the Rails session. For simplicity, let's also assume your session is empty when you start (same effect when there is data):

# In our example, we're in a Rack middleware
request = Rack::Request.new(env)
request.session.merge! :hello => 'Universe'
request.session
=> {}

Wat? Show archive.org snapshot

Even worse: When you inspect your request.session like above (e.g. in a debugger shell, or just in your code) any subsequent request.session.merge! will work! Don't stop reading yet, this is not the fix. ;)

What's going on

The culprit is the ActionController::Session::AbstractStore::SessionHash class (session is of that type in your Rails application, but also in Rack middleware):

class SessionHash < Hash
  
  # ...

  def update(hash)
    load_for_write!
    super
  end

As you can see: While merge! Show archive.org snapshot is the same as update Show archive.org snapshot on a regular Hash (they are not aliased), update has been changed on SessionHash.

SessionHash#load_for_write! calls load! internally which prepares some data in the session hash. The same happens when you say inspect (which uses load_for_read!) which is why it might seem to be working while looking at the issue Show archive.org snapshot .

I'm not sure why SessionHash#merge! is not implemented in a similar fashion, and I don't dare monkey-patching it. Be aware just not to use it -- SessionHash#update is what you are looking for.

Note that this happened for me in a Rails 2 application's Rack middleware. While using #merge! might work on regular Rails applications, it is only because the hash was loaded before; but you don't really have a guarantee that this happened.

On Rails 4 applications the issue is similar. Assigning Symbol keys via merge! will not work, while String work. To avoid that, use update on Rails 4, too.

Posted by Arne Hartherz to makandra dev (2013-03-07 15:42)