Module: Kettle::Rb::GemFloors

Defined in:
lib/kettle/rb/gem_floors.rb,
lib/kettle/rb/gem_floors/data.rb,
lib/kettle/rb/gem_floors/generator.rb

Overview

Security floors for tracked gems, one entry per minor series.

For each series the floor is the newest released patch that keeps the series' original minimum Ruby, never below an advisory's patched version. Advisories that still affect the floor (a series with no fixed release) are recorded as unpatched so consumers can decide how to treat it.

The data lives in gem_floors/data.rb and is regenerated from the rubygems.org versions API and ruby-advisory-db by rake kettle:rb:gem_floors:generate (see Generator).

Defined Under Namespace

Classes: Entry, Generator

Constant Summary collapse

ENTRIES =
begin
  entries = {}
  ROWS.each do |row|
    entry = Entry.new(*row)
    entry.patched_advisories.freeze
    entry.unpatched_advisories.freeze
    entry.freeze
    entries[[entry.gem_name, entry.series]] = entry
  end
  entries.freeze
end
GENERATED_ON =
"2026-09-15"
ADVISORY_DB_COMMIT =
"f08b5bf5b277"
TRACKED_GEMS =
["activemodel", "activerecord", "activesupport", "sqlite3"].freeze
ROWS =
[
  ["activemodel", "5.2", "5.2.8.1", ">= 2.2.2", [], []],
  ["activemodel", "6.0", "6.0.6.1", ">= 2.5.0", [], []],
  ["activemodel", "6.1", "6.1.7.10", ">= 2.5.0", [], []],
  ["activemodel", "7.0", "7.0.10", ">= 2.7.0", [], []],
  ["activemodel", "7.1", "7.1.6", ">= 2.7.0", [], []],
  ["activemodel", "7.2", "7.2.3.2", ">= 3.1.0", [], []],
  ["activemodel", "8.0", "8.0.5.1", ">= 3.2.0", [], []],
  ["activemodel", "8.1", "8.1.3.1", ">= 3.2.0", [], []],
  ["activerecord", "5.2", "5.2.8.1", ">= 2.2.2", ["CVE-2021-22880", "CVE-2022-32224", "CVE-2022-44566"], ["CVE-2025-55193"]],
  ["activerecord", "6.0", "6.0.6.1", ">= 2.5.0", ["CVE-2021-22880", "CVE-2022-32224", "CVE-2023-22794"], ["CVE-2022-44566", "CVE-2025-55193"]],
  ["activerecord", "6.1", "6.1.7.10", ">= 2.5.0", ["CVE-2021-22880", "CVE-2022-32224", "CVE-2022-44566", "CVE-2023-22794"], ["CVE-2025-55193"]],
  ["activerecord", "7.0", "7.0.10", ">= 2.7.0", ["CVE-2022-32224", "CVE-2022-44566", "CVE-2023-22794"], ["CVE-2025-55193"]],
  ["activerecord", "7.1", "7.1.6", ">= 2.7.0", ["CVE-2025-55193"], []],
  ["activerecord", "7.2", "7.2.3.2", ">= 3.1.0", ["CVE-2025-55193"], []],
  ["activerecord", "8.0", "8.0.5.1", ">= 3.2.0", ["CVE-2025-55193"], []],
  ["activerecord", "8.1", "8.1.3.1", ">= 3.2.0", [], []],
  ["activesupport", "5.2", "5.2.8.1", ">= 2.2.2", ["CVE-2020-8165", "CVE-2023-22796"], ["CVE-2023-28120", "CVE-2023-38037", "CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"]],
  ["activesupport", "6.0", "6.0.6.1", ">= 2.5.0", ["CVE-2020-8165"], ["CVE-2023-22796", "CVE-2023-28120", "CVE-2023-38037", "CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"]],
  ["activesupport", "6.1", "6.1.7.10", ">= 2.5.0", ["CVE-2023-22796", "CVE-2023-28120", "CVE-2023-38037"], ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"]],
  ["activesupport", "7.0", "7.0.10", ">= 2.7.0", ["CVE-2023-22796", "CVE-2023-28120", "CVE-2023-38037"], ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"]],
  ["activesupport", "7.1", "7.1.6", ">= 2.7.0", [], ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"]],
  ["activesupport", "7.2", "7.2.3.2", ">= 3.1.0", ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"], []],
  ["activesupport", "8.0", "8.0.5.1", ">= 3.2.0", ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"], []],
  ["activesupport", "8.1", "8.1.3.1", ">= 3.2.0", ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"], []],
  ["sqlite3", "1.3", "1.3.13", ">= 1.8.7", [], ["CVE-2026-54619"]],
  ["sqlite3", "1.4", "1.4.4", ">= 1.8.7", [], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "1.5", "1.5.4", ">= 1.9.2", ["GHSA-mgvv-5mxp-xq67"], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "1.6", "1.6.9", ">= 1.9.2", [], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "1.7", "1.7.3", ">= 2.7", [], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.0", "2.0.4", ">= 3.0", [], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.1", "2.1.1", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.2", "2.2.0", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.3", "2.3.1", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.4", "2.4.1", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.5", "2.5.0", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.6", "2.6.0", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.7", "2.7.4", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.8", "2.8.1", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
  ["sqlite3", "2.9", "2.9.6", ">= 3.2", ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"], []]
].freeze

Class Method Summary collapse

Class Method Details

.entries_for(gem_name) ⇒ Array<Entry>

Returns entries for gem_name, oldest series first.

Parameters:

  • gem_name (String)

Returns:

  • (Array<Entry>) —

    entries for gem_name, oldest series first



57
58
59
60
61
# File 'lib/kettle/rb/gem_floors.rb', line 57

def entries_for(gem_name)
  ENTRIES.values
    .select { |entry| entry.gem_name == gem_name.to_s }
    .sort_by { |entry| Gem::Version.new(entry.series) }
end

.entry(gem_name, version) ⇒ Entry?

Parameters:

  • gem_name (String)
  • version (String, Gem::Version) —

    any version within the series, e.g. "7.1" or "7.1.3"

Returns:



50
51
52
53
# File 'lib/kettle/rb/gem_floors.rb', line 50

def entry(gem_name, version)
  series = series_for(version)
  series && ENTRIES[[gem_name.to_s, series]]
end

.floor(gem_name, version) ⇒ String?

Returns the security floor for the series containing version.

Returns:

  • (String, nil) —

    the security floor for the series containing version



64
65
66
67
# File 'lib/kettle/rb/gem_floors.rb', line 64

def floor(gem_name, version)
  found = entry(gem_name, version)
  found && found.floor
end

.requirements(gem_name, version) ⇒ Array<String>

Requirements that pin the series and enforce its floor.

Examples:

Kettle::Rb::GemFloors.requirements("activerecord", "7.1") #=> ["~> 7.1.0", ">= 7.1.6"]

Returns:

  • (Array<String>) —

    empty when the series is not tracked



74
75
76
77
78
79
# File 'lib/kettle/rb/gem_floors.rb', line 74

def requirements(gem_name, version)
  found = entry(gem_name, version)
  return [] unless found

  ["~> #{found.series}.0", ">= #{found.floor}"]
end

.series_for(version) ⇒ String?

Returns the "major.minor" series, or nil when it cannot be determined.

Parameters:

  • version (String, Gem::Version)

Returns:

  • (String, nil) —

    the "major.minor" series, or nil when it cannot be determined



89
90
91
92
93
94
95
96
# File 'lib/kettle/rb/gem_floors.rb', line 89

def series_for(version)
  segments = Gem::Version.new(version.to_s).segments
  return nil if segments.size < 2

  "#{segments[0]}.#{segments[1]}"
rescue ArgumentError
  nil
end

.tracked?(gem_name) ⇒ Boolean

Returns whether floors are tracked for gem_name.

Parameters:

  • gem_name (String)

Returns:

  • (Boolean) —

    whether floors are tracked for gem_name



43
44
45
# File 'lib/kettle/rb/gem_floors.rb', line 43

def tracked?(gem_name)
  TRACKED_GEMS.include?(gem_name.to_s)
end

.unpatched_advisories(gem_name, version) ⇒ Array<String>

Returns advisory identifiers still affecting the series' floor.

Returns:

  • (Array<String>) —

    advisory identifiers still affecting the series' floor



82
83
84
85
# File 'lib/kettle/rb/gem_floors.rb', line 82

def unpatched_advisories(gem_name, version)
  found = entry(gem_name, version)
  found ? found.unpatched_advisories : []
end