class Gem::Cooldown
Applies a cooldown period to remote gem versions as a supply chain attack mitigation. When a cooldown of N days is configured, gem versions published within the last N days are not considered for installation or update. Versions whose publish time is unknown are never excluded, so sources that do not provide publish times keep working.
The cooldown period comes from the --cooldown DAYS option when given, and 0 there disables the cooldown. Without the option the :cooldown: setting in the gemrc file and Bundler’s own cooldown setting both apply and the longer of the two wins, so a 0 in either of them disables nothing while the other names a period.
Attributes
The cooldown period in days.
Public Class Methods
Source
# File lib/rubygems/cooldown.rb, line 33 def self.from_options(options) days = options[:cooldown] return new(days) unless days.nil? require_relative "bundler_settings" new Gem::CooldownSettings.combine(warn_unless_valid(Gem.configuration.cooldown, "the gemrc file"), warn_unless_valid(Gem::BundlerSettings["cooldown"], "Bundler's configuration")) end
Creates a Cooldown from the command line options. The –cooldown option wins outright, so --cooldown 0 bypasses the cooldown however the two tools are configured. Without it the :cooldown: gemrc setting and Bundler’s cooldown setting are both read and the longer of the two applies, so a cooldown configured for only one of them still covers gem commands.
Source
# File lib/rubygems/cooldown.rb, line 43 def initialize(days, now: Time.now) invalid = Gem::CooldownSettings.invalid?(days) @days = Gem::CooldownSettings.days(days) || 0 @now = now Gem::Cooldown.warn_invalid_days(days, "the cooldown setting") if invalid end
# File lib/rubygems/cooldown.rb, line 86 def self.output_skipped_summary(entries) return if entries.nil? || entries.empty? newest = {} entries.each do |entry| current = newest[entry[:name]] newest[entry[:name]] = entry if current.nil? || entry[:version] > current[:version] end ui = Gem::DefaultUserInteraction.ui ui.say "The following gem versions were skipped by the cooldown setting:" newest.values.sort_by {|entry| entry[:name] }.each do |entry| days = entry[:available_in_days] ui.say " * #{entry[:name]} #{entry[:version]} (available in #{days} #{days == 1 ? "day" : "days"}), resolved #{entry[:resolved]} instead" end end
Reports, per gem, the newest version the cooldown kept out of a completed installation or update. entries are hashes with :name, :version, :resolved and :available_in_days keys; when several entries name the same gem only the newest version is shown.
Source
# File lib/rubygems/cooldown.rb, line 120 def self.parse_created_at(value) return unless value.is_a?(String) && value.match?(FOUR_DIGIT_YEAR) require "time" begin Time.iso8601(value.match?(TIME_ZONE_SUFFIX) ? value : "#{value}Z") rescue ArgumentError nil end end
Parses a created_at timestamp from the compact index. A timestamp without a time zone offset is read as UTC, because reading it as local time would shift the cooldown window by the environment’s offset. Returns nil for anything unparsable, including a year outside four digits, so the cooldown fails open.
# File lib/rubygems/cooldown.rb, line 135 def self.warn_missing_created_at(source) return if @warned @warned = true Gem::DefaultUserInteraction.ui.alert_warning \ "#{source.uri} does not provide gem publish times, the cooldown period does not apply to gems from this source" end
Warns once per process that source did not provide publish times, so the cooldown cannot be applied to gems from it.
Public Instance Methods
Source
# File lib/rubygems/cooldown.rb, line 55 def active? @days > 0 end
True when a cooldown period is configured.
Source
# File lib/rubygems/cooldown.rb, line 74 def remaining_days(created_at) remaining = @days * 86_400 - (@now - created_at) [(remaining / 86_400.0).ceil, 1].max end
Number of days until a gem version published at created_at leaves the cooldown period, rounded up and at least 1.
Source
# File lib/rubygems/cooldown.rb, line 63 def skip?(created_at) return false unless active? return false unless created_at (@now - created_at) < @days * 86_400 end
True when a gem version published at created_at must not be considered. Versions with an unknown publish time (nil) are kept.