Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rubygem-rack-protection for openSUSE:Factory checked in at 2022-10-12 18:25:18 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-rack-protection (Old) and /work/SRC/openSUSE:Factory/.rubygem-rack-protection.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-rack-protection" Wed Oct 12 18:25:18 2022 rev:11 rq:1010075 version:3.0.2 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-rack-protection/rubygem-rack-protection.changes 2022-08-09 15:27:04.785407013 +0200 +++ /work/SRC/openSUSE:Factory/.rubygem-rack-protection.new.2275/rubygem-rack-protection.changes 2022-10-12 18:27:07.574011436 +0200 @@ -1,0 +2,6 @@ +Mon Oct 10 13:15:46 UTC 2022 - Stephan Kulow <co...@suse.com> + +updated to version 3.0.2 + no changelog found + +------------------------------------------------------------------- Old: ---- rack-protection-2.2.2.gem New: ---- rack-protection-3.0.2.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-rack-protection.spec ++++++ --- /var/tmp/diff_new_pack.oiPcQy/_old 2022-10-12 18:27:07.978012326 +0200 +++ /var/tmp/diff_new_pack.oiPcQy/_new 2022-10-12 18:27:07.982012334 +0200 @@ -24,13 +24,13 @@ # Name: rubygem-rack-protection -Version: 2.2.2 +Version: 3.0.2 Release: 0 %define mod_name rack-protection %define mod_full_name %{mod_name}-%{version} BuildRoot: %{_tmppath}/%{name}-%{version}-build +BuildRequires: %{ruby >= 2.6.0} BuildRequires: %{rubygem gem2rpm} -BuildRequires: %{ruby} BuildRequires: ruby-macros >= 5 URL: http://sinatrarb.com/protection/ Source: https://rubygems.org/gems/%{mod_full_name}.gem ++++++ rack-protection-2.2.2.gem -> rack-protection-3.0.2.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Gemfile new/Gemfile --- old/Gemfile 2022-07-23 23:10:39.000000000 +0200 +++ new/Gemfile 2022-10-01 19:22:38.000000000 +0200 @@ -1,13 +1,17 @@ -source "https://rubygems.org" +# frozen_string_literal: true + +source 'https://rubygems.org' # encoding: utf-8 gem 'rake' rack_version = ENV['rack'].to_s -rack_version = nil if rack_version.empty? or rack_version == 'stable' -rack_version = {:github => 'rack/rack'} if rack_version == 'master' +rack_version = nil if rack_version.empty? || (rack_version == 'stable') +rack_version = { github: 'rack/rack' } if rack_version == 'main' gem 'rack', rack_version gem 'sinatra', path: '..' gemspec + +gem 'rack-test', github: 'rack/rack-test' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Rakefile new/Rakefile --- old/Rakefile 2022-07-23 23:10:39.000000000 +0200 +++ new/Rakefile 2022-10-01 19:22:38.000000000 +0200 @@ -1,53 +1,55 @@ -# encoding: utf-8 -$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path('lib', __dir__) begin require 'bundler' Bundler::GemHelper.install_tasks rescue LoadError => e - $stderr.puts e + warn e end -desc "run specs" -task(:spec) { ruby '-S rspec spec' } +desc 'run specs' +task(:spec) { ruby '-S rspec' } namespace :doc do task :readmes do Dir.glob 'lib/rack/protection/*.rb' do |file| excluded_files = %w[lib/rack/protection/base.rb lib/rack/protection/version.rb] next if excluded_files.include?(file) + doc = File.read(file)[/^ module Protection(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n") - file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc" - Dir.mkdir "doc" unless File.directory? "doc" + file = "doc/#{file[4..-4].tr('/_', '-')}.rdoc" + Dir.mkdir 'doc' unless File.directory? 'doc' puts "writing #{file}" - File.open(file, "w") { |f| f << doc } + File.open(file, 'w') { |f| f << doc } end end task :index do - doc = File.read("README.md") - file = "doc/rack-protection-readme.md" - Dir.mkdir "doc" unless File.directory? "doc" + doc = File.read('README.md') + file = 'doc/rack-protection-readme.md' + Dir.mkdir 'doc' unless File.directory? 'doc' puts "writing #{file}" - File.open(file, "w") { |f| f << doc } + File.open(file, 'w') { |f| f << doc } end - task :all => [:readmes, :index] + task all: %i[readmes index] end -desc "generate documentation" -task :doc => 'doc:all' +desc 'generate documentation' +task doc: 'doc:all' -desc "generate gemspec" +desc 'generate gemspec' task 'rack-protection.gemspec' do require 'rack/protection/version' content = File.binread 'rack-protection.gemspec' # fetch data fields = { - :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/), - :email => ["m...@zzak.io", "konstantin.ha...@gmail.com"], - :files => %w(License README.md Rakefile Gemfile rack-protection.gemspec) + Dir['lib/**/*'] + authors: `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/), + email: ['m...@zzak.io', 'konstantin.ha...@gmail.com'], + files: %w[License README.md Rakefile Gemfile rack-protection.gemspec] + Dir['lib/**/*'] } # insert data @@ -67,6 +69,6 @@ File.open('rack-protection.gemspec', 'w') { |f| f << content } end -task :gemspec => 'rack-protection.gemspec' -task :default => :spec -task :test => :spec +task gemspec: 'rack-protection.gemspec' +task default: :spec +task test: :spec Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/authenticity_token.rb new/lib/rack/protection/authenticity_token.rb --- old/lib/rack/protection/authenticity_token.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/authenticity_token.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' require 'securerandom' require 'openssl' @@ -17,7 +19,10 @@ # It checks the <tt>X-CSRF-Token</tt> header and the <tt>POST</tt> form # data. # - # Compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem. + # It is not OOTB-compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem. + # For that, the following patch needs to be applied: + # + # Rack::Protection::AuthenticityToken.default_options(key: "csrf.token", authenticity_param: "_csrf") # # == Options # @@ -92,12 +97,12 @@ class AuthenticityToken < Base TOKEN_LENGTH = 32 - default_options :authenticity_param => 'authenticity_token', - :key => :csrf, - :allow_if => nil + default_options authenticity_param: 'authenticity_token', + key: :csrf, + allow_if: nil def self.token(session, path: nil, method: :post) - self.new(nil).mask_authenticity_token(session, path: path, method: method) + new(nil).mask_authenticity_token(session, path: path, method: method) end def self.random_token @@ -111,8 +116,8 @@ safe?(env) || valid_token?(env, env['HTTP_X_CSRF_TOKEN']) || valid_token?(env, Request.new(env).params[options[:authenticity_param]]) || - ( options[:allow_if] && options[:allow_if].call(env) ) - rescue + options[:allow_if]&.call(env) + rescue StandardError false end @@ -120,10 +125,10 @@ set_token(session) token = if path && method - per_form_token(session, path, method) - else - global_token(session) - end + per_form_token(session, path, method) + else + global_token(session) + end mask_token(token) end @@ -140,7 +145,7 @@ # Checks the client's masked token to see if it matches the # session token. def valid_token?(env, token) - return false if token.nil? || token.empty? + return false if token.nil? || !token.is_a?(String) || token.empty? session = session(env) @@ -182,7 +187,7 @@ # value and decrypt it token_length = masked_token.length / 2 one_time_pad = masked_token[0...token_length] - encrypted_token = masked_token[token_length..-1] + encrypted_token = masked_token[token_length..] xor_byte_strings(one_time_pad, encrypted_token) end @@ -204,8 +209,7 @@ def compare_with_per_form_token(token, session, request) secure_compare(token, - per_form_token(session, request.path.chomp('/'), request.request_method) - ) + per_form_token(session, request.path.chomp('/'), request.request_method)) end def real_token(session) @@ -230,7 +234,7 @@ def token_hmac(session, identifier) OpenSSL::HMAC.digest( - OpenSSL::Digest::SHA256.new, + OpenSSL::Digest.new('SHA256'), real_token(session), identifier ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/base.rb new/lib/rack/protection/base.rb --- old/lib/rack/protection/base.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/base.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' require 'rack/utils' require 'digest' @@ -8,12 +10,12 @@ module Protection class Base DEFAULT_OPTIONS = { - :reaction => :default_reaction, :logging => true, - :message => 'Forbidden', :encryptor => Digest::SHA1, - :session_key => 'rack.session', :status => 403, - :allow_empty_referrer => true, - :report_key => "protection.failed", - :html_types => %w[text/html application/xhtml text/xml application/xml] + reaction: :default_reaction, logging: true, + message: 'Forbidden', encryptor: Digest::SHA1, + session_key: 'rack.session', status: 403, + allow_empty_referrer: true, + report_key: 'protection.failed', + html_types: %w[text/html application/xhtml text/xml application/xml] } attr_reader :app, :options @@ -31,7 +33,8 @@ end def initialize(app, options = {}) - @app, @options = app, default_options.merge(options) + @app = app + @options = default_options.merge(options) end def safe?(env) @@ -52,24 +55,26 @@ def react(env) result = send(options[:reaction], env) - result if Array === result and result.size == 3 + result if (Array === result) && (result.size == 3) end def warn(env, message) return unless options[:logging] + l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors']) l.warn(message) end def instrument(env) - return unless i = options[:instrumenter] + return unless (i = options[:instrumenter]) + env['rack.protection.attack'] = self.class.name.split('::').last.downcase i.instrument('rack.protection', env) end def deny(env) warn env, "attack prevented by #{self.class}" - [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]] + [options[:status], { 'Content-Type' => 'text/plain' }, [options[:message]]] end def report(env) @@ -83,7 +88,8 @@ def session(env) return env[options[:session_key]] if session? env - fail "you need to set up a session middleware *before* #{self.class}" + + raise "you need to set up a session middleware *before* #{self.class}" end def drop_session(env) @@ -92,7 +98,8 @@ def referrer(env) ref = env['HTTP_REFERER'].to_s - return if !options[:allow_empty_referrer] and ref.empty? + return if !options[:allow_empty_referrer] && ref.empty? + URI.parse(ref).host || Request.new(env).host rescue URI::InvalidURIError end @@ -102,7 +109,7 @@ end def random_string(secure = defined? SecureRandom) - secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1) + secure ? SecureRandom.hex(16) : '%032x' % rand((2**128) - 1) rescue NotImplementedError random_string false end @@ -118,8 +125,9 @@ alias default_reaction deny def html?(headers) - return false unless header = headers.detect { |k,v| k.downcase == 'content-type' } - options[:html_types].include? header.last[/^\w+\/\w+/] + return false unless (header = headers.detect { |k, _v| k.downcase == 'content-type' }) + + options[:html_types].include? header.last[%r{^\w+/\w+}] end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/content_security_policy.rb new/lib/rack/protection/content_security_policy.rb --- old/lib/rack/protection/content_security_policy.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/content_security_policy.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -38,16 +39,16 @@ class ContentSecurityPolicy < Base default_options default_src: "'self'", report_only: false - DIRECTIVES = %i(base_uri child_src connect_src default_src + DIRECTIVES = %i[base_uri child_src connect_src default_src font_src form_action frame_ancestors frame_src img_src manifest_src media_src object_src plugin_types referrer reflected_xss report_to report_uri require_sri_for sandbox script_src style_src worker_src webrtc_src navigate_to - prefetch_src).freeze + prefetch_src].freeze - NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener - upgrade_insecure_requests).freeze + NO_ARG_DIRECTIVES = %i[block_all_mixed_content disown_opener + upgrade_insecure_requests].freeze def csp_policy directives = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/cookie_tossing.rb new/lib/rack/protection/cookie_tossing.rb --- old/lib/rack/protection/cookie_tossing.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/cookie_tossing.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' require 'pathname' @@ -29,9 +31,8 @@ cookie_header = env['HTTP_COOKIE'] cookies = Rack::Utils.parse_query(cookie_header, ';,') { |s| s } cookies.each do |k, v| - if k == session_key && Array(v).size > 1 - bad_cookies << k - elsif k != session_key && Rack::Utils.unescape(k) == session_key + if (k == session_key && Array(v).size > 1) || + (k != session_key && Rack::Utils.unescape(k) == session_key) bad_cookies << k end end @@ -40,6 +41,7 @@ def remove_bad_cookies(request, response) return if bad_cookies.empty? + paths = cookie_paths(request.path) bad_cookies.each do |name| paths.each { |path| response.set_cookie name, empty_cookie(request.host, path) } @@ -49,7 +51,7 @@ def redirect(env) request = Request.new(env) warn env, "attack prevented by #{self.class}" - [302, {'Content-Type' => 'text/html', 'Location' => request.path}, []] + [302, { 'Content-Type' => 'text/html', 'Location' => request.path }, []] end def bad_cookies @@ -64,7 +66,7 @@ end def empty_cookie(host, path) - {:value => '', :domain => host, :path => path, :expires => Time.at(0)} + { value: '', domain: host, path: path, expires: Time.at(0) } end def session_key diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/encrypted_cookie.rb new/lib/rack/protection/encrypted_cookie.rb --- old/lib/rack/protection/encrypted_cookie.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/lib/rack/protection/encrypted_cookie.rb 2022-10-01 19:22:38.000000000 +0200 @@ -0,0 +1,273 @@ +# frozen_string_literal: true + +require 'openssl' +require 'zlib' +require 'json' +require 'rack/request' +require 'rack/response' +require 'rack/session/abstract/id' + +module Rack + module Protection + # Rack::Protection::EncryptedCookie provides simple cookie based session management. + # By default, the session is a Ruby Hash stored as base64 encoded marshalled + # data set to :key (default: rack.session). The object that encodes the + # session data is configurable and must respond to +encode+ and +decode+. + # Both methods must take a string and return a string. + # + # When the secret key is set, cookie data is checked for data integrity. + # The old_secret key is also accepted and allows graceful secret rotation. + # A legacy_hmac_secret is also accepted and is used to upgrade existing + # sessions to the new encryption scheme. + # + # There is also a legacy_hmac_coder option which can be set if a non-default + # coder was used for legacy session cookies. + # + # Example: + # + # use Rack::Protection::EncryptedCookie, + # :key => 'rack.session', + # :domain => 'foo.com', + # :path => '/', + # :expire_after => 2592000, + # :secret => 'change_me', + # :old_secret => 'old_secret' + # + # All parameters are optional. + # + # Example using legacy HMAC options + # + # Rack::Protection:EncryptedCookie.new(application, { + # # The secret used for legacy HMAC cookies + # legacy_hmac_secret: 'legacy secret', + # # legacy_hmac_coder will default to Rack::Protection::EncryptedCookie::Base64::Marshal + # legacy_hmac_coder: Rack::Protection::EncryptedCookie::Identity.new, + # # legacy_hmac will default to OpenSSL::Digest::SHA1 + # legacy_hmac: OpenSSL::Digest::SHA256 + # }) + # + # Example of a cookie with no encoding: + # + # Rack::Protection::EncryptedCookie.new(application, { + # :coder => Rack::Protection::EncryptedCookie::Identity.new + # }) + # + # Example of a cookie with custom encoding: + # + # Rack::Protection::EncryptedCookie.new(application, { + # :coder => Class.new { + # def encode(str); str.reverse; end + # def decode(str); str.reverse; end + # }.new + # }) + # + class EncryptedCookie < Rack::Session::Abstract::Persisted + # Encode session cookies as Base64 + class Base64 + def encode(str) + [str].pack('m0') + end + + def decode(str) + str.unpack1('m') + end + + # Encode session cookies as Marshaled Base64 data + class Marshal < Base64 + def encode(str) + super(::Marshal.dump(str)) + end + + def decode(str) + return unless str + + begin + ::Marshal.load(super(str)) + rescue StandardError + nil + end + end + end + + # N.B. Unlike other encoding methods, the contained objects must be a + # valid JSON composite type, either a Hash or an Array. + class JSON < Base64 + def encode(obj) + super(::JSON.dump(obj)) + end + + def decode(str) + return unless str + + begin + ::JSON.parse(super(str)) + rescue StandardError + nil + end + end + end + + class ZipJSON < Base64 + def encode(obj) + super(Zlib::Deflate.deflate(::JSON.dump(obj))) + end + + def decode(str) + return unless str + + ::JSON.parse(Zlib::Inflate.inflate(super(str))) + rescue StandardError + nil + end + end + end + + # Use no encoding for session cookies + class Identity + def encode(str); str; end + def decode(str); str; end + end + + class Marshal + def encode(str) + ::Marshal.dump(str) + end + + def decode(str) + ::Marshal.load(str) if str + end + end + + attr_reader :coder + + def initialize(app, options = {}) + # Assume keys are hex strings and convert them to raw byte strings for + # actual key material + @secrets = options.values_at(:secret, :old_secret).compact.map do |secret| + [secret].pack('H*') + end + + warn <<-MSG unless secure?(options) + SECURITY WARNING: No secret option provided to Rack::Protection::EncryptedCookie. + This poses a security threat. It is strongly recommended that you + provide a secret to prevent exploits that may be possible from crafted + cookies. This will not be supported in future versions of Rack, and + future versions will even invalidate your existing user cookies. + + Called from: #{caller[0]}. + MSG + + warn <<-MSG if @secrets.first && @secrets.first.length < 32 + SECURITY WARNING: Your secret is not long enough. It must be at least + 32 bytes long and securely random. To generate such a key for use + you can run the following command: + + ruby -rsecurerandom -e 'p SecureRandom.hex(32)' + + Called from: #{caller[0]}. + MSG + + if options.key?(:legacy_hmac_secret) + @legacy_hmac = options.fetch(:legacy_hmac, OpenSSL::Digest::SHA1) + + # Multiply the :digest_length: by 2 because this value is the length of + # the digest in bytes but session digest strings are encoded as hex + # strings + @legacy_hmac_length = @legacy_hmac.new.digest_length * 2 + @legacy_hmac_secret = options[:legacy_hmac_secret] + @legacy_hmac_coder = (options[:legacy_hmac_coder] ||= Base64::Marshal.new) + else + @legacy_hmac = false + end + + # If encryption is used we can just use a default Marshal encoder + # without Base64 encoding the results. + # + # If no encryption is used, rely on the previous default (Base64::Marshal) + @coder = (options[:coder] ||= (@secrets.any? ? Marshal.new : Base64::Marshal.new)) + + super(app, options.merge!(cookie_only: true)) + end + + private + + def find_session(req, _sid) + data = unpacked_cookie_data(req) + data = persistent_session_id!(data) + [data['session_id'], data] + end + + def extract_session_id(request) + unpacked_cookie_data(request)['session_id'] + end + + def unpacked_cookie_data(request) + request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k| + session_data = cookie_data = request.cookies[@key] + + # Try to decrypt with the first secret, if that returns nil, try + # with old_secret + unless @secrets.empty? + session_data = Rack::Protection::Encryptor.decrypt_message(cookie_data, @secrets.first) + session_data ||= Rack::Protection::Encryptor.decrypt_message(cookie_data, @secrets[1]) if @secrets.size > 1 + end + + # If session_data is still nil, are there is a legacy HMAC + # configured, try verify and parse the cookie that way + if !session_data && @legacy_hmac + digest = cookie_data.slice!(-@legacy_hmac_length..-1) + cookie_data.slice!(-2..-1) # remove double dash + session_data = cookie_data if digest_match?(cookie_data, digest) + + # Decode using legacy HMAC decoder + request.set_header(k, @legacy_hmac_coder.decode(session_data) || {}) + else + request.set_header(k, coder.decode(session_data) || {}) + end + end + end + + def persistent_session_id!(data, sid = nil) + data ||= {} + data['session_id'] ||= sid || generate_sid + data + end + + def write_session(req, session_id, session, _options) + session = session.merge('session_id' => session_id) + session_data = coder.encode(session) + + unless @secrets.empty? + session_data = Rack::Protection::Encryptor.encrypt_message(session_data, @secrets.first) + end + + if session_data.size > (4096 - @key.size) + req.get_header(RACK_ERRORS).puts('Warning! Rack::Protection::EncryptedCookie data size exceeds 4K.') + nil + else + session_data + end + end + + def delete_session(_req, _session_id, options) + # Nothing to do here, data is in the client + generate_sid unless options[:drop] + end + + def digest_match?(data, digest) + return false unless data && digest + + Rack::Utils.secure_compare(digest, generate_hmac(data)) + end + + def generate_hmac(data) + OpenSSL::HMAC.hexdigest(@legacy_hmac.new, @legacy_hmac_secret, data) + end + + def secure?(options) + @secrets.size >= 1 || + (options[:coder] && options[:let_coder_handle_secure_encoding]) + end + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/encryptor.rb new/lib/rack/protection/encryptor.rb --- old/lib/rack/protection/encryptor.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/lib/rack/protection/encryptor.rb 2022-10-01 19:22:38.000000000 +0200 @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'openssl' + +module Rack + module Protection + module Encryptor + CIPHER = 'aes-256-gcm' + DELIMITER = '--' + + def self.base64_encode(str) + [str].pack('m0') + end + + def self.base64_decode(str) + str.unpack1('m0') + end + + def self.encrypt_message(data, secret, auth_data = '') + raise ArgumentError, 'data cannot be nil' if data.nil? + + cipher = OpenSSL::Cipher.new(CIPHER) + cipher.encrypt + cipher.key = secret[0, cipher.key_len] + + # Rely on OpenSSL for the initialization vector + iv = cipher.random_iv + + # This must be set to properly use AES GCM for the OpenSSL module + cipher.auth_data = auth_data + + cipher_text = cipher.update(data) + cipher_text << cipher.final + + "#{base64_encode cipher_text}#{DELIMITER}#{base64_encode iv}#{DELIMITER}#{base64_encode cipher.auth_tag}" + end + + def self.decrypt_message(data, secret) + return unless data + + cipher = OpenSSL::Cipher.new(CIPHER) + cipher_text, iv, auth_tag = data.split(DELIMITER, 3).map! { |v| base64_decode(v) } + + # This check is from ActiveSupport::MessageEncryptor + # see: https://github.com/ruby/openssl/issues/63 + return if auth_tag.nil? || auth_tag.bytes.length != 16 + + cipher.decrypt + cipher.key = secret[0, cipher.key_len] + cipher.iv = iv + cipher.auth_tag = auth_tag + cipher.auth_data = '' + + decrypted_data = cipher.update(cipher_text) + decrypted_data << cipher.final + decrypted_data + rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError + nil + end + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/escaped_params.rb new/lib/rack/protection/escaped_params.rb --- old/lib/rack/protection/escaped_params.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/escaped_params.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' require 'rack/utils' require 'tempfile' @@ -15,8 +17,7 @@ # More infos:: http://en.wikipedia.org/wiki/Cross-site_scripting # # Automatically escapes Rack::Request#params so they can be embedded in HTML - # or JavaScript without any further issues. Calls +html_safe+ on the escaped - # strings if defined, to avoid double-escaping in Rails. + # or JavaScript without any further issues. # # Options: # escape:: What escaping modes to use, should be Symbol or Array of Symbols. @@ -29,8 +30,8 @@ public :escape_html end - default_options :escape => :html, - :escaper => defined?(EscapeUtils) ? EscapeUtils : self + default_options escape: :html, + escaper: defined?(EscapeUtils) ? EscapeUtils : self def initialize(*) super @@ -41,15 +42,19 @@ @javascript = modes.include? :javascript @url = modes.include? :url - if @javascript and not @escaper.respond_to? :escape_javascript - fail("Use EscapeUtils for JavaScript escaping.") - end + return unless @javascript && (!@escaper.respond_to? :escape_javascript) + + raise('Use EscapeUtils for JavaScript escaping.') end def call(env) request = Request.new(env) get_was = handle(request.GET) - post_was = handle(request.POST) rescue nil + post_was = begin + handle(request.POST) + rescue StandardError + nil + end app.call env ensure request.GET.replace get_was if get_was @@ -68,13 +73,12 @@ when Array then object.map { |o| escape(o) } when String then escape_string(object) when Tempfile then object - else nil end end def escape_hash(hash) hash = hash.dup - hash.each { |k,v| hash[k] = escape(v) } + hash.each { |k, v| hash[k] = escape(v) } hash end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/form_token.rb new/lib/rack/protection/form_token.rb --- old/lib/rack/protection/form_token.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/form_token.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -16,7 +18,7 @@ # Compatible with rack-csrf. class FormToken < AuthenticityToken def accepts?(env) - env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super + env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' or super end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/frame_options.rb new/lib/rack/protection/frame_options.rb --- old/lib/rack/protection/frame_options.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/frame_options.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -17,7 +19,7 @@ # frame. Use :deny to forbid any embedding, :sameorigin # to allow embedding from the same origin (default). class FrameOptions < Base - default_options :frame_options => :sameorigin + default_options frame_options: :sameorigin def frame_options @frame_options ||= begin diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/http_origin.rb new/lib/rack/protection/http_origin.rb --- old/lib/rack/protection/http_origin.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/http_origin.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -19,7 +21,7 @@ class HttpOrigin < Base DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } default_reaction :deny - default_options :allow_if => nil + default_options allow_if: nil def base_url(env) request = Rack::Request.new(env) @@ -29,19 +31,13 @@ def accepts?(env) return true if safe? env - return true unless origin = env['HTTP_ORIGIN'] + return true unless (origin = env['HTTP_ORIGIN']) return true if base_url(env) == origin - return true if options[:allow_if] && options[:allow_if].call(env) - - if options.key? :origin_whitelist - warn env, "Rack::Protection origin_whitelist option is deprecated and will be removed, " \ - "use permitted_origins instead.\n" - end + return true if options[:allow_if]&.call(env) - permitted_origins = options[:permitted_origins] || options[:origin_whitelist] + permitted_origins = options[:permitted_origins] Array(permitted_origins).include? origin end - end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/ip_spoofing.rb new/lib/rack/protection/ip_spoofing.rb --- old/lib/rack/protection/ip_spoofing.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/ip_spoofing.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -13,9 +15,11 @@ def accepts?(env) return true unless env.include? 'HTTP_X_FORWARDED_FOR' + ips = env['HTTP_X_FORWARDED_FOR'].split(/\s*,\s*/) - return false if env.include? 'HTTP_CLIENT_IP' and not ips.include? env['HTTP_CLIENT_IP'] - return false if env.include? 'HTTP_X_REAL_IP' and not ips.include? env['HTTP_X_REAL_IP'] + return false if env.include?('HTTP_CLIENT_IP') && (!ips.include? env['HTTP_CLIENT_IP']) + return false if env.include?('HTTP_X_REAL_IP') && (!ips.include? env['HTTP_X_REAL_IP']) + true end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/json_csrf.rb new/lib/rack/protection/json_csrf.rb --- old/lib/rack/protection/json_csrf.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/json_csrf.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -17,7 +19,7 @@ # # The `:allow_if` option can be set to a proc to use custom allow/deny logic. class JsonCsrf < Base - default_options :allow_if => nil + default_options allow_if: nil alias react deny @@ -36,8 +38,9 @@ def has_vector?(request, headers) return false if request.xhr? - return false if options[:allow_if] && options[:allow_if].call(request.env) - return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/ + return false if options[:allow_if]&.call(request.env) + return false unless headers['Content-Type'].to_s.split(';', 2).first =~ %r{^\s*application/json\s*$} + origin(request.env).nil? and referrer(request.env) != request.host end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/path_traversal.rb new/lib/rack/protection/path_traversal.rb --- old/lib/rack/protection/path_traversal.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/path_traversal.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -11,11 +13,11 @@ # Thus <tt>GET /foo/%2e%2e%2fbar</tt> becomes <tt>GET /bar</tt>. class PathTraversal < Base def call(env) - path_was = env["PATH_INFO"] - env["PATH_INFO"] = cleanup path_was if path_was && !path_was.empty? + path_was = env['PATH_INFO'] + env['PATH_INFO'] = cleanup path_was if path_was && !path_was.empty? app.call env ensure - env["PATH_INFO"] = path_was + env['PATH_INFO'] = path_was end def cleanup(path) @@ -29,12 +31,13 @@ unescaped = unescaped.gsub(backslash, slash) unescaped.split(slash).each do |part| - next if part.empty? or part == dot + next if part.empty? || (part == dot) + part == '..' ? parts.pop : parts << part end cleaned = slash + parts.join(slash) - cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$} + cleaned << slash if parts.any? && unescaped =~ (%r{/\.{0,2}$}) cleaned end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/referrer_policy.rb new/lib/rack/protection/referrer_policy.rb --- old/lib/rack/protection/referrer_policy.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/referrer_policy.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -13,7 +15,7 @@ # Options: # referrer_policy:: The policy to use (default: 'strict-origin-when-cross-origin') class ReferrerPolicy < Base - default_options :referrer_policy => 'strict-origin-when-cross-origin' + default_options referrer_policy: 'strict-origin-when-cross-origin' def call(env) status, headers, body = @app.call(env) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/remote_referrer.rb new/lib/rack/protection/remote_referrer.rb --- old/lib/rack/protection/remote_referrer.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/remote_referrer.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/remote_token.rb new/lib/rack/protection/remote_token.rb --- old/lib/rack/protection/remote_token.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/remote_token.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/session_hijacking.rb new/lib/rack/protection/session_hijacking.rb --- old/lib/rack/protection/session_hijacking.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/session_hijacking.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -13,23 +15,22 @@ # spoofed, too, this will not prevent determined hijacking attempts. class SessionHijacking < Base default_reaction :drop_session - default_options :tracking_key => :tracking, :encrypt_tracking => true, - :track => %w[HTTP_USER_AGENT] + default_options tracking_key: :tracking, + track: %w[HTTP_USER_AGENT] def accepts?(env) session = session env key = options[:tracking_key] if session.include? key - session[key].all? { |k,v| v == encrypt(env[k]) } + session[key].all? { |k, v| v == encode(env[k]) } else session[key] = {} - options[:track].each { |k| session[key][k] = encrypt(env[k]) } + options[:track].each { |k| session[key][k] = encode(env[k]) } end end - def encrypt(value) - value = value.to_s.downcase - options[:encrypt_tracking] ? super(value) : value + def encode(value) + value.to_s.downcase end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/strict_transport.rb new/lib/rack/protection/strict_transport.rb --- old/lib/rack/protection/strict_transport.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/strict_transport.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -18,11 +20,11 @@ # preload:: Allow this domain to be included in browsers HSTS preload list. See https://hstspreload.appspot.com/ class StrictTransport < Base - default_options :max_age => 31_536_000, :include_subdomains => false, :preload => false + default_options max_age: 31_536_000, include_subdomains: false, preload: false def strict_transport @strict_transport ||= begin - strict_transport = 'max-age=' + options[:max_age].to_s + strict_transport = "max-age=#{options[:max_age]}" strict_transport += '; includeSubDomains' if options[:include_subdomains] strict_transport += '; preload' if options[:preload] strict_transport.to_str diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/version.rb new/lib/rack/protection/version.rb --- old/lib/rack/protection/version.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/version.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,5 +1,7 @@ +# frozen_string_literal: true + module Rack module Protection - VERSION = '2.2.1' + VERSION = '3.0.1' end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection/xss_header.rb new/lib/rack/protection/xss_header.rb --- old/lib/rack/protection/xss_header.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection/xss_header.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection' module Rack @@ -12,7 +14,7 @@ # Options: # xss_mode:: How the browser should prevent the attack (default: :block) class XSSHeader < Base - default_options :xss_mode => :block, :nosniff => true + default_options xss_mode: :block, nosniff: true def call(env) status, headers, body = @app.call(env) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack/protection.rb new/lib/rack/protection.rb --- old/lib/rack/protection.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack/protection.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/protection/version' require 'rack' @@ -7,6 +9,8 @@ autoload :Base, 'rack/protection/base' autoload :CookieTossing, 'rack/protection/cookie_tossing' autoload :ContentSecurityPolicy, 'rack/protection/content_security_policy' + autoload :Encryptor, 'rack/protection/encryptor' + autoload :EncryptedCookie, 'rack/protection/encrypted_cookie' autoload :EscapedParams, 'rack/protection/escaped_params' autoload :FormToken, 'rack/protection/form_token' autoload :FrameOptions, 'rack/protection/frame_options' @@ -27,7 +31,7 @@ use_these = Array options[:use] if options.fetch(:without_session, false) - except += [:session_hijacking, :remote_token] + except += %i[session_hijacking remote_token] end Rack::Builder.new do diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack-protection.rb new/lib/rack-protection.rb --- old/lib/rack-protection.rb 2022-07-23 23:10:39.000000000 +0200 +++ new/lib/rack-protection.rb 2022-10-01 19:22:38.000000000 +0200 @@ -1 +1 @@ -require "rack/protection" +require 'rack/protection' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/rack_protection.rb new/lib/rack_protection.rb --- old/lib/rack_protection.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/lib/rack_protection.rb 2022-10-01 19:22:38.000000000 +0200 @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require 'rack/protection' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2022-07-23 23:10:39.000000000 +0200 +++ new/metadata 2022-10-01 19:22:38.000000000 +0200 @@ -1,14 +1,14 @@ --- !ruby/object:Gem::Specification name: rack-protection version: !ruby/object:Gem::Version - version: 2.2.2 + version: 3.0.2 platform: ruby authors: - https://github.com/sinatra/sinatra/graphs/contributors autorequire: bindir: bin cert_chain: [] -date: 2022-07-23 00:00:00.000000000 Z +date: 2022-10-01 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rack @@ -28,30 +28,30 @@ name: rack-test requirement: !ruby/object:Gem::Requirement requirements: - - - ">=" + - - "~>" - !ruby/object:Gem::Version - version: '0' + version: '2' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - - ">=" + - - "~>" - !ruby/object:Gem::Version - version: '0' + version: '2' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version - version: '3.6' + version: '3' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version - version: '3.6' + version: '3' description: Protect against typical web attacks, works with all Rack apps, including Rails. email: sinatr...@googlegroups.com @@ -69,6 +69,8 @@ - lib/rack/protection/base.rb - lib/rack/protection/content_security_policy.rb - lib/rack/protection/cookie_tossing.rb +- lib/rack/protection/encrypted_cookie.rb +- lib/rack/protection/encryptor.rb - lib/rack/protection/escaped_params.rb - lib/rack/protection/form_token.rb - lib/rack/protection/frame_options.rb @@ -83,6 +85,7 @@ - lib/rack/protection/strict_transport.rb - lib/rack/protection/version.rb - lib/rack/protection/xss_header.rb +- lib/rack_protection.rb - rack-protection.gemspec homepage: http://sinatrarb.com/protection/ licenses: @@ -91,6 +94,7 @@ source_code_uri: https://github.com/sinatra/sinatra/tree/master/rack-protection homepage_uri: http://sinatrarb.com/protection/ documentation_uri: https://www.rubydoc.info/gems/rack-protection + rubygems_mfa_required: 'true' post_install_message: rdoc_options: [] require_paths: @@ -99,14 +103,14 @@ requirements: - - ">=" - !ruby/object:Gem::Version - version: '0' + version: 2.6.0 required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] -rubygems_version: 3.0.3.1 +rubygems_version: 3.2.3 signing_key: specification_version: 4 summary: Protect against typical web attacks, works with all Rack apps, including diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/rack-protection.gemspec new/rack-protection.gemspec --- old/rack-protection.gemspec 2022-07-23 23:10:39.000000000 +0200 +++ new/rack-protection.gemspec 2022-10-01 19:22:38.000000000 +0200 @@ -1,40 +1,45 @@ -version = File.read(File.expand_path("../../VERSION", __FILE__)).strip +# frozen_string_literal: true + +version = File.read(File.expand_path('../VERSION', __dir__)).strip Gem::Specification.new do |s| # general infos - s.name = "rack-protection" + s.name = 'rack-protection' s.version = version - s.description = "Protect against typical web attacks, works with all Rack apps, including Rails." - s.homepage = "http://sinatrarb.com/protection/" + s.description = 'Protect against typical web attacks, works with all Rack apps, including Rails.' + s.homepage = 'http://sinatrarb.com/protection/' s.summary = s.description s.license = 'MIT' - s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"] - s.email = "sinatr...@googlegroups.com" - s.files = Dir["lib/**/*.rb"] + [ - "License", - "README.md", - "Rakefile", - "Gemfile", - "rack-protection.gemspec" + s.authors = ['https://github.com/sinatra/sinatra/graphs/contributors'] + s.email = 'sinatr...@googlegroups.com' + s.files = Dir['lib/**/*.rb'] + [ + 'License', + 'README.md', + 'Rakefile', + 'Gemfile', + 'rack-protection.gemspec' ] - if s.respond_to?(:metadata) - s.metadata = { - 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/rack-protection', - 'homepage_uri' => 'http://sinatrarb.com/protection/', - 'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection' - } - else - raise <<-EOF + unless s.respond_to?(:metadata) + raise <<-WARN RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: gem install rubygems-update update_rubygems: gem update --system -EOF + WARN end + s.metadata = { + 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/rack-protection', + 'homepage_uri' => 'http://sinatrarb.com/protection/', + 'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection', + 'rubygems_mfa_required' => 'true' + } + + s.required_ruby_version = '>= 2.6.0' + # dependencies - s.add_dependency "rack" - s.add_development_dependency "rack-test" - s.add_development_dependency "rspec", "~> 3.6" + s.add_dependency 'rack' + s.add_development_dependency 'rack-test', '~> 2' + s.add_development_dependency 'rspec', '~> 3' end