Bundler Source Order (Yes, It's Important)23 Nov 2015
If you ever use gems from your own private repository, then there is something important that you should know about Bundler. To explain what that is, let me introduce Tadas. He’s the founder of a startup called Fishoo, that sells fish-shaped iPhone cases (a niche but lucrative market):
Here is the
Gemfile of the main Fishoo application:
The first two lines are telling Bundler to install gems from two different places. Some gems, such as
redis, are on the public RubyGems repository. Others, such as
fishoo-payments, are private gems on Fishoo’s internal repository. Bundler will check both sources when installing gems.
And here is the question: in which order does Bundler check the sources? The order is actually important, as we’ll find out when a new, shady character enters our story: Sadat, Tadas’s evil twin.
Sadat has been working on Fishoo in the past, and he still has a clone of the
fishoo-payments source code on his computer. So he comes up with an evil plan: he modifies his copy of
fishoo-payments to divert all payments from the site to his own Bitcoin account. Then he deploys
fishoo-payments as a public gem on RubyGems.
That night, Tadas (the Good twin) sets up a new Fishoo server on Amazon EC2, and uses Bundler to install all the required gems - but Bundler finds a gem named
fishoo-payments on the public RubyGems server, and installs that tampered gem instead of the gem from Fishoo’s server. Sadat (the Evil twin) can now sit back and sip his daiquiri as he watches money flow from Fishoo to his untraceable Bitcoin account!
If you also have a private gem server in your company, then the order of Bundler sources can be a real security threat. Here are three ways that you can solve the problem.
Use the safer, block-based form of Bundler’s
source method. For example, you can write this:
Gemfile above tells Bundler that RubyGems is the default source for gems, but a few gems are special, and they should be downloaded from a different source.
Whenever you can, it’s a good idea to use the technique above. Unfortunately, sometimes you can’t. One problem we had at DaWanda is that some of our projects are themselves gems, and their dependencies are listed in the
gemspec file - which doesn’t support multiple sources at all (for reasons that depend on the way RubyGems works).
A second way to solve the problem is to sign your gems. This is extra work, but probably worth it if you have a serious project.
A third, cheap way to solve the problem is to know the order in which Bundler loads gems from sources. That order is, perhaps surprisingly, bottom-to-top (at least since Bundler 1.7). Respect this order, and you will always know which source each gem is loaded from. For example, Tadas can force Bundler to check the Fishoo gem server before RubyGems by simply switching the first two lines in the
If you have multiple sources in your
Gemfile, you’re likely following this order already: first, you specify the public gem server - then, any private ones. If you’re doing this consistently, then you’re safe.
Today, my team spent some time to retrieve this information. In the end, we had to dig into Bundler’s tests. We hope that this blog post will make it easier for you to spoil Sadat’s despicable plots. Oh, and if you want to know more about the priority of gem sources, take a minute to read this as well.