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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +22 -1
- data/README.md +1 -1
- data/lib/kettle/rb/gem_dependencies.rb +65 -0
- data/lib/kettle/rb/gem_floors/data.rb +55 -0
- data/lib/kettle/rb/gem_floors/generator.rb +186 -0
- data/lib/kettle/rb/gem_floors.rb +100 -0
- data/lib/kettle/rb/version.rb +1 -1
- data/lib/kettle/rb.rb +2 -0
- data.tar.gz.sig +0 -0
- metadata +10 -6
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f07aaac5073eca17b157f5f95f6bdedf6a634b840f426c9c82530b12e3ab4184
|
|
4
|
+
data.tar.gz: ecf84249deec006ad63e7ab04d488f51fdb2e7eaf5088e5684571c7b6e0e5cab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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
|
data/lib/kettle/rb/version.rb
CHANGED
|
@@ -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.
|
|
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
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.
|
|
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.
|
|
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.
|
|
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.
|
|
263
|
-
changelog_uri: https://github.com/kettle-dev/kettle-rb/blob/v0.1.
|
|
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.
|
|
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
|