Your Security Scanner Is Reading Gemspecs Wrong


How dependency declaration syntax in Ruby gemspecs produces false positive vulnerability reports — and what to check before you panic. (Also check out my Ruby Gems Analyzer that covers the CVEs and Bundled/Default Gems in each Ruby version, as compiled directly from pristine installs of Ruby on clean images)


If you run a Ruby application in production, you’ve probably received a vulnerability report that looks something like this:

Vulnerability Reported

OS command injection vulnerability in Ruby Rake before 12.3.3 in Rake::FileList when supplying a filename that begins with the pipe character |. Successful exploitation could affect integrity, availability, and confidentiality.

The report lists multiple “installed versions” of Rake — 12.0, 11.0.0, 10.0.0, 10.0, 12.3.2 — all below the required 12.3.3 threshold, all flagged as vulnerable. It looks alarming. Multiple vulnerable installs, scattered across several gem paths.

Except none of those are actually installed versions of Rake.

What the scanner actually found

Those “version numbers” are fragments of dependency declarations inside .gemspec files from other gems. When a Ruby gem is packaged and distributed, its gemspec file travels with it. That gemspec contains metadata including the gem’s dependencies — the other gems it needs and what version ranges are acceptable.

A typical gemspec dependency line looks like this:

spec.add_development_dependency "rake", "~> 12.0"

The security scanner is extracting the string 12.0 from this declaration and interpreting it as a literal installed version. It’s not. It’s the lower bound of a version range constraint.

How the pessimistic operator works

The ~> operator in RubyGems — sometimes called the “pessimistic version constraint” or “twiddle-wakka” — means “this version or higher, but only within the same major/minor segment.” The last digit is allowed to float upward:

ConstraintMeaningAllows 12.3.3?
~> 12.0≥ 12.0, < 13.0Yes
~> 10.0≥ 10.0, < 11.0No
~> 13.0.0≥ 13.0.0, < 13.1.0Yes (13.x)

The scanner strips the ~> operator and treats the remaining number as a concrete installation. That’s the root of the false positive.

Development dependencies don’t run in production

There’s a second, equally important issue the scanner misses: dependency type.

In Ruby’s gem ecosystem, dependencies come in two flavors. Runtime dependencies are gems required when your application runs. Development dependencies are gems needed only when building, testing, or developing the library itself. Think test frameworks, linters, documentation generators — and yes, Rake as a build tool.

Unless you are actively developing on a third-party gem (modifying net-ssh or http-accept at the source level), these development dependencies are inert metadata. They’re never resolved, never installed, never executed.

Here’s what the scanner flagged versus what’s actually going on:

GemVersionRake specAllows ≥12.3.3?Dep. type
net-ssh7.3.0~> 12.0Yesdev
http-accept2.1.1~> 10.0Nodev
rspec-its1.3.1~> 13.0.0Yesdev

Every single flagged entry is a development dependency. None of them affect what version of Rake actually runs on your system.

What’s actually installed

The version of Rake that matters is the one Bundler resolves and installs — the actual gem sitting in your GEM_HOME, the one bundle exec rake --version reports.

Actual installed version

On a pristine install, the resolved Rake version is 13.0.6 — well above the 12.3.3 threshold. The vulnerability does not apply.

Bundler’s dependency resolver takes all the constraints from your Gemfile and Gemfile.lock, resolves a single coherent set of gem versions, and installs exactly those. The stale version ranges in third-party gemspecs are irrelevant to the final resolution — especially when they’re development dependencies that Bundler ignores entirely in a production context.

Three things your scanner got wrong

  1. Treating version constraints as installed versions. The numeric part of ~> 12.0 is not an installed version. It’s the lower bound of an acceptable range. The scanner needs to parse the operator, not discard it.
  2. Ignoring dependency type. Development dependencies in third-party gemspecs are metadata artifacts. They don’t participate in runtime dependency resolution for your application.
  3. Not checking the lockfile. The single source of truth for what’s installed is the Bundler lockfile (Gemfile.lock) and the actual gem directory. Scanning gemspec declarations inside vendored gems produces noise, not signal. Note that a Gemfile bundled with a gem can be equally misleading — it contains dependency declarations with version constraints, not resolved versions. Only the Gemfile.lock reflects what Bundler actually installed.

· · ·

What to do when you get this report

Before escalating, verify the actual installed version:

$ bundle exec rake --version
rake, version 13.0.6

Or check the lockfile directly:

$ grep "rake " Gemfile.lock
    rake (13.0.6)

Finding what’s actually on disk

If you want to go deeper — or you don’t have a Bundler context handy — you can inspect the filesystem directly. First, look for the actual installed gem (the directory that contains the real library code), then separately look for gemspec files that merely reference the gem as a dependency.

On Linux / macOS (Bash):

# Set this to the root of your Ruby installation
RUBY_ROOT="/opt/ruby"

# Find the actual installed rake gem (the real thing)
find "$RUBY_ROOT" -type d -name "rake-*" \
  -path "*/gems/rake-*" 2>/dev/null

# Find gemspec files that *mention* rake as a dependency
# (these are what the scanner is misreading)
find "$RUBY_ROOT" -name "*.gemspec" \
  -exec grep -l '"rake"' {} \; 2>/dev/null

On Windows (PowerShell):

# Set this to the root of your Ruby installation
$RubyRoot = "C:\ruby"

# Find the actual installed rake gem
Get-ChildItem -Path $RubyRoot -Recurse -Directory `
  -Filter "rake-*" -ErrorAction SilentlyContinue |
  Where-Object { $_.FullName -match '\\gems\\rake-' }

# Find gemspec files that mention rake as a dependency
Get-ChildItem -Path $RubyRoot -Recurse `
  -Filter "*.gemspec" -ErrorAction SilentlyContinue |
  Select-String -Pattern '"rake"' -List |
  Select-Object -ExpandProperty Path

The distinction is critical. The first command finds directories like gems/rake-13.0.6/ — that’s your actual installed version. The second finds .gemspec files inside other gems that declare rake as a dependency. The scanner is treating the second set as if they were the first.

If the resolved version is at or above the patched version, file it as a false positive and move on. If your scanning tool doesn’t distinguish between gemspec dependency declarations and actual installations, that’s a tooling limitation worth raising with the vendor — because this class of false positive will recur for every gem that ships with a gemspec containing flexible development dependency ranges.

And they almost all do.

Further reading


This pattern isn’t unique to Rake. Any Ruby gem vulnerability can trigger the same false positive when security scanners parse gemspec dependency lines without understanding the ~> operator or the distinction between runtime and development dependencies. The fix is better tooling — or at minimum, knowing where to look before you sound the alarm.


Leave a Reply