rack-protection 2.2.4 → 3.2.0

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: 329a83327187635894cd3f8530c6e96b6cac29e8e28b1a8db0f1339cffa07a23
4
- data.tar.gz: da8bea5ddeab7426d74fe977b7856a78c10f397da94b5769f5f72b83d5e0a1b1
3
+ metadata.gz: 7640a15f8659807abd53474e7ce538a42e476e4bd99dc745f3b9b8c16161c008
4
+ data.tar.gz: '05468ec6c8113d3afce2df62221e4c866616999700c30ba3ef94a2705b11138b'
5
5
  SHA512:
6
- metadata.gz: 6e4e35ae58ac6f131cff279bf7e68770eb87253d40c518980e1b44abbd9fbc7deb8952618e92289631d3641dd627ffb57f1963562536e8164aba2357544f36d6
7
- data.tar.gz: 26b2c4e7413a6bf68386c474173ccad34ed7334064d4f262dfc0e04f546edc61b1de6ec95ca151544decbb1962957d85907734c38566b7e1b2501e6254d4956f
6
+ metadata.gz: eeaff5e584a8ee3be6c80dc92c67fcc95bdbb97b084509ed90ca9ad524598fba63690cfa372586edd27940cd609fa44210637e9c95fbf1191e1a5cc297f222ac
7
+ data.tar.gz: 26e2160d65b6015c7aaa52266b7241d15f645eb259d1371b864b3e2b6a3b1fbef841e62304bc8e39a83fab1a52ddb0c3455a51385a9a451c833cbed91b75d00a
data/Gemfile CHANGED
@@ -1,13 +1,16 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
  # encoding: utf-8
3
5
 
4
6
  gem 'rake'
7
+ gem 'rspec', '~> 3'
5
8
 
6
9
  rack_version = ENV['rack'].to_s
7
- rack_version = nil if rack_version.empty? or rack_version == 'stable'
8
- rack_version = {:github => 'rack/rack'} if rack_version == 'master'
10
+ rack_version = nil if rack_version.empty? || (rack_version == 'stable')
11
+ rack_version = { github: 'rack/rack' } if rack_version == 'head'
9
12
  gem 'rack', rack_version
10
13
 
11
- gem 'sinatra', path: '..'
12
-
13
14
  gemspec
15
+
16
+ gem 'rack-test'
data/README.md CHANGED
@@ -74,6 +74,7 @@ Prevented by:
74
74
  ## Cookie Tossing
75
75
 
76
76
  Prevented by:
77
+
77
78
  * [`Rack::Protection::CookieTossing`][cookie-tossing] (not included by `use Rack::Protection`)
78
79
 
79
80
  ## IP Spoofing
@@ -95,6 +96,7 @@ Prevented by:
95
96
  # Instrumentation
96
97
 
97
98
  Instrumentation is enabled by passing in an instrumenter as an option.
99
+
98
100
  ```
99
101
  use Rack::Protection, instrumenter: ActiveSupport::Notifications
100
102
  ```
data/Rakefile CHANGED
@@ -1,53 +1,55 @@
1
- # encoding: utf-8
2
- $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
3
4
 
4
5
  begin
5
6
  require 'bundler'
6
7
  Bundler::GemHelper.install_tasks
7
8
  rescue LoadError => e
8
- $stderr.puts e
9
+ warn e
9
10
  end
10
11
 
11
- desc "run specs"
12
- task(:spec) { ruby '-S rspec spec' }
12
+ desc 'run specs'
13
+ task(:spec) { ruby '-S rspec' }
13
14
 
14
15
  namespace :doc do
15
16
  task :readmes do
16
17
  Dir.glob 'lib/rack/protection/*.rb' do |file|
17
18
  excluded_files = %w[lib/rack/protection/base.rb lib/rack/protection/version.rb]
18
19
  next if excluded_files.include?(file)
20
+
19
21
  doc = File.read(file)[/^ module Protection(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n")
20
- file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc"
21
- Dir.mkdir "doc" unless File.directory? "doc"
22
+ file = "doc/#{file[4..-4].tr('/_', '-')}.rdoc"
23
+ Dir.mkdir 'doc' unless File.directory? 'doc'
22
24
  puts "writing #{file}"
23
- File.open(file, "w") { |f| f << doc }
25
+ File.open(file, 'w') { |f| f << doc }
24
26
  end
25
27
  end
26
28
 
27
29
  task :index do
28
- doc = File.read("README.md")
29
- file = "doc/rack-protection-readme.md"
30
- Dir.mkdir "doc" unless File.directory? "doc"
30
+ doc = File.read('README.md')
31
+ file = 'doc/rack-protection-readme.md'
32
+ Dir.mkdir 'doc' unless File.directory? 'doc'
31
33
  puts "writing #{file}"
32
- File.open(file, "w") { |f| f << doc }
34
+ File.open(file, 'w') { |f| f << doc }
33
35
  end
34
36
 
35
- task :all => [:readmes, :index]
37
+ task all: %i[readmes index]
36
38
  end
37
39
 
38
- desc "generate documentation"
39
- task :doc => 'doc:all'
40
+ desc 'generate documentation'
41
+ task doc: 'doc:all'
40
42
 
41
- desc "generate gemspec"
43
+ desc 'generate gemspec'
42
44
  task 'rack-protection.gemspec' do
43
45
  require 'rack/protection/version'
44
46
  content = File.binread 'rack-protection.gemspec'
45
47
 
46
48
  # fetch data
47
49
  fields = {
48
- :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/),
49
- :email => ["mail@zzak.io", "konstantin.haase@gmail.com"],
50
- :files => %w(License README.md Rakefile Gemfile rack-protection.gemspec) + Dir['lib/**/*']
50
+ authors: `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/),
51
+ email: ['mail@zzak.io', 'konstantin.haase@gmail.com'],
52
+ files: %w[License README.md Rakefile Gemfile rack-protection.gemspec] + Dir['lib/**/*']
51
53
  }
52
54
 
53
55
  # insert data
@@ -67,6 +69,6 @@ task 'rack-protection.gemspec' do
67
69
  File.open('rack-protection.gemspec', 'w') { |f| f << content }
68
70
  end
69
71
 
70
- task :gemspec => 'rack-protection.gemspec'
71
- task :default => :spec
72
- task :test => :spec
72
+ task gemspec: 'rack-protection.gemspec'
73
+ task default: :spec
74
+ task test: :spec
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
  require 'securerandom'
3
5
  require 'openssl'
@@ -17,7 +19,10 @@ module Rack
17
19
  # It checks the <tt>X-CSRF-Token</tt> header and the <tt>POST</tt> form
18
20
  # data.
19
21
  #
20
- # Compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem.
22
+ # It is not OOTB-compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem.
23
+ # For that, the following patch needs to be applied:
24
+ #
25
+ # Rack::Protection::AuthenticityToken.default_options(key: "csrf.token", authenticity_param: "_csrf")
21
26
  #
22
27
  # == Options
23
28
  #
@@ -92,12 +97,12 @@ module Rack
92
97
  class AuthenticityToken < Base
93
98
  TOKEN_LENGTH = 32
94
99
 
95
- default_options :authenticity_param => 'authenticity_token',
96
- :key => :csrf,
97
- :allow_if => nil
100
+ default_options authenticity_param: 'authenticity_token',
101
+ key: :csrf,
102
+ allow_if: nil
98
103
 
99
104
  def self.token(session, path: nil, method: :post)
100
- self.new(nil).mask_authenticity_token(session, path: path, method: method)
105
+ new(nil).mask_authenticity_token(session, path: path, method: method)
101
106
  end
102
107
 
103
108
  def self.random_token
@@ -111,8 +116,8 @@ module Rack
111
116
  safe?(env) ||
112
117
  valid_token?(env, env['HTTP_X_CSRF_TOKEN']) ||
113
118
  valid_token?(env, Request.new(env).params[options[:authenticity_param]]) ||
114
- ( options[:allow_if] && options[:allow_if].call(env) )
115
- rescue
119
+ options[:allow_if]&.call(env)
120
+ rescue StandardError
116
121
  false
117
122
  end
118
123
 
@@ -120,10 +125,10 @@ module Rack
120
125
  set_token(session)
121
126
 
122
127
  token = if path && method
123
- per_form_token(session, path, method)
124
- else
125
- global_token(session)
126
- end
128
+ per_form_token(session, path, method)
129
+ else
130
+ global_token(session)
131
+ end
127
132
 
128
133
  mask_token(token)
129
134
  end
@@ -140,7 +145,7 @@ module Rack
140
145
  # Checks the client's masked token to see if it matches the
141
146
  # session token.
142
147
  def valid_token?(env, token)
143
- return false if token.nil? || token.empty?
148
+ return false if token.nil? || !token.is_a?(String) || token.empty?
144
149
 
145
150
  session = session(env)
146
151
 
@@ -182,7 +187,7 @@ module Rack
182
187
  # value and decrypt it
183
188
  token_length = masked_token.length / 2
184
189
  one_time_pad = masked_token[0...token_length]
185
- encrypted_token = masked_token[token_length..-1]
190
+ encrypted_token = masked_token[token_length..]
186
191
  xor_byte_strings(one_time_pad, encrypted_token)
187
192
  end
188
193
 
@@ -204,8 +209,7 @@ module Rack
204
209
 
205
210
  def compare_with_per_form_token(token, session, request)
206
211
  secure_compare(token,
207
- per_form_token(session, request.path.chomp('/'), request.request_method)
208
- )
212
+ per_form_token(session, request.path.chomp('/'), request.request_method))
209
213
  end
210
214
 
211
215
  def real_token(session)
@@ -230,7 +234,7 @@ module Rack
230
234
 
231
235
  def token_hmac(session, identifier)
232
236
  OpenSSL::HMAC.digest(
233
- OpenSSL::Digest::SHA256.new,
237
+ OpenSSL::Digest.new('SHA256'),
234
238
  real_token(session),
235
239
  identifier
236
240
  )
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
  require 'rack/utils'
3
5
  require 'digest'
@@ -8,12 +10,12 @@ module Rack
8
10
  module Protection
9
11
  class Base
10
12
  DEFAULT_OPTIONS = {
11
- :reaction => :default_reaction, :logging => true,
12
- :message => 'Forbidden', :encryptor => Digest::SHA1,
13
- :session_key => 'rack.session', :status => 403,
14
- :allow_empty_referrer => true,
15
- :report_key => "protection.failed",
16
- :html_types => %w[text/html application/xhtml text/xml application/xml]
13
+ reaction: :default_reaction, logging: true,
14
+ message: 'Forbidden', encryptor: Digest::SHA1,
15
+ session_key: 'rack.session', status: 403,
16
+ allow_empty_referrer: true,
17
+ report_key: 'protection.failed',
18
+ html_types: %w[text/html application/xhtml text/xml application/xml]
17
19
  }
18
20
 
19
21
  attr_reader :app, :options
@@ -31,7 +33,8 @@ module Rack
31
33
  end
32
34
 
33
35
  def initialize(app, options = {})
34
- @app, @options = app, default_options.merge(options)
36
+ @app = app
37
+ @options = default_options.merge(options)
35
38
  end
36
39
 
37
40
  def safe?(env)
@@ -52,24 +55,26 @@ module Rack
52
55
 
53
56
  def react(env)
54
57
  result = send(options[:reaction], env)
55
- result if Array === result and result.size == 3
58
+ result if (Array === result) && (result.size == 3)
56
59
  end
57
60
 
58
61
  def warn(env, message)
59
62
  return unless options[:logging]
63
+
60
64
  l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors'])
61
65
  l.warn(message)
62
66
  end
63
67
 
64
68
  def instrument(env)
65
- return unless i = options[:instrumenter]
69
+ return unless (i = options[:instrumenter])
70
+
66
71
  env['rack.protection.attack'] = self.class.name.split('::').last.downcase
67
72
  i.instrument('rack.protection', env)
68
73
  end
69
74
 
70
75
  def deny(env)
71
76
  warn env, "attack prevented by #{self.class}"
72
- [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]]
77
+ [options[:status], { 'Content-Type' => 'text/plain' }, [options[:message]]]
73
78
  end
74
79
 
75
80
  def report(env)
@@ -83,16 +88,24 @@ module Rack
83
88
 
84
89
  def session(env)
85
90
  return env[options[:session_key]] if session? env
86
- fail "you need to set up a session middleware *before* #{self.class}"
91
+
92
+ raise "you need to set up a session middleware *before* #{self.class}"
87
93
  end
88
94
 
89
95
  def drop_session(env)
90
- session(env).clear if session? env
96
+ return unless session? env
97
+
98
+ session(env).clear
99
+
100
+ return if ["1", "true"].include?(ENV["RACK_PROTECTION_SILENCE_DROP_SESSION_WARNING"])
101
+
102
+ warn env, "session dropped by #{self.class}"
91
103
  end
92
104
 
93
105
  def referrer(env)
94
106
  ref = env['HTTP_REFERER'].to_s
95
- return if !options[:allow_empty_referrer] and ref.empty?
107
+ return if !options[:allow_empty_referrer] && ref.empty?
108
+
96
109
  URI.parse(ref).host || Request.new(env).host
97
110
  rescue URI::InvalidURIError
98
111
  end
@@ -102,7 +115,7 @@ module Rack
102
115
  end
103
116
 
104
117
  def random_string(secure = defined? SecureRandom)
105
- secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1)
118
+ secure ? SecureRandom.hex(16) : '%032x' % rand((2**128) - 1)
106
119
  rescue NotImplementedError
107
120
  random_string false
108
121
  end
@@ -118,8 +131,9 @@ module Rack
118
131
  alias default_reaction deny
119
132
 
120
133
  def html?(headers)
121
- return false unless header = headers.detect { |k,v| k.downcase == 'content-type' }
122
- options[:html_types].include? header.last[/^\w+\/\w+/]
134
+ return false unless (header = headers.detect { |k, _v| k.downcase == 'content-type' })
135
+
136
+ options[:html_types].include? header.last[%r{^\w+/\w+}]
123
137
  end
124
138
  end
125
139
  end
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  require 'rack/protection'
3
4
 
4
5
  module Rack
@@ -38,16 +39,16 @@ module Rack
38
39
  class ContentSecurityPolicy < Base
39
40
  default_options default_src: "'self'", report_only: false
40
41
 
41
- DIRECTIVES = %i(base_uri child_src connect_src default_src
42
+ DIRECTIVES = %i[base_uri child_src connect_src default_src
42
43
  font_src form_action frame_ancestors frame_src
43
44
  img_src manifest_src media_src object_src
44
45
  plugin_types referrer reflected_xss report_to
45
46
  report_uri require_sri_for sandbox script_src
46
47
  style_src worker_src webrtc_src navigate_to
47
- prefetch_src).freeze
48
+ prefetch_src].freeze
48
49
 
49
- NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener
50
- upgrade_insecure_requests).freeze
50
+ NO_ARG_DIRECTIVES = %i[block_all_mixed_content disown_opener
51
+ upgrade_insecure_requests].freeze
51
52
 
52
53
  def csp_policy
53
54
  directives = []
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
  require 'pathname'
3
5
 
@@ -29,9 +31,8 @@ module Rack
29
31
  cookie_header = env['HTTP_COOKIE']
30
32
  cookies = Rack::Utils.parse_query(cookie_header, ';,') { |s| s }
31
33
  cookies.each do |k, v|
32
- if k == session_key && Array(v).size > 1
33
- bad_cookies << k
34
- elsif k != session_key && Rack::Utils.unescape(k) == session_key
34
+ if (k == session_key && Array(v).size > 1) ||
35
+ (k != session_key && Rack::Utils.unescape(k) == session_key)
35
36
  bad_cookies << k
36
37
  end
37
38
  end
@@ -40,6 +41,7 @@ module Rack
40
41
 
41
42
  def remove_bad_cookies(request, response)
42
43
  return if bad_cookies.empty?
44
+
43
45
  paths = cookie_paths(request.path)
44
46
  bad_cookies.each do |name|
45
47
  paths.each { |path| response.set_cookie name, empty_cookie(request.host, path) }
@@ -49,7 +51,7 @@ module Rack
49
51
  def redirect(env)
50
52
  request = Request.new(env)
51
53
  warn env, "attack prevented by #{self.class}"
52
- [302, {'Content-Type' => 'text/html', 'Location' => request.path}, []]
54
+ [302, { 'Content-Type' => 'text/html', 'Location' => request.path }, []]
53
55
  end
54
56
 
55
57
  def bad_cookies
@@ -64,7 +66,7 @@ module Rack
64
66
  end
65
67
 
66
68
  def empty_cookie(host, path)
67
- {:value => '', :domain => host, :path => path, :expires => Time.at(0)}
69
+ { value: '', domain: host, path: path, expires: Time.at(0) }
68
70
  end
69
71
 
70
72
  def session_key
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'zlib'
5
+ require 'json'
6
+ require 'rack/request'
7
+ require 'rack/response'
8
+ require 'rack/session/abstract/id'
9
+
10
+ module Rack
11
+ module Protection
12
+ # Rack::Protection::EncryptedCookie provides simple cookie based session management.
13
+ # By default, the session is a Ruby Hash stored as base64 encoded marshalled
14
+ # data set to :key (default: rack.session). The object that encodes the
15
+ # session data is configurable and must respond to +encode+ and +decode+.
16
+ # Both methods must take a string and return a string.
17
+ #
18
+ # When the secret key is set, cookie data is checked for data integrity.
19
+ # The old_secret key is also accepted and allows graceful secret rotation.
20
+ # A legacy_hmac_secret is also accepted and is used to upgrade existing
21
+ # sessions to the new encryption scheme.
22
+ #
23
+ # There is also a legacy_hmac_coder option which can be set if a non-default
24
+ # coder was used for legacy session cookies.
25
+ #
26
+ # Example:
27
+ #
28
+ # use Rack::Protection::EncryptedCookie,
29
+ # :key => 'rack.session',
30
+ # :domain => 'foo.com',
31
+ # :path => '/',
32
+ # :expire_after => 2592000,
33
+ # :secret => 'change_me',
34
+ # :old_secret => 'old_secret'
35
+ #
36
+ # All parameters are optional.
37
+ #
38
+ # Example using legacy HMAC options
39
+ #
40
+ # Rack::Protection:EncryptedCookie.new(application, {
41
+ # # The secret used for legacy HMAC cookies
42
+ # legacy_hmac_secret: 'legacy secret',
43
+ # # legacy_hmac_coder will default to Rack::Protection::EncryptedCookie::Base64::Marshal
44
+ # legacy_hmac_coder: Rack::Protection::EncryptedCookie::Identity.new,
45
+ # # legacy_hmac will default to OpenSSL::Digest::SHA1
46
+ # legacy_hmac: OpenSSL::Digest::SHA256
47
+ # })
48
+ #
49
+ # Example of a cookie with no encoding:
50
+ #
51
+ # Rack::Protection::EncryptedCookie.new(application, {
52
+ # :coder => Rack::Protection::EncryptedCookie::Identity.new
53
+ # })
54
+ #
55
+ # Example of a cookie with custom encoding:
56
+ #
57
+ # Rack::Protection::EncryptedCookie.new(application, {
58
+ # :coder => Class.new {
59
+ # def encode(str); str.reverse; end
60
+ # def decode(str); str.reverse; end
61
+ # }.new
62
+ # })
63
+ #
64
+ class EncryptedCookie < Rack::Session::Abstract::Persisted
65
+ # Encode session cookies as Base64
66
+ class Base64
67
+ def encode(str)
68
+ [str].pack('m0')
69
+ end
70
+
71
+ def decode(str)
72
+ str.unpack1('m')
73
+ end
74
+
75
+ # Encode session cookies as Marshaled Base64 data
76
+ class Marshal < Base64
77
+ def encode(str)
78
+ super(::Marshal.dump(str))
79
+ end
80
+
81
+ def decode(str)
82
+ return unless str
83
+
84
+ begin
85
+ ::Marshal.load(super(str))
86
+ rescue StandardError
87
+ nil
88
+ end
89
+ end
90
+ end
91
+
92
+ # N.B. Unlike other encoding methods, the contained objects must be a
93
+ # valid JSON composite type, either a Hash or an Array.
94
+ class JSON < Base64
95
+ def encode(obj)
96
+ super(::JSON.dump(obj))
97
+ end
98
+
99
+ def decode(str)
100
+ return unless str
101
+
102
+ begin
103
+ ::JSON.parse(super(str))
104
+ rescue StandardError
105
+ nil
106
+ end
107
+ end
108
+ end
109
+
110
+ class ZipJSON < Base64
111
+ def encode(obj)
112
+ super(Zlib::Deflate.deflate(::JSON.dump(obj)))
113
+ end
114
+
115
+ def decode(str)
116
+ return unless str
117
+
118
+ ::JSON.parse(Zlib::Inflate.inflate(super(str)))
119
+ rescue StandardError
120
+ nil
121
+ end
122
+ end
123
+ end
124
+
125
+ # Use no encoding for session cookies
126
+ class Identity
127
+ def encode(str); str; end
128
+ def decode(str); str; end
129
+ end
130
+
131
+ class Marshal
132
+ def encode(str)
133
+ ::Marshal.dump(str)
134
+ end
135
+
136
+ def decode(str)
137
+ ::Marshal.load(str) if str
138
+ end
139
+ end
140
+
141
+ attr_reader :coder
142
+
143
+ def initialize(app, options = {})
144
+ # Assume keys are hex strings and convert them to raw byte strings for
145
+ # actual key material
146
+ @secrets = options.values_at(:secret, :old_secret).compact.map do |secret|
147
+ [secret].pack('H*')
148
+ end
149
+
150
+ warn <<-MSG unless secure?(options)
151
+ SECURITY WARNING: No secret option provided to Rack::Protection::EncryptedCookie.
152
+ This poses a security threat. It is strongly recommended that you
153
+ provide a secret to prevent exploits that may be possible from crafted
154
+ cookies. This will not be supported in future versions of Rack, and
155
+ future versions will even invalidate your existing user cookies.
156
+
157
+ Called from: #{caller[0]}.
158
+ MSG
159
+
160
+ warn <<-MSG if @secrets.first && @secrets.first.length < 32
161
+ SECURITY WARNING: Your secret is not long enough. It must be at least
162
+ 32 bytes long and securely random. To generate such a key for use
163
+ you can run the following command:
164
+
165
+ ruby -rsecurerandom -e 'p SecureRandom.hex(32)'
166
+
167
+ Called from: #{caller[0]}.
168
+ MSG
169
+
170
+ if options.key?(:legacy_hmac_secret)
171
+ @legacy_hmac = options.fetch(:legacy_hmac, OpenSSL::Digest::SHA1)
172
+
173
+ # Multiply the :digest_length: by 2 because this value is the length of
174
+ # the digest in bytes but session digest strings are encoded as hex
175
+ # strings
176
+ @legacy_hmac_length = @legacy_hmac.new.digest_length * 2
177
+ @legacy_hmac_secret = options[:legacy_hmac_secret]
178
+ @legacy_hmac_coder = (options[:legacy_hmac_coder] ||= Base64::Marshal.new)
179
+ else
180
+ @legacy_hmac = false
181
+ end
182
+
183
+ # If encryption is used we can just use a default Marshal encoder
184
+ # without Base64 encoding the results.
185
+ #
186
+ # If no encryption is used, rely on the previous default (Base64::Marshal)
187
+ @coder = (options[:coder] ||= (@secrets.any? ? Marshal.new : Base64::Marshal.new))
188
+
189
+ super(app, options.merge!(cookie_only: true))
190
+ end
191
+
192
+ private
193
+
194
+ def find_session(req, _sid)
195
+ data = unpacked_cookie_data(req)
196
+ data = persistent_session_id!(data)
197
+ [data['session_id'], data]
198
+ end
199
+
200
+ def extract_session_id(request)
201
+ unpacked_cookie_data(request)['session_id']
202
+ end
203
+
204
+ def unpacked_cookie_data(request)
205
+ request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
206
+ session_data = cookie_data = request.cookies[@key]
207
+
208
+ # Try to decrypt with the first secret, if that returns nil, try
209
+ # with old_secret
210
+ unless @secrets.empty?
211
+ session_data = Rack::Protection::Encryptor.decrypt_message(cookie_data, @secrets.first)
212
+ session_data ||= Rack::Protection::Encryptor.decrypt_message(cookie_data, @secrets[1]) if @secrets.size > 1
213
+ end
214
+
215
+ # If session_data is still nil, are there is a legacy HMAC
216
+ # configured, try verify and parse the cookie that way
217
+ if !session_data && @legacy_hmac
218
+ digest = cookie_data.slice!(-@legacy_hmac_length..-1)
219
+ cookie_data.slice!(-2..-1) # remove double dash
220
+ session_data = cookie_data if digest_match?(cookie_data, digest)
221
+
222
+ # Decode using legacy HMAC decoder
223
+ request.set_header(k, @legacy_hmac_coder.decode(session_data) || {})
224
+ else
225
+ request.set_header(k, coder.decode(session_data) || {})
226
+ end
227
+ end
228
+ end
229
+
230
+ def persistent_session_id!(data, sid = nil)
231
+ data ||= {}
232
+ data['session_id'] ||= sid || generate_sid
233
+ data
234
+ end
235
+
236
+ def write_session(req, session_id, session, _options)
237
+ session = session.merge('session_id' => session_id)
238
+ session_data = coder.encode(session)
239
+
240
+ unless @secrets.empty?
241
+ session_data = Rack::Protection::Encryptor.encrypt_message(session_data, @secrets.first)
242
+ end
243
+
244
+ if session_data.size > (4096 - @key.size)
245
+ req.get_header(RACK_ERRORS).puts('Warning! Rack::Protection::EncryptedCookie data size exceeds 4K.')
246
+ nil
247
+ else
248
+ session_data
249
+ end
250
+ end
251
+
252
+ def delete_session(_req, _session_id, options)
253
+ # Nothing to do here, data is in the client
254
+ generate_sid unless options[:drop]
255
+ end
256
+
257
+ def digest_match?(data, digest)
258
+ return false unless data && digest
259
+
260
+ Rack::Utils.secure_compare(digest, generate_hmac(data))
261
+ end
262
+
263
+ def generate_hmac(data)
264
+ OpenSSL::HMAC.hexdigest(@legacy_hmac.new, @legacy_hmac_secret, data)
265
+ end
266
+
267
+ def secure?(options)
268
+ @secrets.size >= 1 ||
269
+ (options[:coder] && options[:let_coder_handle_secure_encoding])
270
+ end
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module Rack
6
+ module Protection
7
+ module Encryptor
8
+ CIPHER = 'aes-256-gcm'
9
+ DELIMITER = '--'
10
+
11
+ def self.base64_encode(str)
12
+ [str].pack('m0')
13
+ end
14
+
15
+ def self.base64_decode(str)
16
+ str.unpack1('m0')
17
+ end
18
+
19
+ def self.encrypt_message(data, secret, auth_data = '')
20
+ raise ArgumentError, 'data cannot be nil' if data.nil?
21
+
22
+ cipher = OpenSSL::Cipher.new(CIPHER)
23
+ cipher.encrypt
24
+ cipher.key = secret[0, cipher.key_len]
25
+
26
+ # Rely on OpenSSL for the initialization vector
27
+ iv = cipher.random_iv
28
+
29
+ # This must be set to properly use AES GCM for the OpenSSL module
30
+ cipher.auth_data = auth_data
31
+
32
+ cipher_text = cipher.update(data)
33
+ cipher_text << cipher.final
34
+
35
+ "#{base64_encode cipher_text}#{DELIMITER}#{base64_encode iv}#{DELIMITER}#{base64_encode cipher.auth_tag}"
36
+ end
37
+
38
+ def self.decrypt_message(data, secret)
39
+ return unless data
40
+
41
+ cipher = OpenSSL::Cipher.new(CIPHER)
42
+ cipher_text, iv, auth_tag = data.split(DELIMITER, 3).map! { |v| base64_decode(v) }
43
+
44
+ # This check is from ActiveSupport::MessageEncryptor
45
+ # see: https://github.com/ruby/openssl/issues/63
46
+ return if auth_tag.nil? || auth_tag.bytes.length != 16
47
+
48
+ cipher.decrypt
49
+ cipher.key = secret[0, cipher.key_len]
50
+ cipher.iv = iv
51
+ cipher.auth_tag = auth_tag
52
+ cipher.auth_data = ''
53
+
54
+ decrypted_data = cipher.update(cipher_text)
55
+ decrypted_data << cipher.final
56
+ decrypted_data
57
+ rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
58
+ nil
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
  require 'rack/utils'
3
5
  require 'tempfile'
@@ -15,8 +17,7 @@ module Rack
15
17
  # More infos:: http://en.wikipedia.org/wiki/Cross-site_scripting
16
18
  #
17
19
  # Automatically escapes Rack::Request#params so they can be embedded in HTML
18
- # or JavaScript without any further issues. Calls +html_safe+ on the escaped
19
- # strings if defined, to avoid double-escaping in Rails.
20
+ # or JavaScript without any further issues.
20
21
  #
21
22
  # Options:
22
23
  # escape:: What escaping modes to use, should be Symbol or Array of Symbols.
@@ -29,8 +30,8 @@ module Rack
29
30
  public :escape_html
30
31
  end
31
32
 
32
- default_options :escape => :html,
33
- :escaper => defined?(EscapeUtils) ? EscapeUtils : self
33
+ default_options escape: :html,
34
+ escaper: defined?(EscapeUtils) ? EscapeUtils : self
34
35
 
35
36
  def initialize(*)
36
37
  super
@@ -41,15 +42,19 @@ module Rack
41
42
  @javascript = modes.include? :javascript
42
43
  @url = modes.include? :url
43
44
 
44
- if @javascript and not @escaper.respond_to? :escape_javascript
45
- fail("Use EscapeUtils for JavaScript escaping.")
46
- end
45
+ return unless @javascript && (!@escaper.respond_to? :escape_javascript)
46
+
47
+ raise('Use EscapeUtils for JavaScript escaping.')
47
48
  end
48
49
 
49
50
  def call(env)
50
51
  request = Request.new(env)
51
52
  get_was = handle(request.GET)
52
- post_was = handle(request.POST) rescue nil
53
+ post_was = begin
54
+ handle(request.POST)
55
+ rescue StandardError
56
+ nil
57
+ end
53
58
  app.call env
54
59
  ensure
55
60
  request.GET.replace get_was if get_was
@@ -68,13 +73,12 @@ module Rack
68
73
  when Array then object.map { |o| escape(o) }
69
74
  when String then escape_string(object)
70
75
  when Tempfile then object
71
- else nil
72
76
  end
73
77
  end
74
78
 
75
79
  def escape_hash(hash)
76
80
  hash = hash.dup
77
- hash.each { |k,v| hash[k] = escape(v) }
81
+ hash.each { |k, v| hash[k] = escape(v) }
78
82
  hash
79
83
  end
80
84
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -16,7 +18,7 @@ module Rack
16
18
  # Compatible with rack-csrf.
17
19
  class FormToken < AuthenticityToken
18
20
  def accepts?(env)
19
- env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super
21
+ env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' or super
20
22
  end
21
23
  end
22
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -17,7 +19,7 @@ module Rack
17
19
  # frame. Use :deny to forbid any embedding, :sameorigin
18
20
  # to allow embedding from the same origin (default).
19
21
  class FrameOptions < Base
20
- default_options :frame_options => :sameorigin
22
+ default_options frame_options: :sameorigin
21
23
 
22
24
  def frame_options
23
25
  @frame_options ||= begin
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -19,7 +21,7 @@ module Rack
19
21
  class HttpOrigin < Base
20
22
  DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
21
23
  default_reaction :deny
22
- default_options :allow_if => nil
24
+ default_options allow_if: nil
23
25
 
24
26
  def base_url(env)
25
27
  request = Rack::Request.new(env)
@@ -29,19 +31,13 @@ module Rack
29
31
 
30
32
  def accepts?(env)
31
33
  return true if safe? env
32
- return true unless origin = env['HTTP_ORIGIN']
34
+ return true unless (origin = env['HTTP_ORIGIN'])
33
35
  return true if base_url(env) == origin
34
- return true if options[:allow_if] && options[:allow_if].call(env)
35
-
36
- if options.key? :origin_whitelist
37
- warn env, "Rack::Protection origin_whitelist option is deprecated and will be removed, " \
38
- "use permitted_origins instead.\n"
39
- end
36
+ return true if options[:allow_if]&.call(env)
40
37
 
41
- permitted_origins = options[:permitted_origins] || options[:origin_whitelist]
38
+ permitted_origins = options[:permitted_origins]
42
39
  Array(permitted_origins).include? origin
43
40
  end
44
-
45
41
  end
46
42
  end
47
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -17,7 +19,7 @@ module Rack
17
19
  #
18
20
  # The `:allow_if` option can be set to a proc to use custom allow/deny logic.
19
21
  class JsonCsrf < Base
20
- default_options :allow_if => nil
22
+ default_options allow_if: nil
21
23
 
22
24
  alias react deny
23
25
 
@@ -36,8 +38,9 @@ module Rack
36
38
 
37
39
  def has_vector?(request, headers)
38
40
  return false if request.xhr?
39
- return false if options[:allow_if] && options[:allow_if].call(request.env)
40
- return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/
41
+ return false if options[:allow_if]&.call(request.env)
42
+ return false unless headers['Content-Type'].to_s.split(';', 2).first =~ %r{^\s*application/json\s*$}
43
+
41
44
  origin(request.env).nil? and referrer(request.env) != request.host
42
45
  end
43
46
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -11,11 +13,11 @@ module Rack
11
13
  # Thus <tt>GET /foo/%2e%2e%2fbar</tt> becomes <tt>GET /bar</tt>.
12
14
  class PathTraversal < Base
13
15
  def call(env)
14
- path_was = env["PATH_INFO"]
15
- env["PATH_INFO"] = cleanup path_was if path_was && !path_was.empty?
16
+ path_was = env['PATH_INFO']
17
+ env['PATH_INFO'] = cleanup path_was if path_was && !path_was.empty?
16
18
  app.call env
17
19
  ensure
18
- env["PATH_INFO"] = path_was
20
+ env['PATH_INFO'] = path_was
19
21
  end
20
22
 
21
23
  def cleanup(path)
@@ -29,12 +31,13 @@ module Rack
29
31
  unescaped = unescaped.gsub(backslash, slash)
30
32
 
31
33
  unescaped.split(slash).each do |part|
32
- next if part.empty? or part == dot
34
+ next if part.empty? || (part == dot)
35
+
33
36
  part == '..' ? parts.pop : parts << part
34
37
  end
35
38
 
36
39
  cleaned = slash + parts.join(slash)
37
- cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$}
40
+ cleaned << slash if parts.any? && unescaped =~ (%r{/\.{0,2}$})
38
41
  cleaned
39
42
  end
40
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -13,7 +15,7 @@ module Rack
13
15
  # Options:
14
16
  # referrer_policy:: The policy to use (default: 'strict-origin-when-cross-origin')
15
17
  class ReferrerPolicy < Base
16
- default_options :referrer_policy => 'strict-origin-when-cross-origin'
18
+ default_options referrer_policy: 'strict-origin-when-cross-origin'
17
19
 
18
20
  def call(env)
19
21
  status, headers, body = @app.call(env)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -13,23 +15,22 @@ module Rack
13
15
  # spoofed, too, this will not prevent determined hijacking attempts.
14
16
  class SessionHijacking < Base
15
17
  default_reaction :drop_session
16
- default_options :tracking_key => :tracking, :encrypt_tracking => true,
17
- :track => %w[HTTP_USER_AGENT]
18
+ default_options tracking_key: :tracking,
19
+ track: %w[HTTP_USER_AGENT]
18
20
 
19
21
  def accepts?(env)
20
22
  session = session env
21
23
  key = options[:tracking_key]
22
24
  if session.include? key
23
- session[key].all? { |k,v| v == encrypt(env[k]) }
25
+ session[key].all? { |k, v| v == encode(env[k]) }
24
26
  else
25
27
  session[key] = {}
26
- options[:track].each { |k| session[key][k] = encrypt(env[k]) }
28
+ options[:track].each { |k| session[key][k] = encode(env[k]) }
27
29
  end
28
30
  end
29
31
 
30
- def encrypt(value)
31
- value = value.to_s.downcase
32
- options[:encrypt_tracking] ? super(value) : value
32
+ def encode(value)
33
+ value.to_s.downcase
33
34
  end
34
35
  end
35
36
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -18,11 +20,11 @@ module Rack
18
20
  # preload:: Allow this domain to be included in browsers HSTS preload list. See https://hstspreload.appspot.com/
19
21
 
20
22
  class StrictTransport < Base
21
- default_options :max_age => 31_536_000, :include_subdomains => false, :preload => false
23
+ default_options max_age: 31_536_000, include_subdomains: false, preload: false
22
24
 
23
25
  def strict_transport
24
26
  @strict_transport ||= begin
25
- strict_transport = 'max-age=' + options[:max_age].to_s
27
+ strict_transport = "max-age=#{options[:max_age]}"
26
28
  strict_transport += '; includeSubDomains' if options[:include_subdomains]
27
29
  strict_transport += '; preload' if options[:preload]
28
30
  strict_transport.to_str
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Protection
3
- VERSION = '2.2.3'
5
+ VERSION = '3.2.0'
4
6
  end
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection'
2
4
 
3
5
  module Rack
@@ -12,7 +14,7 @@ module Rack
12
14
  # Options:
13
15
  # xss_mode:: How the browser should prevent the attack (default: :block)
14
16
  class XSSHeader < Base
15
- default_options :xss_mode => :block, :nosniff => true
17
+ default_options xss_mode: :block, nosniff: true
16
18
 
17
19
  def call(env)
18
20
  status, headers, body = @app.call(env)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection/version'
2
4
  require 'rack'
3
5
 
@@ -7,6 +9,8 @@ module Rack
7
9
  autoload :Base, 'rack/protection/base'
8
10
  autoload :CookieTossing, 'rack/protection/cookie_tossing'
9
11
  autoload :ContentSecurityPolicy, 'rack/protection/content_security_policy'
12
+ autoload :Encryptor, 'rack/protection/encryptor'
13
+ autoload :EncryptedCookie, 'rack/protection/encrypted_cookie'
10
14
  autoload :EscapedParams, 'rack/protection/escaped_params'
11
15
  autoload :FormToken, 'rack/protection/form_token'
12
16
  autoload :FrameOptions, 'rack/protection/frame_options'
@@ -27,7 +31,7 @@ module Rack
27
31
  use_these = Array options[:use]
28
32
 
29
33
  if options.fetch(:without_session, false)
30
- except += [:session_hijacking, :remote_token]
34
+ except += %i[session_hijacking remote_token]
31
35
  end
32
36
 
33
37
  Rack::Builder.new do
@@ -1 +1 @@
1
- require "rack/protection"
1
+ require 'rack/protection'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/protection'
@@ -1,40 +1,44 @@
1
- version = File.read(File.expand_path("../../VERSION", __FILE__)).strip
1
+ # frozen_string_literal: true
2
+
3
+ version = File.read(File.expand_path('../VERSION', __dir__)).strip
2
4
 
3
5
  Gem::Specification.new do |s|
4
6
  # general infos
5
- s.name = "rack-protection"
7
+ s.name = 'rack-protection'
6
8
  s.version = version
7
- s.description = "Protect against typical web attacks, works with all Rack apps, including Rails."
8
- s.homepage = "http://sinatrarb.com/protection/"
9
- s.summary = s.description
9
+ s.description = 'Protect against typical web attacks, works with all Rack apps, including Rails'
10
+ s.homepage = 'https://sinatrarb.com/protection/'
11
+ s.summary = "#{s.description}."
10
12
  s.license = 'MIT'
11
- s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"]
12
- s.email = "sinatrarb@googlegroups.com"
13
- s.files = Dir["lib/**/*.rb"] + [
14
- "License",
15
- "README.md",
16
- "Rakefile",
17
- "Gemfile",
18
- "rack-protection.gemspec"
13
+ s.authors = ['https://github.com/sinatra/sinatra/graphs/contributors']
14
+ s.email = 'sinatrarb@googlegroups.com'
15
+ s.files = Dir['lib/**/*.rb'] + [
16
+ 'License',
17
+ 'README.md',
18
+ 'Rakefile',
19
+ 'Gemfile',
20
+ 'rack-protection.gemspec'
19
21
  ]
20
22
 
21
- if s.respond_to?(:metadata)
22
- s.metadata = {
23
- 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/rack-protection',
24
- 'homepage_uri' => 'http://sinatrarb.com/protection/',
25
- 'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection'
26
- }
27
- else
28
- raise <<-EOF
23
+ unless s.respond_to?(:metadata)
24
+ raise <<-WARN
29
25
  RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running:
30
26
  gem install rubygems-update
31
27
  update_rubygems:
32
28
  gem update --system
33
- EOF
29
+ WARN
34
30
  end
35
31
 
32
+ s.metadata = {
33
+ 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/main/rack-protection',
34
+ 'homepage_uri' => 'http://sinatrarb.com/protection/',
35
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection',
36
+ 'rubygems_mfa_required' => 'true'
37
+ }
38
+
39
+ s.required_ruby_version = '>= 2.6.0'
40
+
36
41
  # dependencies
37
- s.add_dependency "rack"
38
- s.add_development_dependency "rack-test"
39
- s.add_development_dependency "rspec", "~> 3.6"
42
+ s.add_dependency 'base64', '>= 0.1.0'
43
+ s.add_dependency 'rack', '~> 2.2', '>= 2.2.4'
40
44
  end
metadata CHANGED
@@ -1,59 +1,51 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-protection
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.4
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/sinatra/sinatra/graphs/contributors
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-16 00:00:00.000000000 Z
11
+ date: 2023-12-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rack
14
+ name: base64
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 0.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 0.1.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: rack-test
28
+ name: rack
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
33
+ version: '2.2'
38
34
  - - ">="
39
35
  - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.6'
48
- type: :development
36
+ version: 2.2.4
37
+ type: :runtime
49
38
  prerelease: false
50
39
  version_requirements: !ruby/object:Gem::Requirement
51
40
  requirements:
52
41
  - - "~>"
53
42
  - !ruby/object:Gem::Version
54
- version: '3.6'
43
+ version: '2.2'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.2.4
55
47
  description: Protect against typical web attacks, works with all Rack apps, including
56
- Rails.
48
+ Rails
57
49
  email: sinatrarb@googlegroups.com
58
50
  executables: []
59
51
  extensions: []
@@ -69,6 +61,8 @@ files:
69
61
  - lib/rack/protection/base.rb
70
62
  - lib/rack/protection/content_security_policy.rb
71
63
  - lib/rack/protection/cookie_tossing.rb
64
+ - lib/rack/protection/encrypted_cookie.rb
65
+ - lib/rack/protection/encryptor.rb
72
66
  - lib/rack/protection/escaped_params.rb
73
67
  - lib/rack/protection/form_token.rb
74
68
  - lib/rack/protection/frame_options.rb
@@ -83,15 +77,17 @@ files:
83
77
  - lib/rack/protection/strict_transport.rb
84
78
  - lib/rack/protection/version.rb
85
79
  - lib/rack/protection/xss_header.rb
80
+ - lib/rack_protection.rb
86
81
  - rack-protection.gemspec
87
- homepage: http://sinatrarb.com/protection/
82
+ homepage: https://sinatrarb.com/protection/
88
83
  licenses:
89
84
  - MIT
90
85
  metadata:
91
- source_code_uri: https://github.com/sinatra/sinatra/tree/master/rack-protection
86
+ source_code_uri: https://github.com/sinatra/sinatra/tree/main/rack-protection
92
87
  homepage_uri: http://sinatrarb.com/protection/
93
88
  documentation_uri: https://www.rubydoc.info/gems/rack-protection
94
- post_install_message:
89
+ rubygems_mfa_required: 'true'
90
+ post_install_message:
95
91
  rdoc_options: []
96
92
  require_paths:
97
93
  - lib
@@ -99,16 +95,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
99
95
  requirements:
100
96
  - - ">="
101
97
  - !ruby/object:Gem::Version
102
- version: '0'
98
+ version: 2.6.0
103
99
  required_rubygems_version: !ruby/object:Gem::Requirement
104
100
  requirements:
105
101
  - - ">="
106
102
  - !ruby/object:Gem::Version
107
103
  version: '0'
108
104
  requirements: []
109
- rubyforge_project:
110
- rubygems_version: 2.7.6.3
111
- signing_key:
105
+ rubygems_version: 3.5.3
106
+ signing_key:
112
107
  specification_version: 4
113
108
  summary: Protect against typical web attacks, works with all Rack apps, including
114
109
  Rails.