rack-protection 2.2.2 → 4.0.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: fa9d7fd8b6dd44cfd54fd833a1679efedfccdf76619d0b01e0aa2057f2e8b586
4
- data.tar.gz: f0541b4bf7e3cc865bdca886de807968787e90a26ca4fcd3ff3787496e64a9de
3
+ metadata.gz: 8762f8b7bc260c94e353c6c9eb9a24d3a0efe695cb5904f526a5a84094f567db
4
+ data.tar.gz: d3c7e05bf8bfea7c6b077025f330429f386b416bccce5ebfab2d91d0513e437b
5
5
  SHA512:
6
- metadata.gz: 89f4d7bd2b9ee4a1e51fb9a7d9699f0d1d3881a82bb3b8813527f6ce8cce96274462fd502e8df4d5f6b21cbf2d79da9bd69bff6a4e6e7e289e69aa7cb4e18893
7
- data.tar.gz: 381cdab10dffb59181caef1a80b46e9d584e1f20fa602843f1649164bbd16d44cbfd5ba5aef2ebd0a447e52fad7437644d29188e98b9998a19a4377abbc0051f
6
+ metadata.gz: 48b9fffade45349249d1122c3acc8618f2cb8ca05cde4ce1959b063899faad7c5c433bf6481518beedd0a0dd2ff28c878b1f5898fc3f21629ea39233fa747dc6
7
+ data.tar.gz: ec3176d39b37dfdd97b4d230f98251681a93c6dddcb17db4659c998d8f296dc9fc97808a9b732bbf055d0d2b0504f9696f541b6d6864983b214df94ceff636a5
data/Gemfile CHANGED
@@ -1,13 +1,13 @@
1
- source "https://rubygems.org"
2
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gemspec
3
5
 
4
6
  gem 'rake'
7
+ gem 'rspec', '~> 3'
8
+ gem 'rack-test'
5
9
 
6
10
  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'
11
+ rack_version = nil if rack_version.empty? || (rack_version == 'stable')
12
+ rack_version = { github: 'rack/rack' } if rack_version == 'head'
9
13
  gem 'rack', rack_version
10
-
11
- gem 'sinatra', path: '..'
12
-
13
- gemspec
data/README.md CHANGED
@@ -69,11 +69,12 @@ Prevented by:
69
69
 
70
70
  Prevented by:
71
71
 
72
- * [`Rack::Protection::SessionHijacking`][session-hijacking]
72
+ * [`Rack::Protection::SessionHijacking`][session-hijacking] (not included by `use Rack::Protection`)
73
73
 
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
  #
@@ -46,6 +51,7 @@ module Rack
46
51
  # Here is <tt>server.rb</tt>:
47
52
  #
48
53
  # require 'rack/protection'
54
+ # require 'rack/session'
49
55
  #
50
56
  # app = Rack::Builder.app do
51
57
  # use Rack::Session::Cookie, secret: 'secret'
@@ -92,12 +98,12 @@ module Rack
92
98
  class AuthenticityToken < Base
93
99
  TOKEN_LENGTH = 32
94
100
 
95
- default_options :authenticity_param => 'authenticity_token',
96
- :key => :csrf,
97
- :allow_if => nil
101
+ default_options authenticity_param: 'authenticity_token',
102
+ key: :csrf,
103
+ allow_if: nil
98
104
 
99
105
  def self.token(session, path: nil, method: :post)
100
- self.new(nil).mask_authenticity_token(session, path: path, method: method)
106
+ new(nil).mask_authenticity_token(session, path: path, method: method)
101
107
  end
102
108
 
103
109
  def self.random_token
@@ -111,8 +117,8 @@ module Rack
111
117
  safe?(env) ||
112
118
  valid_token?(env, env['HTTP_X_CSRF_TOKEN']) ||
113
119
  valid_token?(env, Request.new(env).params[options[:authenticity_param]]) ||
114
- ( options[:allow_if] && options[:allow_if].call(env) )
115
- rescue
120
+ options[:allow_if]&.call(env)
121
+ rescue StandardError
116
122
  false
117
123
  end
118
124
 
@@ -120,10 +126,10 @@ module Rack
120
126
  set_token(session)
121
127
 
122
128
  token = if path && method
123
- per_form_token(session, path, method)
124
- else
125
- global_token(session)
126
- end
129
+ per_form_token(session, path, method)
130
+ else
131
+ global_token(session)
132
+ end
127
133
 
128
134
  mask_token(token)
129
135
  end
@@ -140,7 +146,7 @@ module Rack
140
146
  # Checks the client's masked token to see if it matches the
141
147
  # session token.
142
148
  def valid_token?(env, token)
143
- return false if token.nil? || token.empty?
149
+ return false if token.nil? || !token.is_a?(String) || token.empty?
144
150
 
145
151
  session = session(env)
146
152
 
@@ -182,7 +188,7 @@ module Rack
182
188
  # value and decrypt it
183
189
  token_length = masked_token.length / 2
184
190
  one_time_pad = masked_token[0...token_length]
185
- encrypted_token = masked_token[token_length..-1]
191
+ encrypted_token = masked_token[token_length..]
186
192
  xor_byte_strings(one_time_pad, encrypted_token)
187
193
  end
188
194
 
@@ -204,8 +210,7 @@ module Rack
204
210
 
205
211
  def compare_with_per_form_token(token, session, request)
206
212
  secure_compare(token,
207
- per_form_token(session, request.path.chomp('/'), request.request_method)
208
- )
213
+ per_form_token(session, request.path.chomp('/'), request.request_method))
209
214
  end
210
215
 
211
216
  def real_token(session)
@@ -230,7 +235,7 @@ module Rack
230
235
 
231
236
  def token_hmac(session, identifier)
232
237
  OpenSSL::HMAC.digest(
233
- OpenSSL::Digest::SHA256.new,
238
+ OpenSSL::Digest.new('SHA256'),
234
239
  real_token(session),
235
240
  identifier
236
241
  )
@@ -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
@@ -25,7 +26,7 @@ module Rack
25
26
  # https://scotthelme.co.uk/csp-cheat-sheet/
26
27
  # http://www.html5rocks.com/en/tutorials/security/content-security-policy/
27
28
  #
28
- # Sets the 'Content-Security-Policy[-Report-Only]' header.
29
+ # Sets the 'content-security-policy[-report-only]' header.
29
30
  #
30
31
  # Options: ContentSecurityPolicy configuration is a complex topic with
31
32
  # several levels of support that has evolved over time.
@@ -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 = []
@@ -70,7 +71,7 @@ module Rack
70
71
 
71
72
  def call(env)
72
73
  status, headers, body = @app.call(env)
73
- header = options[:report_only] ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
74
+ header = options[:report_only] ? 'content-security-policy-report-only' : 'content-security-policy'
74
75
  headers[header] ||= csp_policy if html? headers
75
76
  [status, headers, body]
76
77
  end
@@ -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
@@ -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
@@ -29,7 +31,7 @@ module Rack
29
31
 
30
32
  def call(env)
31
33
  status, headers, body = @app.call(env)
32
- headers['X-Frame-Options'] ||= frame_options if html? headers
34
+ headers['x-frame-options'] ||= frame_options if html? headers
33
35
  [status, headers, body]
34
36
  end
35
37
  end
@@ -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
@@ -13,9 +15,11 @@ module Rack
13
15
 
14
16
  def accepts?(env)
15
17
  return true unless env.include? 'HTTP_X_FORWARDED_FOR'
16
- ips = env['HTTP_X_FORWARDED_FOR'].split(/\s*,\s*/)
17
- return false if env.include? 'HTTP_CLIENT_IP' and not ips.include? env['HTTP_CLIENT_IP']
18
- return false if env.include? 'HTTP_X_REAL_IP' and not ips.include? env['HTTP_X_REAL_IP']
18
+
19
+ ips = env['HTTP_X_FORWARDED_FOR'].split(',').map(&:strip)
20
+ return false if env.include?('HTTP_CLIENT_IP') && (!ips.include? env['HTTP_CLIENT_IP'])
21
+ return false if env.include?('HTTP_X_REAL_IP') && (!ips.include? env['HTTP_X_REAL_IP'])
22
+
19
23
  true
20
24
  end
21
25
  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
  #
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,11 +15,11 @@ 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)
20
- headers['Referrer-Policy'] ||= options[:referrer_policy]
22
+ headers['referrer-policy'] ||= options[:referrer_policy]
21
23
  [status, headers, body]
22
24
  end
23
25
  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
@@ -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
@@ -31,7 +33,7 @@ module Rack
31
33
 
32
34
  def call(env)
33
35
  status, headers, body = @app.call(env)
34
- headers['Strict-Transport-Security'] ||= strict_transport
36
+ headers['strict-transport-security'] ||= strict_transport
35
37
  [status, headers, body]
36
38
  end
37
39
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  module Protection
3
- VERSION = '2.2.1'
5
+ VERSION = '4.0.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,12 +14,12 @@ 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)
19
- headers['X-XSS-Protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers
20
- headers['X-Content-Type-Options'] ||= 'nosniff' if options[:nosniff]
21
+ headers['x-xss-protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers
22
+ headers['x-content-type-options'] ||= 'nosniff' if options[:nosniff]
21
23
  [status, headers, body]
22
24
  end
23
25
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/protection/version'
2
4
  require 'rack'
3
5
 
@@ -22,12 +24,11 @@ module Rack
22
24
  autoload :XSSHeader, 'rack/protection/xss_header'
23
25
 
24
26
  def self.new(app, options = {})
25
- # does not include: RemoteReferrer, AuthenticityToken and FormToken
26
27
  except = Array options[:except]
27
28
  use_these = Array options[:use]
28
29
 
29
30
  if options.fetch(:without_session, false)
30
- except += [:session_hijacking, :remote_token]
31
+ except += %i[remote_token]
31
32
  end
32
33
 
33
34
  Rack::Builder.new do
@@ -39,6 +40,7 @@ module Rack
39
40
  use ::Rack::Protection::FormToken, options if use_these.include? :form_token
40
41
  use ::Rack::Protection::ReferrerPolicy, options if use_these.include? :referrer_policy
41
42
  use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer
43
+ use ::Rack::Protection::SessionHijacking, options if use_these.include? :session_hijacking
42
44
  use ::Rack::Protection::StrictTransport, options if use_these.include? :strict_transport
43
45
 
44
46
  # On by default, unless skipped
@@ -48,7 +50,6 @@ module Rack
48
50
  use ::Rack::Protection::JsonCsrf, options unless except.include? :json_csrf
49
51
  use ::Rack::Protection::PathTraversal, options unless except.include? :path_traversal
50
52
  use ::Rack::Protection::RemoteToken, options unless except.include? :remote_token
51
- use ::Rack::Protection::SessionHijacking, options unless except.include? :session_hijacking
52
53
  use ::Rack::Protection::XSSHeader, options unless except.include? :xss_header
53
54
  run app
54
55
  end.to_app
@@ -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.7.8'
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', '>= 3.0.0', '< 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.2
4
+ version: 4.0.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-07-23 00:00:00.000000000 Z
11
+ date: 2024-01-19 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
33
+ version: 3.0.0
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '4'
37
+ type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
41
  - - ">="
39
42
  - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
43
+ version: 3.0.0
44
+ - - "<"
46
45
  - !ruby/object:Gem::Version
47
- version: '3.6'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.6'
46
+ version: '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: []
@@ -83,15 +75,17 @@ files:
83
75
  - lib/rack/protection/strict_transport.rb
84
76
  - lib/rack/protection/version.rb
85
77
  - lib/rack/protection/xss_header.rb
78
+ - lib/rack_protection.rb
86
79
  - rack-protection.gemspec
87
- homepage: http://sinatrarb.com/protection/
80
+ homepage: https://sinatrarb.com/protection/
88
81
  licenses:
89
82
  - MIT
90
83
  metadata:
91
- source_code_uri: https://github.com/sinatra/sinatra/tree/master/rack-protection
84
+ source_code_uri: https://github.com/sinatra/sinatra/tree/main/rack-protection
92
85
  homepage_uri: http://sinatrarb.com/protection/
93
86
  documentation_uri: https://www.rubydoc.info/gems/rack-protection
94
- post_install_message:
87
+ rubygems_mfa_required: 'true'
88
+ post_install_message:
95
89
  rdoc_options: []
96
90
  require_paths:
97
91
  - lib
@@ -99,15 +93,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
99
93
  requirements:
100
94
  - - ">="
101
95
  - !ruby/object:Gem::Version
102
- version: '0'
96
+ version: 2.7.8
103
97
  required_rubygems_version: !ruby/object:Gem::Requirement
104
98
  requirements:
105
99
  - - ">="
106
100
  - !ruby/object:Gem::Version
107
101
  version: '0'
108
102
  requirements: []
109
- rubygems_version: 3.0.3.1
110
- signing_key:
103
+ rubygems_version: 3.5.3
104
+ signing_key:
111
105
  specification_version: 4
112
106
  summary: Protect against typical web attacks, works with all Rack apps, including
113
107
  Rails.