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.
-
A
Gemfile.lock
file 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.
About Bundler-audit
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 above.
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 example:
# .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 bundle audit
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 bundle audit
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.
Yet 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 use.
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
Just like bundler audit
, 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 addressable
and 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
As 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 dependencies.
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.
Wrapping Up
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 audit
quickly 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 outdated
allows you to flag gems that should be updated. It complementsbundle 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.
Happy coding!
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!
Top comments (0)