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 +4 -4
- data/Gemfile +8 -5
- data/README.md +2 -0
- data/Rakefile +24 -22
- data/lib/rack/protection/authenticity_token.rb +20 -16
- data/lib/rack/protection/base.rb +30 -16
- data/lib/rack/protection/content_security_policy.rb +6 -5
- data/lib/rack/protection/cookie_tossing.rb +7 -5
- data/lib/rack/protection/encrypted_cookie.rb +273 -0
- data/lib/rack/protection/encryptor.rb +62 -0
- data/lib/rack/protection/escaped_params.rb +14 -10
- data/lib/rack/protection/form_token.rb +3 -1
- data/lib/rack/protection/frame_options.rb +3 -1
- data/lib/rack/protection/http_origin.rb +6 -10
- data/lib/rack/protection/ip_spoofing.rb +2 -0
- data/lib/rack/protection/json_csrf.rb +6 -3
- data/lib/rack/protection/path_traversal.rb +8 -5
- data/lib/rack/protection/referrer_policy.rb +3 -1
- data/lib/rack/protection/remote_referrer.rb +2 -0
- data/lib/rack/protection/remote_token.rb +2 -0
- data/lib/rack/protection/session_hijacking.rb +8 -7
- data/lib/rack/protection/strict_transport.rb +4 -2
- data/lib/rack/protection/version.rb +3 -1
- data/lib/rack/protection/xss_header.rb +3 -1
- data/lib/rack/protection.rb +5 -1
- data/lib/rack-protection.rb +1 -1
- data/lib/rack_protection.rb +3 -0
- data/rack-protection.gemspec +29 -25
- metadata +26 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7640a15f8659807abd53474e7ce538a42e476e4bd99dc745f3b9b8c16161c008
|
4
|
+
data.tar.gz: '05468ec6c8113d3afce2df62221e4c866616999700c30ba3ef94a2705b11138b'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eeaff5e584a8ee3be6c80dc92c67fcc95bdbb97b084509ed90ca9ad524598fba63690cfa372586edd27940cd609fa44210637e9c95fbf1191e1a5cc297f222ac
|
7
|
+
data.tar.gz: 26e2160d65b6015c7aaa52266b7241d15f645eb259d1371b864b3e2b6a3b1fbef841e62304bc8e39a83fab1a52ddb0c3455a51385a9a451c833cbed91b75d00a
|
data/Gemfile
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
-
|
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?
|
8
|
-
rack_version = {:
|
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
|
-
#
|
2
|
-
|
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
|
-
|
9
|
+
warn e
|
9
10
|
end
|
10
11
|
|
11
|
-
desc
|
12
|
-
task(:spec) { ruby '-S rspec
|
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(
|
21
|
-
Dir.mkdir
|
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,
|
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(
|
29
|
-
file =
|
30
|
-
Dir.mkdir
|
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,
|
34
|
+
File.open(file, 'w') { |f| f << doc }
|
33
35
|
end
|
34
36
|
|
35
|
-
task :
|
37
|
+
task all: %i[readmes index]
|
36
38
|
end
|
37
39
|
|
38
|
-
desc
|
39
|
-
task :
|
40
|
+
desc 'generate documentation'
|
41
|
+
task doc: 'doc:all'
|
40
42
|
|
41
|
-
desc
|
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
|
-
:
|
49
|
-
:
|
50
|
-
:
|
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 :
|
71
|
-
task :
|
72
|
-
task :
|
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
|
-
#
|
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 :
|
96
|
-
:
|
97
|
-
:
|
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
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
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
|
-
|
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
|
237
|
+
OpenSSL::Digest.new('SHA256'),
|
234
238
|
real_token(session),
|
235
239
|
identifier
|
236
240
|
)
|
data/lib/rack/protection/base.rb
CHANGED
@@ -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
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
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
|
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
|
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
|
-
|
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
|
-
|
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]
|
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) :
|
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,
|
122
|
-
|
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
|
-
#
|
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
|
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
|
48
|
+
prefetch_src].freeze
|
48
49
|
|
49
|
-
NO_ARG_DIRECTIVES = %i
|
50
|
-
upgrade_insecure_requests
|
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
|
-
|
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
|
-
{:
|
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.
|
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 :
|
33
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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 =
|
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[
|
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 :
|
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 :
|
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]
|
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]
|
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
|
@@ -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 :
|
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]
|
40
|
-
return false unless headers['Content-Type'].to_s.split(';', 2).first =~
|
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[
|
15
|
-
env[
|
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[
|
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?
|
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?
|
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 :
|
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
|
@@ -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 :
|
17
|
-
|
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 ==
|
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] =
|
28
|
+
options[:track].each { |k| session[key][k] = encode(env[k]) }
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
|
-
def
|
31
|
-
value
|
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 :
|
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 =
|
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,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 :
|
17
|
+
default_options xss_mode: :block, nosniff: true
|
16
18
|
|
17
19
|
def call(env)
|
18
20
|
status, headers, body = @app.call(env)
|
data/lib/rack/protection.rb
CHANGED
@@ -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 += [
|
34
|
+
except += %i[session_hijacking remote_token]
|
31
35
|
end
|
32
36
|
|
33
37
|
Rack::Builder.new do
|
data/lib/rack-protection.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require
|
1
|
+
require 'rack/protection'
|
data/rack-protection.gemspec
CHANGED
@@ -1,40 +1,44 @@
|
|
1
|
-
|
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 =
|
7
|
+
s.name = 'rack-protection'
|
6
8
|
s.version = version
|
7
|
-
s.description =
|
8
|
-
s.homepage =
|
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 = [
|
12
|
-
s.email =
|
13
|
-
s.files = Dir[
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
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
|
38
|
-
s.
|
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:
|
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:
|
11
|
+
date: 2023-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: base64
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
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:
|
26
|
+
version: 0.1.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: rack
|
28
|
+
name: rack
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
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:
|
41
|
-
|
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: '
|
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:
|
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/
|
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
|
-
|
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:
|
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
|
-
|
110
|
-
|
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.
|