“...I've been working since 2008 with Ruby / Ruby on Rails, love a bit of Elixir / Phoenix and learning Rust. I also poke through other people's code and make PRs for OpenSource Ruby projects that sometimes make it. Currently working for InPay...”

Rob Lacey (contact@robl.me)
Senior Software Engineer, Brighton, UK

Mongoid OR queries are seriously dangerous

Upgrading to Mongoid 7.x and found a pretty frightening change in behaviour for OR queries. Any unclear OR declarations could have devastating side-effects.

5.4.1

irb(main):001:0> Train.where(id: 1).or(:expiry.gte => Date.today)
=> #<Mongoid::Criteria
  selector: {"_id"=>1, "$or"=>[{"expiry"=>{"$gte"=>2021-07-07 00:00:00 UTC}}]}
  options:  {:sort=>{"number"=>1}}
  class:    Train
  embedded: false>

irb(main):002:0> Train.or(:expiry.gte => Date.today).where(id: 1)
=> #<Mongoid::Criteria
  selector: {"$or"=>[{"expiry"=>{"$gte"=>2021-07-07 00:00:00 UTC}}], "_id"=>1}
  options:  {:sort=>{"number"=>1}}
  class:    Tenant
  embedded: false>

Previously, or queries would append the selector so where(id: 1) and some OR statement

7.3.0

irb(main):006:0> Train.where(id: 1).or(:expiry.gte => Date.today)
=> #<Mongoid::Criteria
  selector: {"$or"=>[{"_id"=>1}, {"expiry"=>{"$gte"=>2021-07-07 00:00:00 UTC}}]}
  options:  {:sort=>{"number"=>1}}
  class:    Train
  embedded: false>

irb(main):007:0> Train.or(:expiry.gte => Date.today).where(id: 1)
=> #<Mongoid::Criteria
  selector: {"$or"=>[{"expiry"=>{"$gte"=>2021-07-07 00:00:00 UTC}}], "_id"=>1}
  options:  {:sort=>{"number"=>1}}
  class:    Train
  embedded: false>

Now it combines them depending on the order they are declared. I am not entirely sure I like either way of declaring this. Having come from ActiveRecord land I would nearly always assume that you’re AND-ing everything you chain onto a scope.

So in this case we could look for our Tenant by ID, but actually find the first unexpired one instead. That’s one hell of a security hole.

I think the lesson here is to be extra cautious when using ‘OR’ in either ActiveRecord or Mongoid since the outcome is not always as obvious as you might think.