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>



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?



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?



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"]


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?



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



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>



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