kettle-rb 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 929a9a89e13805ede75597d92a832af5dc36d339a792dd672a62c84e04a7ed56
4
- data.tar.gz: e1e0a006146f7c6db4bc52fbd3f80bb2f73369a987daebe7a234a0626973cc88
3
+ metadata.gz: f07aaac5073eca17b157f5f95f6bdedf6a634b840f426c9c82530b12e3ab4184
4
+ data.tar.gz: ecf84249deec006ad63e7ab04d488f51fdb2e7eaf5088e5684571c7b6e0e5cab
5
5
  SHA512:
6
- metadata.gz: d9a3de2698e45a90dff50e41e398e28d89da0c6be23633f70d6aa55d3fb16c18a780248de669b4e380841a11748e6317f20c31229dd746e85d25b064a30ca839
7
- data.tar.gz: 04ae9fbd5782ab533db5d40d46023ddadd0a6b962232da953b21c1f3ff8d332eacf5e531e814872e72837f45a2330ee3e97466bb5ecd234d398b752e5b461350
6
+ metadata.gz: 31ce05600e747f9427b86734813976e1f4f3831ffc6d9314f2edfb70fc1c8b233c59fccc33d3bf2144d4a2fd77d43e516e618d7a0b0dcacdf6731eb49db46653
7
+ data.tar.gz: e5568624e5ebb0fa4c5becc6f77254834e6f43d7d5db892e40a7b8f3e596445825d20445b4131636f29f60607b7fd426270fc66d433f9a95369e745a1882b467
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,25 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [0.1.14] - 2026-09-14
34
+
35
+ - TAG: [v0.1.14][0.1.14t]
36
+ - COVERAGE: 100.00% -- 233/233 lines in 7 files
37
+ - BRANCH COVERAGE: 97.37% -- 37/38 branches in 7 files
38
+ - 60.98% documented
39
+
40
+ ### Added
41
+
42
+ - `Kettle::Rb::GemFloors`: security floors for tracked gems (activemodel,
43
+ activerecord, activesupport, sqlite3), one entry per minor series. Each floor
44
+ is the newest patch that keeps the series' original minimum Ruby, never below
45
+ an advisory's patched version; advisories left unfixed in a series are
46
+ recorded as unpatched.
47
+ - `rake kettle:rb:gem_floors:generate` regenerates the floor data from the
48
+ rubygems.org versions API and ruby-advisory-db (via bundler-audit).
49
+ - `Kettle::Rb::GemDependencies`: curated runtime requirements between tracked
50
+ gems, starting with the sqlite3 requirement ActiveRecord declares per series.
51
+
33
52
  ## [0.1.13] - 2026-09-14
34
53
 
35
54
  - TAG: [v0.1.13][0.1.13t]
@@ -299,7 +318,9 @@ Please file a bug if you notice a violation of semantic versioning.
299
318
  - Add `Kettle::Rb::CompatMatrix` as the shared Ruby, engine, Rails, RuboCop,
300
319
  and RuboCop LTS compatibility source of truth for kettle-dev tooling.
301
320
 
302
- [Unreleased]: https://github.com/kettle-dev/kettle-rb/compare/v0.1.13...HEAD
321
+ [Unreleased]: https://github.com/kettle-dev/kettle-rb/compare/v0.1.14...HEAD
322
+ [0.1.14]: https://github.com/kettle-dev/kettle-rb/compare/v0.1.13...v0.1.14
323
+ [0.1.14t]: https://github.com/kettle-dev/kettle-rb/releases/tag/v0.1.14
303
324
  [0.1.13]: https://github.com/kettle-dev/kettle-rb/compare/v0.1.12...v0.1.13
304
325
  [0.1.13t]: https://github.com/kettle-dev/kettle-rb/releases/tag/v0.1.13
305
326
  [0.1.12]: https://github.com/kettle-dev/kettle-rb/compare/v0.1.11...v0.1.12
data/README.md CHANGED
@@ -551,7 +551,7 @@ Thanks for RTFM. ☺️
551
551
  [📌gitmoji]: https://gitmoji.dev
552
552
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
553
553
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
554
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.076-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
554
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.233-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
555
555
  [🔐security]: https://github.com/kettle-dev/kettle-rb/blob/main/SECURITY.md
556
556
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
557
557
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+
5
+ module Kettle
6
+ module Rb
7
+ # Curated runtime requirements that a tracked gem places on another tracked
8
+ # gem, per minor series.
9
+ #
10
+ # Each requirement is copied from the dependent gem's own source at the
11
+ # series' newest patch (the patch {GemFloors} uses as the security floor), so
12
+ # consumers that apply those floors also get the matching requirement.
13
+ module GemDependencies
14
+ Entry = Struct.new(:gem_name, :series, :dependency, :requirements, :source)
15
+
16
+ ACTIVERECORD_SQLITE3_ADAPTER = "activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb"
17
+
18
+ ROWS = [
19
+ ["activerecord", "5.2", "sqlite3", ["~> 1.3", ">= 1.3.6"], "rails v5.2.8.1 #{ACTIVERECORD_SQLITE3_ADAPTER}"],
20
+ ["activerecord", "6.0", "sqlite3", ["~> 1.4"], "rails v6.0.6.1 #{ACTIVERECORD_SQLITE3_ADAPTER}"],
21
+ ["activerecord", "6.1", "sqlite3", ["~> 1.4"], "rails v6.1.7.10 #{ACTIVERECORD_SQLITE3_ADAPTER}"],
22
+ ["activerecord", "7.0", "sqlite3", ["~> 1.4"], "rails v7.0.10 #{ACTIVERECORD_SQLITE3_ADAPTER}"],
23
+ ["activerecord", "7.1", "sqlite3", [">= 1.4"], "rails v7.1.6 #{ACTIVERECORD_SQLITE3_ADAPTER}"],
24
+ ["activerecord", "7.2", "sqlite3", [">= 1.4"], "rails v7.2.3.2 #{ACTIVERECORD_SQLITE3_ADAPTER}"],
25
+ ["activerecord", "8.0", "sqlite3", [">= 2.1"], "rails v8.0.5.1 #{ACTIVERECORD_SQLITE3_ADAPTER}"],
26
+ ["activerecord", "8.1", "sqlite3", [">= 2.1"], "rails v8.1.3.1 #{ACTIVERECORD_SQLITE3_ADAPTER}"]
27
+ ].freeze
28
+
29
+ ENTRIES = begin
30
+ entries = {}
31
+ ROWS.each do |row|
32
+ entry = Entry.new(*row)
33
+ entry.requirements.freeze
34
+ entry.freeze
35
+ entries[[entry.gem_name, entry.series, entry.dependency]] = entry
36
+ end
37
+ entries.freeze
38
+ end
39
+
40
+ class << self
41
+ # @param gem_name [String] the dependent gem, e.g. "activerecord"
42
+ # @param version [String, Gem::Version] any version within the series
43
+ # @param dependency [String] the required gem, e.g. "sqlite3"
44
+ # @return [Entry, nil]
45
+ def entry(gem_name, version, dependency)
46
+ series = GemFloors.series_for(version)
47
+ series && ENTRIES[[gem_name.to_s, series, dependency.to_s]]
48
+ end
49
+
50
+ # @return [Array<String>] requirements +gem_name+ places on +dependency+; empty when unknown
51
+ def requirements(gem_name, version, dependency)
52
+ found = entry(gem_name, version, dependency)
53
+ found ? found.requirements : []
54
+ end
55
+
56
+ # @return [Array<Entry>] entries for a dependent/dependency pair, oldest series first
57
+ def entries_for(gem_name, dependency)
58
+ ENTRIES.values
59
+ .select { |entry| entry.gem_name == gem_name.to_s && entry.dependency == dependency.to_s }
60
+ .sort_by { |entry| Gem::Version.new(entry.series) }
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Generated by `rake kettle:rb:gem_floors:generate`; do not edit by hand.
4
+ # Sources: rubygems.org versions API and ruby-advisory-db.
5
+
6
+ module Kettle
7
+ module Rb
8
+ module GemFloors
9
+ GENERATED_ON = "2026-09-15"
10
+ ADVISORY_DB_COMMIT = "f08b5bf5b277"
11
+ TRACKED_GEMS = ["activemodel", "activerecord", "activesupport", "sqlite3"].freeze
12
+ ROWS = [
13
+ ["activemodel", "5.2", "5.2.8.1", ">= 2.2.2", [], []],
14
+ ["activemodel", "6.0", "6.0.6.1", ">= 2.5.0", [], []],
15
+ ["activemodel", "6.1", "6.1.7.10", ">= 2.5.0", [], []],
16
+ ["activemodel", "7.0", "7.0.10", ">= 2.7.0", [], []],
17
+ ["activemodel", "7.1", "7.1.6", ">= 2.7.0", [], []],
18
+ ["activemodel", "7.2", "7.2.3.2", ">= 3.1.0", [], []],
19
+ ["activemodel", "8.0", "8.0.5.1", ">= 3.2.0", [], []],
20
+ ["activemodel", "8.1", "8.1.3.1", ">= 3.2.0", [], []],
21
+ ["activerecord", "5.2", "5.2.8.1", ">= 2.2.2", ["CVE-2021-22880", "CVE-2022-32224", "CVE-2022-44566"], ["CVE-2025-55193"]],
22
+ ["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"]],
23
+ ["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"]],
24
+ ["activerecord", "7.0", "7.0.10", ">= 2.7.0", ["CVE-2022-32224", "CVE-2022-44566", "CVE-2023-22794"], ["CVE-2025-55193"]],
25
+ ["activerecord", "7.1", "7.1.6", ">= 2.7.0", ["CVE-2025-55193"], []],
26
+ ["activerecord", "7.2", "7.2.3.2", ">= 3.1.0", ["CVE-2025-55193"], []],
27
+ ["activerecord", "8.0", "8.0.5.1", ">= 3.2.0", ["CVE-2025-55193"], []],
28
+ ["activerecord", "8.1", "8.1.3.1", ">= 3.2.0", [], []],
29
+ ["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"]],
30
+ ["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"]],
31
+ ["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"]],
32
+ ["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"]],
33
+ ["activesupport", "7.1", "7.1.6", ">= 2.7.0", [], ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"]],
34
+ ["activesupport", "7.2", "7.2.3.2", ">= 3.1.0", ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"], []],
35
+ ["activesupport", "8.0", "8.0.5.1", ">= 3.2.0", ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"], []],
36
+ ["activesupport", "8.1", "8.1.3.1", ">= 3.2.0", ["CVE-2026-33169", "CVE-2026-33170", "CVE-2026-33176"], []],
37
+ ["sqlite3", "1.3", "1.3.13", ">= 1.8.7", [], ["CVE-2026-54619"]],
38
+ ["sqlite3", "1.4", "1.4.4", ">= 1.8.7", [], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
39
+ ["sqlite3", "1.5", "1.5.4", ">= 1.9.2", ["GHSA-mgvv-5mxp-xq67"], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
40
+ ["sqlite3", "1.6", "1.6.9", ">= 1.9.2", [], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
41
+ ["sqlite3", "1.7", "1.7.3", ">= 2.7", [], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
42
+ ["sqlite3", "2.0", "2.0.4", ">= 3.0", [], ["CVE-2026-54619", "GHSA-mwm8-39rw-8826"]],
43
+ ["sqlite3", "2.1", "2.1.1", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
44
+ ["sqlite3", "2.2", "2.2.0", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
45
+ ["sqlite3", "2.3", "2.3.1", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
46
+ ["sqlite3", "2.4", "2.4.1", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
47
+ ["sqlite3", "2.5", "2.5.0", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
48
+ ["sqlite3", "2.6", "2.6.0", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
49
+ ["sqlite3", "2.7", "2.7.4", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
50
+ ["sqlite3", "2.8", "2.8.1", ">= 3.1", [], ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"]],
51
+ ["sqlite3", "2.9", "2.9.6", ">= 3.2", ["CVE-2026-54619", "CVE-2026-54620", "GHSA-mwm8-39rw-8826"], []]
52
+ ].freeze
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+
5
+ module Kettle
6
+ module Rb
7
+ module GemFloors
8
+ # Regenerates +lib/kettle/rb/gem_floors/data.rb+ for tracked gems from the
9
+ # rubygems.org versions API and ruby-advisory-db (through bundler-audit).
10
+ #
11
+ # This is development tooling. It is loaded by the
12
+ # +kettle:rb:gem_floors:generate+ rake task, never by +require "kettle/rb"+.
13
+ #
14
+ # For each minor series of a tracked gem the floor is the newest released
15
+ # patch that does not raise the series' original minimum Ruby. When that
16
+ # patch is affected by an advisory but an older compatible patch is not,
17
+ # the newest unaffected patch is used instead. Advisories that still affect
18
+ # the chosen floor are recorded as unpatched.
19
+ class Generator
20
+ # Tracked gems and the oldest minor series to record for each.
21
+ TRACKED = [
22
+ ["activemodel", "5.2"],
23
+ ["activerecord", "5.2"],
24
+ ["activesupport", "5.2"],
25
+ ["sqlite3", "1.3"]
26
+ ].freeze
27
+
28
+ DATA_PATH = File.expand_path(File.join(File.dirname(__FILE__), "data.rb"))
29
+
30
+ VERSIONS_URL = "https://rubygems.org/api/v1/versions/%s.json"
31
+
32
+ # @return [Array<Array(String, String)>] tracked gem names and oldest series
33
+ attr_reader :tracked
34
+
35
+ # @return [String, nil] ruby-advisory-db commit recorded in the data file
36
+ attr_reader :advisory_db_commit
37
+
38
+ # @return [String] generation date recorded in the data file
39
+ attr_reader :generated_on
40
+
41
+ # @param options [Hash]
42
+ # @option options [Array<Array(String, String)>] :tracked gem names and oldest series
43
+ # @option options [#call] :versions_fetcher returns rubygems.org version hashes for a gem name
44
+ # @option options [#call] :advisories_fetcher returns advisories (+#id+, +#vulnerable?+) for a gem name
45
+ # @option options [String, nil] :advisory_db_commit ruby-advisory-db commit to record
46
+ # @option options [String] :generated_on date to record
47
+ def initialize(options = {})
48
+ @tracked = options[:tracked] || TRACKED
49
+ @versions_fetcher = options[:versions_fetcher] || method(:fetch_versions)
50
+ @advisories_fetcher = options[:advisories_fetcher] || method(:fetch_advisories)
51
+ @advisory_db_commit = options.key?(:advisory_db_commit) ? options[:advisory_db_commit] : database_commit
52
+ @generated_on = options[:generated_on] || Time.now.utc.strftime("%Y-%m-%d")
53
+ end
54
+
55
+ # @return [Array<Array>] rows for +GemFloors::ROWS+
56
+ def rows
57
+ result = []
58
+ tracked.each do |gem_name, oldest_series|
59
+ result.concat(rows_for(gem_name, Gem::Version.new(oldest_series)))
60
+ end
61
+ result
62
+ end
63
+
64
+ # @return [String] Ruby source for +data.rb+
65
+ def render
66
+ lines = [
67
+ "# frozen_string_literal: true",
68
+ "",
69
+ "# Generated by `rake kettle:rb:gem_floors:generate`; do not edit by hand.",
70
+ "# Sources: rubygems.org versions API and ruby-advisory-db.",
71
+ "",
72
+ "module Kettle",
73
+ " module Rb",
74
+ " module GemFloors",
75
+ " GENERATED_ON = #{generated_on.inspect}",
76
+ " ADVISORY_DB_COMMIT = #{advisory_db_commit.inspect}",
77
+ " TRACKED_GEMS = #{tracked.map { |pair| pair.first }.inspect}.freeze",
78
+ " ROWS = ["
79
+ ]
80
+ lines << rows.map { |row| " #{row.inspect}" }.join(",\n")
81
+ lines.concat([" ].freeze", " end", " end", "end", ""])
82
+ lines.join("\n")
83
+ end
84
+
85
+ # Writes the rendered data file.
86
+ #
87
+ # @param path [String]
88
+ # @return [String] the written path
89
+ def write(path = DATA_PATH)
90
+ File.open(path, "w") { |file| file.write(render) }
91
+ path
92
+ end
93
+
94
+ private
95
+
96
+ def rows_for(gem_name, oldest_series)
97
+ advisories = @advisories_fetcher.call(gem_name)
98
+ by_series = releases_by_series(gem_name)
99
+ result = []
100
+ by_series.keys.sort_by { |series| Gem::Version.new(series) }.each do |series|
101
+ next if Gem::Version.new(series) < oldest_series
102
+
103
+ releases = by_series[series].sort_by { |release| release[:version] }
104
+ floor = floor_release(releases, advisories)
105
+ affecting = advisories.select do |advisory|
106
+ releases.any? { |release| advisory.vulnerable?(release[:version]) }
107
+ end
108
+ unpatched = affecting.select { |advisory| advisory.vulnerable?(floor[:version]) }
109
+ result << [
110
+ gem_name,
111
+ series,
112
+ floor[:number],
113
+ floor[:ruby],
114
+ advisory_ids(affecting - unpatched),
115
+ advisory_ids(unpatched)
116
+ ]
117
+ end
118
+ result
119
+ end
120
+
121
+ def floor_release(releases, advisories)
122
+ original_ruby = minimum_ruby(releases.first[:ruby])
123
+ compatible = releases.select { |release| minimum_ruby(release[:ruby]) <= original_ruby }
124
+ unaffected = compatible.reject do |release|
125
+ advisories.any? { |advisory| advisory.vulnerable?(release[:version]) }
126
+ end
127
+ unaffected.last || compatible.last
128
+ end
129
+
130
+ def releases_by_series(gem_name)
131
+ by_series = {}
132
+ @versions_fetcher.call(gem_name).each do |data|
133
+ next if data["prerelease"]
134
+ next if data["platform"] && data["platform"] != "ruby"
135
+
136
+ version = Gem::Version.new(data["number"].to_s)
137
+ segments = version.segments
138
+ next if segments.size < 2
139
+
140
+ series = "#{segments[0]}.#{segments[1]}"
141
+ (by_series[series] ||= []) << {:number => data["number"].to_s, :version => version, :ruby => data["ruby_version"]}
142
+ end
143
+ by_series
144
+ end
145
+
146
+ def minimum_ruby(requirement)
147
+ floor = Gem::Version.new("0")
148
+ return floor if requirement.nil? || requirement.to_s.strip.empty?
149
+
150
+ Gem::Requirement.new(*requirement.to_s.split(/\s*,\s*/)).requirements.each do |operator, version|
151
+ floor = version if [">=", ">", "~>", "="].include?(operator) && version > floor
152
+ end
153
+ floor
154
+ rescue ArgumentError
155
+ Gem::Version.new("0")
156
+ end
157
+
158
+ def advisory_ids(advisories)
159
+ advisories.map { |advisory| advisory.id.to_s }.uniq.sort
160
+ end
161
+
162
+ def fetch_versions(gem_name)
163
+ require "json"
164
+ require "net/http"
165
+ require "uri"
166
+
167
+ JSON.parse(Net::HTTP.get(URI.parse(format(VERSIONS_URL, gem_name))))
168
+ end
169
+
170
+ def fetch_advisories(gem_name)
171
+ database.advisories_for(gem_name)
172
+ end
173
+
174
+ def database
175
+ require "bundler/audit/database"
176
+ @database ||= Bundler::Audit::Database.new
177
+ end
178
+
179
+ def database_commit
180
+ commit = database.commit_id
181
+ commit && commit[0, 12]
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require_relative "gem_floors/data"
5
+
6
+ module Kettle
7
+ module Rb
8
+ # Security floors for tracked gems, one entry per minor series.
9
+ #
10
+ # For each series the floor is the newest released patch that keeps the
11
+ # series' original minimum Ruby, never below an advisory's patched version.
12
+ # Advisories that still affect the floor (a series with no fixed release)
13
+ # are recorded as unpatched so consumers can decide how to treat it.
14
+ #
15
+ # The data lives in +gem_floors/data.rb+ and is regenerated from the
16
+ # rubygems.org versions API and ruby-advisory-db by
17
+ # +rake kettle:rb:gem_floors:generate+ (see {GemFloors::Generator}).
18
+ module GemFloors
19
+ Entry = Struct.new(
20
+ :gem_name,
21
+ :series,
22
+ :floor,
23
+ :ruby,
24
+ :patched_advisories,
25
+ :unpatched_advisories
26
+ )
27
+
28
+ ENTRIES = begin
29
+ entries = {}
30
+ ROWS.each do |row|
31
+ entry = Entry.new(*row)
32
+ entry.patched_advisories.freeze
33
+ entry.unpatched_advisories.freeze
34
+ entry.freeze
35
+ entries[[entry.gem_name, entry.series]] = entry
36
+ end
37
+ entries.freeze
38
+ end
39
+
40
+ class << self
41
+ # @param gem_name [String]
42
+ # @return [Boolean] whether floors are tracked for +gem_name+
43
+ def tracked?(gem_name)
44
+ TRACKED_GEMS.include?(gem_name.to_s)
45
+ end
46
+
47
+ # @param gem_name [String]
48
+ # @param version [String, Gem::Version] any version within the series, e.g. "7.1" or "7.1.3"
49
+ # @return [Entry, nil]
50
+ def entry(gem_name, version)
51
+ series = series_for(version)
52
+ series && ENTRIES[[gem_name.to_s, series]]
53
+ end
54
+
55
+ # @param gem_name [String]
56
+ # @return [Array<Entry>] entries for +gem_name+, oldest series first
57
+ def entries_for(gem_name)
58
+ ENTRIES.values
59
+ .select { |entry| entry.gem_name == gem_name.to_s }
60
+ .sort_by { |entry| Gem::Version.new(entry.series) }
61
+ end
62
+
63
+ # @return [String, nil] the security floor for the series containing +version+
64
+ def floor(gem_name, version)
65
+ found = entry(gem_name, version)
66
+ found && found.floor
67
+ end
68
+
69
+ # Requirements that pin the series and enforce its floor.
70
+ #
71
+ # @example
72
+ # Kettle::Rb::GemFloors.requirements("activerecord", "7.1") #=> ["~> 7.1.0", ">= 7.1.6"]
73
+ # @return [Array<String>] empty when the series is not tracked
74
+ def requirements(gem_name, version)
75
+ found = entry(gem_name, version)
76
+ return [] unless found
77
+
78
+ ["~> #{found.series}.0", ">= #{found.floor}"]
79
+ end
80
+
81
+ # @return [Array<String>] advisory identifiers still affecting the series' floor
82
+ def unpatched_advisories(gem_name, version)
83
+ found = entry(gem_name, version)
84
+ found ? found.unpatched_advisories : []
85
+ end
86
+
87
+ # @param version [String, Gem::Version]
88
+ # @return [String, nil] the "major.minor" series, or nil when it cannot be determined
89
+ def series_for(version)
90
+ segments = Gem::Version.new(version.to_s).segments
91
+ return nil if segments.size < 2
92
+
93
+ "#{segments[0]}.#{segments[1]}"
94
+ rescue ArgumentError
95
+ nil
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -5,7 +5,7 @@ module Kettle
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = "0.1.13"
8
+ VERSION = "0.1.14"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
data/lib/kettle/rb.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "rb/compat_matrix"
4
+ require_relative "rb/gem_floors"
5
+ require_relative "rb/gem_dependencies"
4
6
  require_relative "rb/version"
5
7
 
6
8
  module Kettle
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -46,7 +46,7 @@ dependencies:
46
46
  version: '3.1'
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: 3.1.0
49
+ version: 3.1.1
50
50
  type: :development
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
@@ -56,7 +56,7 @@ dependencies:
56
56
  version: '3.1'
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: 3.1.0
59
+ version: 3.1.1
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: bundler-audit
62
62
  requirement: !ruby/object:Gem::Requirement
@@ -252,6 +252,10 @@ files:
252
252
  - README.md
253
253
  - lib/kettle/rb.rb
254
254
  - lib/kettle/rb/compat_matrix.rb
255
+ - lib/kettle/rb/gem_dependencies.rb
256
+ - lib/kettle/rb/gem_floors.rb
257
+ - lib/kettle/rb/gem_floors/data.rb
258
+ - lib/kettle/rb/gem_floors/generator.rb
255
259
  - lib/kettle/rb/version.rb
256
260
  - sig/kettle/rb.rbs
257
261
  homepage: https://github.com/kettle-dev/kettle-rb
@@ -259,10 +263,10 @@ licenses:
259
263
  - MIT
260
264
  metadata:
261
265
  homepage_uri: https://kettle-rb.galtzo.com
262
- source_code_uri: https://github.com/kettle-dev/kettle-rb/tree/v0.1.13
263
- changelog_uri: https://github.com/kettle-dev/kettle-rb/blob/v0.1.13/CHANGELOG.md
266
+ source_code_uri: https://github.com/kettle-dev/kettle-rb/tree/v0.1.14
267
+ changelog_uri: https://github.com/kettle-dev/kettle-rb/blob/v0.1.14/CHANGELOG.md
264
268
  bug_tracker_uri: https://github.com/kettle-dev/kettle-rb/issues
265
- documentation_uri: https://www.rubydoc.info/gems/kettle-rb/0.1.13
269
+ documentation_uri: https://www.rubydoc.info/gems/kettle-rb/0.1.14
266
270
  funding_uri: https://github.com/sponsors/pboling
267
271
  wiki_uri: https://github.com/kettle-dev/kettle-rb/wiki
268
272
  news_uri: https://www.railsbling.com/tags/kettle-rb
metadata.gz.sig CHANGED
Binary file