This article covers the use of
bundler features to secure Ruby applications. In this day and age, we have to be
more and more careful about software supply chain security.
We'll show you how to start this journey by
relying on a Gemfile and
bundler to manage your project's dependencies.
By the end of the post, you will better understand how
bundler audit and
bundler outdated work. Both can
help you monitor the security state of your
project's dependency tree.
Let's dive in!
An Introduction to Bundler for Ruby
The history of Bundler is linked to RubyGems. RubyGems, first released in 2004 by Chad Fowler, is a package manager that makes it possible to distribute and manage Ruby libraries, applications, and their dependencies.
Bundler, on the other hand, is a dependency manager. It was first released in 2009 by Carl Lerche.
Bundler helps developers manage dependencies between different Ruby libraries and applications. It uses a Gemfile to define the libraries and versions that a project depends on, then installs and loads those libraries in the correct order. Bundler provides a simple way to manage dependencies and ensure that all the required libraries are installed and loaded correctly.
Supply Chain Security
RubyGems and Bundler can only solve some security issues. For example, in 2013, 2018, and 2020, several security issues emerged directly related to RubyGems. Each time, gems were compromised, potentially exposing thousands of projects and products to malicious actors.
Nowadays, the security of the software supply chain has become more high profile, due to major incidents like 2020's SolarWinds hack, EventStream in 2018, and Equifax, Maersk, Merck, and FedEx in 2017.
Let's see how Bundler can help us avoid some security issues.
Bundler's Security-Related Features for Ruby
Bundler comes with several features that allow us to secure a Ruby project:
- Source validation: ensures gems are installed from a specific, trusted source.
- With a dependency graph, we can see a whole list of dependencies for each gem, helping us to identify potential security risks.
Gemfile.lockfile records the specific versions of each gem used in the project. As it's packaged with the project, it ensures that the same versions of gems are used across different environments.
- Vulnerability detection: Thanks to an integration with the Ruby Advisory Database (RDB), Bundler can detect known security vulnerabilities in gems.
- An audit command lets you list known security vulnerabilities related to gems a project depends on.
- Checksum verification: Bundler verifies the checksum of each gem before installing it.
- Gem signing and signature verification: Gems can be signed cryptographically by a developer.
But, alas, signing gems is complicated and requires a lot of steps. As a consequence, not all gems are signed, thus putting many projects at risk.
Yet, in the meantime, we can rely on two features in particular to ensure that a project doesn't rely on packages that are too old or that have been identified as carrying a security risk.
These features are
bundler audit and
bundler outdated. Let's look at
bundler audit first.
bundle audit is a command-line tool that helps you identify security vulnerabilities in the Ruby libraries that your
project depends on. It is a plugin for the Ruby dependency manager Bundler and is included with Bundler version 1.10 and
bundle audit works by analyzing your Gemfile.lock file, which lists all the dependencies for your project and their
versions. It then compares this information with a database of known vulnerabilities in Ruby gems (maintained by Rubysec).
If a vulnerability is found,
bundle audit will provide you with information about the vulnerability, including its
severity level and which gem versions have been affected. It will also suggest actions you can take to address the
vulnerability, such as upgrading to a patched version of the gem or removing it entirely.
bundle audit is a useful tool for ensuring the security of your Ruby projects, especially if you are using third-party
libraries or dependencies. By regularly running
bundle audit as part of your development workflow, you can stay
up-to-date on any vulnerabilities that may affect your project and take proactive steps to address them.
Usage of Bundler-audit in Ruby
The basic use of Bundler-audit is through the
bundle audit command:
$> bundle-audit Name: actionpack Version: 3.2.10 Advisory: OSVDB-91452 Criticality: Medium URL: http://www.osvdb.org/show/osvdb/91452 Title: XSS vulnerability in sanitize_css in Action Pack Solution: upgrade to ~> 2.3.18, ~> 3.1.12, >= 3.2.13 $>
As you can see, it outputs the name and version of a package used by the project we are testing, which contains a medium-level security issue. It also outputs the URL of the security advisory and a possible solution.
Keeping Bundler-audit's Database Up to Date
As the audit is basically comparing a list of gems used in the project with a list of known security issues in a local copy of the security advisory database, you need to keep that database up to date.
To do so, you just need to run the
bundle audit update command. This will fetch the latest version of the database.
You should only have to run the audit again to check for any new security risks.
Integration In a CI Pipeline
As with other tools aimed at keeping your project safe, it's best to ensure an audit runs automatically on a regular basis (if not after every commit and push to the Git repository).
You can rely on a git post commit hook to run the audit locally if the
Gemfile.lock changes. Here is an
# .git/hooks/post-commit if git diff --name-only HEAD~1 | grep -q Gemfile.lock; then echo "Gemfile.lock has changed, running audit..." bundle audit update && bundle audit fi
This is a bit radical, but it works. You can rely on the same approach within the CI pipeline. As the
command will return a non-zero exit status, the CI pipeline will consider it to have failed. Unfortunately, we
don't have a proper audit report out of the box. Instead, you have to direct the output of the
command towards a file and save it as an artifact of the build for the CI pipeline.
Here is how to direct the command's output to a file:
$> bundle audit update && bundle audit > /tmp/bundle-audit-report.txt
As each CI service handles artifacts differently, check the relevant documentation to see how to do this.
Bundler-audit in Summary
bundle audit is not a complete solution, yet it still lets your team know if your project relies on
unsafe gems (without costing you an arm and a leg). Your team should put tools such as git hooks or
CI tasks in place, and relevant integrations to make any issues visible.
All things considered, I'd say it's not too much trouble. It probably requires a few hours to get started and have the basic information spit out as a comment in your favorite git hosting solution, or as a notice in a Slack channel.
bundle audit is only one part of what you can do. It only tells you if there is a security issue related to a gem
relied on by a project. It does not tell you if a gem is outdated. To handle that,
bundler outdated is the command to
Bundler Outdated for Ruby Gems
Auditing for security risks is very important, yet listing outdated gems is also a big issue. The longer you wait to update a dependency, the higher the risk that your code breaks.
You should aim to work in small iterations, deployed frequently, to limit the amount of change contained in each deployment. Equally, you should aim to update any gem you use as soon as it's updated, or as close to that as possible.
Usage of Bundler Outdated
bundler outdated is very simple to use. Just call it at the root of your Ruby project. It
will compare the
Gemfile.lock content to the current gem releases listed in it.
$> bundle outdated Fetching gem metadata from https://rubygems.org/......... Resolving dependencies...... Gem Current Latest Requested Groups addressable 2.8.1 2.8.4 capybara 3.38.0 3.39.0 >= 0 test devise 4.9.0 4.9.2 >= 4.6.0 default $>
As you can see, three gems are outdated in this project. Let's see how we should read this table:
- The first column contains the name of the gem.
- The second column shows the currently installed version.
- The third column shows the latest version available.
- The fourth column shows the request version (from the Gemfile).
- The fifth column shows the group the gem is part of (in the Gemfile).
Here, we can see we don't have much to worry about for
devise, as the difference in releases is
within the patch versions (third part of the release number). The version of
capybara is only behind one minor release, which
implies a few more changes, but this rarely means breaking changes.
Still, the logical thing to do here is to update all three as soon as possible.
Usage In a CI Pipeline
bundle outdated will return a non-zero exit status for outdated gems, a CI task it's based on is
considered failed. So you can have a simple and direct flag in your CI pipeline in case of outdated
Yet you might want to rely on a pair of flags to improve your use of
bundle outdated. The command can filter its
output to only list gems that have pending patch-level updates:
$> bundle outdated --filter-patch ... Gem Current Latest Requested Groups addressable 2.8.1 2.8.4 devise 4.9.0 4.9.2 >= 4.6.0 default
In the same manner, you can list only gems with pending minor version updates:
$> bundle outdated --filter-minor Gem Current Latest Requested Groups capybara 3.38.0 3.39.0 >= 0 test
And in the same way, you can list only gems with pending major release updates:
$> bundle outdated --filter-major Gem Current Latest Requested Groups sidekiq-scheduler 4.0.3 5.0.1 >= 0 default
Those three flags let you easily tailor a CI task to only warn you that a patch-level update is available but not block the build (or the reverse), for example.
A complimentary option allows you to only list outdated gems within the default group:
$> bundle outdated --group default ... Gem Current Latest Requested Groups devise 4.9.0 4.9.2 >= 4.6.0 default faker 3.1.1 3.2.0 >= 0 default
As gems within test and development groups don't impact the production release, they could be considered less of a priority to keep up to date, for example. Thus you can use the flag only to raise a warning or block the CI pipeline if gems within the default group are outdated.
Maintaining the security of your Ruby application is not just the result of one action. Rather, it's a cumulative effect of several actions put together. As we've covered in this post, Bundler provides us with two commands that can help you to improve your application's security:
bundle auditquickly lets you know if any gem you add or use in your project could pose a security threat and what to do about it.
bundle outdatedallows you to flag gems that should be updated. It complements
bundle audit, and if you keep on top of it, you avoid having an update bundle that's too large.
These two commands are easy to use and integrate with local feedback loops and CI pipelines.
P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!