homura-runtime 0.3.3 → 0.3.4

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/lib/homura/runtime/version.rb +1 -1
  4. data/vendor/rack/auth/abstract/handler.rb +41 -0
  5. data/vendor/rack/auth/abstract/request.rb +51 -0
  6. data/vendor/rack/auth/basic.rb +58 -0
  7. data/vendor/rack/bad_request.rb +8 -0
  8. data/vendor/rack/body_proxy.rb +63 -0
  9. data/vendor/rack/builder.rb +315 -0
  10. data/vendor/rack/cascade.rb +67 -0
  11. data/vendor/rack/common_logger.rb +94 -0
  12. data/vendor/rack/conditional_get.rb +87 -0
  13. data/vendor/rack/config.rb +22 -0
  14. data/vendor/rack/constants.rb +68 -0
  15. data/vendor/rack/content_length.rb +34 -0
  16. data/vendor/rack/content_type.rb +33 -0
  17. data/vendor/rack/deflater.rb +159 -0
  18. data/vendor/rack/directory.rb +210 -0
  19. data/vendor/rack/etag.rb +71 -0
  20. data/vendor/rack/events.rb +172 -0
  21. data/vendor/rack/files.rb +224 -0
  22. data/vendor/rack/head.rb +25 -0
  23. data/vendor/rack/headers.rb +238 -0
  24. data/vendor/rack/lint.rb +1000 -0
  25. data/vendor/rack/lock.rb +29 -0
  26. data/vendor/rack/media_type.rb +42 -0
  27. data/vendor/rack/method_override.rb +56 -0
  28. data/vendor/rack/mime.rb +694 -0
  29. data/vendor/rack/mock.rb +3 -0
  30. data/vendor/rack/mock_request.rb +161 -0
  31. data/vendor/rack/mock_response.rb +147 -0
  32. data/vendor/rack/multipart/generator.rb +99 -0
  33. data/vendor/rack/multipart/parser.rb +586 -0
  34. data/vendor/rack/multipart/uploaded_file.rb +82 -0
  35. data/vendor/rack/multipart.rb +77 -0
  36. data/vendor/rack/null_logger.rb +48 -0
  37. data/vendor/rack/protection/authenticity_token.rb +256 -0
  38. data/vendor/rack/protection/base.rb +140 -0
  39. data/vendor/rack/protection/content_security_policy.rb +80 -0
  40. data/vendor/rack/protection/cookie_tossing.rb +77 -0
  41. data/vendor/rack/protection/escaped_params.rb +93 -0
  42. data/vendor/rack/protection/form_token.rb +25 -0
  43. data/vendor/rack/protection/frame_options.rb +39 -0
  44. data/vendor/rack/protection/http_origin.rb +43 -0
  45. data/vendor/rack/protection/ip_spoofing.rb +27 -0
  46. data/vendor/rack/protection/json_csrf.rb +60 -0
  47. data/vendor/rack/protection/path_traversal.rb +45 -0
  48. data/vendor/rack/protection/referrer_policy.rb +27 -0
  49. data/vendor/rack/protection/remote_referrer.rb +22 -0
  50. data/vendor/rack/protection/remote_token.rb +24 -0
  51. data/vendor/rack/protection/session_hijacking.rb +37 -0
  52. data/vendor/rack/protection/strict_transport.rb +41 -0
  53. data/vendor/rack/protection/version.rb +7 -0
  54. data/vendor/rack/protection/xss_header.rb +27 -0
  55. data/vendor/rack/protection.rb +58 -0
  56. data/vendor/rack/query_parser.rb +261 -0
  57. data/vendor/rack/recursive.rb +66 -0
  58. data/vendor/rack/reloader.rb +112 -0
  59. data/vendor/rack/request.rb +818 -0
  60. data/vendor/rack/response.rb +403 -0
  61. data/vendor/rack/rewindable_input.rb +116 -0
  62. data/vendor/rack/runtime.rb +35 -0
  63. data/vendor/rack/sendfile.rb +197 -0
  64. data/vendor/rack/session/abstract/id.rb +533 -0
  65. data/vendor/rack/session/constants.rb +13 -0
  66. data/vendor/rack/session/cookie.rb +292 -0
  67. data/vendor/rack/session/encryptor.rb +415 -0
  68. data/vendor/rack/session/pool.rb +76 -0
  69. data/vendor/rack/session/version.rb +10 -0
  70. data/vendor/rack/session.rb +12 -0
  71. data/vendor/rack/show_exceptions.rb +433 -0
  72. data/vendor/rack/show_status.rb +121 -0
  73. data/vendor/rack/static.rb +188 -0
  74. data/vendor/rack/tempfile_reaper.rb +44 -0
  75. data/vendor/rack/urlmap.rb +99 -0
  76. data/vendor/rack/utils.rb +631 -0
  77. data/vendor/rack/version.rb +17 -0
  78. data/vendor/rack.rb +66 -0
  79. metadata +76 -1
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
6
+ require_relative 'multipart/parser'
7
+ require_relative 'multipart/generator'
8
+
9
+ require_relative 'bad_request'
10
+
11
+ module Rack
12
+ # A multipart form data parser, adapted from IOWA.
13
+ #
14
+ # Usually, Rack::Request#POST takes care of calling this.
15
+ module Multipart
16
+ MULTIPART_BOUNDARY = "AaB03x"
17
+
18
+ class MissingInputError < StandardError
19
+ include BadRequest
20
+ end
21
+
22
+ # Accumulator for multipart form data, conforming to the QueryParser API.
23
+ # In future, the Parser could return the pair list directly, but that would
24
+ # change its API.
25
+ class ParamList # :nodoc:
26
+ def self.make_params
27
+ new
28
+ end
29
+
30
+ def self.normalize_params(params, key, value)
31
+ params << [key, value]
32
+ end
33
+
34
+ def initialize
35
+ @pairs = []
36
+ end
37
+
38
+ def <<(pair)
39
+ @pairs << pair
40
+ end
41
+
42
+ def to_params_hash
43
+ @pairs
44
+ end
45
+ end
46
+
47
+ class << self
48
+ def parse_multipart(env, params = Rack::Utils.default_query_parser)
49
+ unless io = env[RACK_INPUT]
50
+ raise MissingInputError, "Missing input stream!"
51
+ end
52
+
53
+ if content_length = env['CONTENT_LENGTH']
54
+ content_length = content_length.to_i
55
+ end
56
+
57
+ content_type = env['CONTENT_TYPE']
58
+
59
+ tempfile = env[RACK_MULTIPART_TEMPFILE_FACTORY] || Parser::TEMPFILE_FACTORY
60
+ bufsize = env[RACK_MULTIPART_BUFFER_SIZE] || Parser::BUFSIZE
61
+
62
+ info = Parser.parse(io, content_length, content_type, tempfile, bufsize, params)
63
+ env[RACK_TEMPFILES] = info.tmp_files
64
+
65
+ return info.params
66
+ end
67
+
68
+ def extract_multipart(request, params = Rack::Utils.default_query_parser)
69
+ parse_multipart(request.env)
70
+ end
71
+
72
+ def build_multipart(params, first = true)
73
+ Generator.new(params, first).dump
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'constants'
4
+
5
+ module Rack
6
+ class NullLogger
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[RACK_LOGGER] = self
13
+ @app.call(env)
14
+ end
15
+
16
+ def info(progname = nil, &block); end
17
+ def debug(progname = nil, &block); end
18
+ def warn(progname = nil, &block); end
19
+ def error(progname = nil, &block); end
20
+ def fatal(progname = nil, &block); end
21
+ def unknown(progname = nil, &block); end
22
+ def info? ; end
23
+ def debug? ; end
24
+ def warn? ; end
25
+ def error? ; end
26
+ def fatal? ; end
27
+ def debug! ; end
28
+ def error! ; end
29
+ def fatal! ; end
30
+ def info! ; end
31
+ def warn! ; end
32
+ def level ; end
33
+ def progname ; end
34
+ def datetime_format ; end
35
+ def formatter ; end
36
+ def sev_threshold ; end
37
+ def level=(level); end
38
+ def progname=(progname); end
39
+ def datetime_format=(datetime_format); end
40
+ def formatter=(formatter); end
41
+ def sev_threshold=(sev_threshold); end
42
+ def close ; end
43
+ def add(severity, message = nil, progname = nil, &block); end
44
+ def log(severity, message = nil, progname = nil, &block); end
45
+ def <<(msg); end
46
+ def reopen(logdev = nil); end
47
+ end
48
+ end
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/protection'
4
+ require 'securerandom'
5
+ # require 'openssl'
6
+ require 'base64'
7
+
8
+ module Rack
9
+ module Protection
10
+ ##
11
+ # Prevented attack:: CSRF
12
+ # Supported browsers:: all
13
+ # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery
14
+ #
15
+ # This middleware only accepts requests other than <tt>GET</tt>,
16
+ # <tt>HEAD</tt>, <tt>OPTIONS</tt>, <tt>TRACE</tt> if their given access
17
+ # token matches the token included in the session.
18
+ #
19
+ # It checks the <tt>X-CSRF-Token</tt> header and the <tt>POST</tt> form
20
+ # data.
21
+ #
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")
26
+ #
27
+ # == Options
28
+ #
29
+ # [<tt>:authenticity_param</tt>] the name of the param that should contain
30
+ # the token on a request. Default value:
31
+ # <tt>"authenticity_token"</tt>
32
+ #
33
+ # [<tt>:key</tt>] the name of the param that should contain
34
+ # the token in the session. Default value:
35
+ # <tt>:csrf</tt>
36
+ #
37
+ # [<tt>:allow_if</tt>] a proc for custom allow/deny logic. Default value:
38
+ # <tt>nil</tt>
39
+ #
40
+ # == Example: Forms application
41
+ #
42
+ # To show what the AuthenticityToken does, this section includes a sample
43
+ # program which shows two forms. One with, and one without a CSRF token
44
+ # The one without CSRF token field will get a 403 Forbidden response.
45
+ #
46
+ # Install the gem, then run the program:
47
+ #
48
+ # gem install 'rack-protection'
49
+ # ruby server.rb
50
+ #
51
+ # Here is <tt>server.rb</tt>:
52
+ #
53
+ # require 'rack/protection'
54
+ # require 'rack/session'
55
+ #
56
+ # app = Rack::Builder.app do
57
+ # use Rack::Session::Cookie, secret: 'secret'
58
+ # use Rack::Protection::AuthenticityToken
59
+ #
60
+ # run -> (env) do
61
+ # [200, {}, [
62
+ # <<~EOS
63
+ # <!DOCTYPE html>
64
+ # <html lang="en">
65
+ # <head>
66
+ # <meta charset="UTF-8" />
67
+ # <title>rack-protection minimal example</title>
68
+ # </head>
69
+ # <body>
70
+ # <h1>Without Authenticity Token</h1>
71
+ # <p>This takes you to <tt>Forbidden</tt></p>
72
+ # <form action="" method="post">
73
+ # <input type="text" name="foo" />
74
+ # <input type="submit" />
75
+ # </form>
76
+ #
77
+ # <h1>With Authenticity Token</h1>
78
+ # <p>This successfully takes you to back to this form.</p>
79
+ # <form action="" method="post">
80
+ # <input type="hidden" name="authenticity_token" value="#{Rack::Protection::AuthenticityToken.token(env['rack.session'])}" />
81
+ # <input type="text" name="foo" />
82
+ # <input type="submit" />
83
+ # </form>
84
+ # </body>
85
+ # </html>
86
+ # EOS
87
+ # ]]
88
+ # end
89
+ # end
90
+ #
91
+ # Rack::Handler::WEBrick.run app
92
+ #
93
+ # == Example: Customize which POST parameter holds the token
94
+ #
95
+ # To customize the authenticity parameter for form data, use the
96
+ # <tt>:authenticity_param</tt> option:
97
+ # use Rack::Protection::AuthenticityToken, authenticity_param: 'your_token_param_name'
98
+ class AuthenticityToken < Base
99
+ TOKEN_LENGTH = 32
100
+
101
+ default_options authenticity_param: 'authenticity_token',
102
+ key: :csrf,
103
+ allow_if: nil
104
+
105
+ def self.token(session, path: nil, method: :post)
106
+ new(nil).mask_authenticity_token(session, path: path, method: method)
107
+ end
108
+
109
+ def self.random_token
110
+ SecureRandom.urlsafe_base64(TOKEN_LENGTH, padding: false)
111
+ end
112
+
113
+ def accepts?(env)
114
+ session = session(env)
115
+ set_token(session)
116
+
117
+ safe?(env) ||
118
+ valid_token?(env, env['HTTP_X_CSRF_TOKEN']) ||
119
+ valid_token?(env, Request.new(env).params[options[:authenticity_param]]) ||
120
+ options[:allow_if]&.call(env)
121
+ rescue StandardError
122
+ false
123
+ end
124
+
125
+ def mask_authenticity_token(session, path: nil, method: :post)
126
+ set_token(session)
127
+
128
+ token = if path && method
129
+ per_form_token(session, path, method)
130
+ else
131
+ global_token(session)
132
+ end
133
+
134
+ mask_token(token)
135
+ end
136
+
137
+ GLOBAL_TOKEN_IDENTIFIER = '!real_csrf_token'
138
+ private_constant :GLOBAL_TOKEN_IDENTIFIER
139
+
140
+ private
141
+
142
+ def set_token(session)
143
+ session[options[:key]] ||= self.class.random_token
144
+ end
145
+
146
+ # Checks the client's masked token to see if it matches the
147
+ # session token.
148
+ def valid_token?(env, token)
149
+ return false if token.nil? || !token.is_a?(String) || token.empty?
150
+
151
+ session = session(env)
152
+
153
+ begin
154
+ token = decode_token(token)
155
+ rescue ArgumentError # encoded_masked_token is invalid Base64
156
+ return false
157
+ end
158
+
159
+ # See if it's actually a masked token or not. We should be able
160
+ # to handle any unmasked tokens that we've issued without error.
161
+
162
+ if unmasked_token?(token)
163
+ compare_with_real_token(token, session)
164
+ elsif masked_token?(token)
165
+ token = unmask_token(token)
166
+
167
+ compare_with_global_token(token, session) ||
168
+ compare_with_real_token(token, session) ||
169
+ compare_with_per_form_token(token, session, Request.new(env))
170
+ else
171
+ false # Token is malformed
172
+ end
173
+ end
174
+
175
+ # Creates a masked version of the authenticity token that varies
176
+ # on each request. The masking is used to mitigate SSL attacks
177
+ # like BREACH.
178
+ def mask_token(token)
179
+ one_time_pad = SecureRandom.random_bytes(token.length)
180
+ encrypted_token = xor_byte_strings(one_time_pad, token)
181
+ masked_token = one_time_pad + encrypted_token
182
+ encode_token(masked_token)
183
+ end
184
+
185
+ # Essentially the inverse of +mask_token+.
186
+ def unmask_token(masked_token)
187
+ # Split the token into the one-time pad and the encrypted
188
+ # value and decrypt it
189
+ token_length = masked_token.length / 2
190
+ one_time_pad = masked_token[0...token_length]
191
+ encrypted_token = masked_token[token_length..]
192
+ xor_byte_strings(one_time_pad, encrypted_token)
193
+ end
194
+
195
+ def unmasked_token?(token)
196
+ token.length == TOKEN_LENGTH
197
+ end
198
+
199
+ def masked_token?(token)
200
+ token.length == TOKEN_LENGTH * 2
201
+ end
202
+
203
+ def compare_with_real_token(token, session)
204
+ secure_compare(token, real_token(session))
205
+ end
206
+
207
+ def compare_with_global_token(token, session)
208
+ secure_compare(token, global_token(session))
209
+ end
210
+
211
+ def compare_with_per_form_token(token, session, request)
212
+ secure_compare(token,
213
+ per_form_token(session, request.path.chomp('/'), request.request_method))
214
+ end
215
+
216
+ def real_token(session)
217
+ decode_token(session[options[:key]])
218
+ end
219
+
220
+ def global_token(session)
221
+ token_hmac(session, GLOBAL_TOKEN_IDENTIFIER)
222
+ end
223
+
224
+ def per_form_token(session, path, method)
225
+ token_hmac(session, "#{path}##{method.downcase}")
226
+ end
227
+
228
+ def encode_token(token)
229
+ Base64.urlsafe_encode64(token)
230
+ end
231
+
232
+ def decode_token(token)
233
+ Base64.urlsafe_decode64(token)
234
+ end
235
+
236
+ def token_hmac(session, identifier)
237
+ OpenSSL::HMAC.digest(
238
+ OpenSSL::Digest.new('SHA256'),
239
+ real_token(session),
240
+ identifier
241
+ )
242
+ end
243
+
244
+ def xor_byte_strings(s1, s2)
245
+ s2 = s2.dup
246
+ size = s1.bytesize
247
+ i = 0
248
+ while i < size
249
+ s2.setbyte(i, s1.getbyte(i) ^ s2.getbyte(i))
250
+ i += 1
251
+ end
252
+ s2
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/protection'
4
+ require 'rack/utils'
5
+ # require 'digest'
6
+ require 'logger'
7
+ require 'uri'
8
+
9
+ module Rack
10
+ module Protection
11
+ class Base
12
+ DEFAULT_OPTIONS = {
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]
19
+ }
20
+
21
+ attr_reader :app, :options
22
+
23
+ def self.default_options(options)
24
+ define_method(:default_options) { super().merge(options) }
25
+ end
26
+
27
+ def self.default_reaction(reaction)
28
+ alias_method(:default_reaction, reaction)
29
+ end
30
+
31
+ def default_options
32
+ DEFAULT_OPTIONS
33
+ end
34
+
35
+ def initialize(app, options = {})
36
+ @app = app
37
+ @options = default_options.merge(options)
38
+ end
39
+
40
+ def safe?(env)
41
+ %w[GET HEAD OPTIONS TRACE].include? env['REQUEST_METHOD']
42
+ end
43
+
44
+ def accepts?(env)
45
+ raise NotImplementedError, "#{self.class} implementation pending"
46
+ end
47
+
48
+ def call(env)
49
+ unless accepts? env
50
+ instrument env
51
+ result = react env
52
+ end
53
+ result or app.call(env)
54
+ end
55
+
56
+ def react(env)
57
+ result = send(options[:reaction], env)
58
+ result if (Array === result) && (result.size == 3)
59
+ end
60
+
61
+ def warn(env, message)
62
+ return unless options[:logging]
63
+
64
+ l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors'])
65
+ l.warn(message)
66
+ end
67
+
68
+ def instrument(env)
69
+ return unless (i = options[:instrumenter])
70
+
71
+ env['rack.protection.attack'] = self.class.name.split('::').last.downcase
72
+ i.instrument('rack.protection', env)
73
+ end
74
+
75
+ def deny(env)
76
+ warn env, "attack prevented by #{self.class}"
77
+ [options[:status], { 'content-type' => 'text/plain' }, [options[:message]]]
78
+ end
79
+
80
+ def report(env)
81
+ warn env, "attack reported by #{self.class}"
82
+ env[options[:report_key]] = true
83
+ end
84
+
85
+ def session?(env)
86
+ env.include? options[:session_key]
87
+ end
88
+
89
+ def session(env)
90
+ return env[options[:session_key]] if session? env
91
+
92
+ raise "you need to set up a session middleware *before* #{self.class}"
93
+ end
94
+
95
+ def drop_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}"
103
+ end
104
+
105
+ def referrer(env)
106
+ ref = env['HTTP_REFERER'].to_s
107
+ return if !options[:allow_empty_referrer] && ref.empty?
108
+
109
+ URI.parse(ref).host || Request.new(env).host
110
+ rescue URI::InvalidURIError
111
+ end
112
+
113
+ def origin(env)
114
+ env['HTTP_ORIGIN'] || env['HTTP_X_ORIGIN']
115
+ end
116
+
117
+ def random_string(secure = defined? SecureRandom)
118
+ secure ? SecureRandom.hex(16) : '%032x' % rand((2**128) - 1)
119
+ rescue NotImplementedError
120
+ random_string false
121
+ end
122
+
123
+ def encrypt(value)
124
+ options[:encryptor].hexdigest value.to_s
125
+ end
126
+
127
+ def secure_compare(a, b)
128
+ Rack::Utils.secure_compare(a.to_s, b.to_s)
129
+ end
130
+
131
+ alias default_reaction deny
132
+
133
+ def html?(headers)
134
+ return false unless (header = headers.detect { |k, _v| k.downcase == 'content-type' })
135
+
136
+ options[:html_types].include? header.last[%r{^\w+/\w+}]
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/protection'
4
+
5
+ module Rack
6
+ module Protection
7
+ ##
8
+ # Prevented attack:: XSS and others
9
+ # Supported browsers:: Firefox 23+, Safari 7+, Chrome 25+, Opera 15+
10
+ #
11
+ # Description:: Content Security Policy, a mechanism web applications
12
+ # can use to mitigate a broad class of content injection
13
+ # vulnerabilities, such as cross-site scripting (XSS).
14
+ # Content Security Policy is a declarative policy that lets
15
+ # the authors (or server administrators) of a web application
16
+ # inform the client about the sources from which the
17
+ # application expects to load resources.
18
+ #
19
+ # More info:: W3C CSP Level 1 : https://www.w3.org/TR/CSP1/ (deprecated)
20
+ # W3C CSP Level 2 : https://www.w3.org/TR/CSP2/ (current)
21
+ # W3C CSP Level 3 : https://www.w3.org/TR/CSP3/ (draft)
22
+ # https://developer.mozilla.org/en-US/docs/Web/Security/CSP
23
+ # http://caniuse.com/#search=ContentSecurityPolicy
24
+ # http://content-security-policy.com/
25
+ # https://securityheaders.io
26
+ # https://scotthelme.co.uk/csp-cheat-sheet/
27
+ # http://www.html5rocks.com/en/tutorials/security/content-security-policy/
28
+ #
29
+ # Sets the 'content-security-policy[-report-only]' header.
30
+ #
31
+ # Options: ContentSecurityPolicy configuration is a complex topic with
32
+ # several levels of support that has evolved over time.
33
+ # See the W3C documentation and the links in the more info
34
+ # section for CSP usage examples and best practices. The
35
+ # CSP3 directives in the 'NO_ARG_DIRECTIVES' constant need to be
36
+ # presented in the options hash with a boolean 'true' in order
37
+ # to be used in a policy.
38
+ #
39
+ class ContentSecurityPolicy < Base
40
+ default_options default_src: "'self'", report_only: false
41
+
42
+ DIRECTIVES = %i[base_uri child_src connect_src default_src
43
+ font_src form_action frame_ancestors frame_src
44
+ img_src manifest_src media_src object_src
45
+ plugin_types referrer reflected_xss report_to
46
+ report_uri require_sri_for sandbox script_src
47
+ style_src worker_src webrtc_src navigate_to
48
+ prefetch_src].freeze
49
+
50
+ NO_ARG_DIRECTIVES = %i[block_all_mixed_content disown_opener
51
+ upgrade_insecure_requests].freeze
52
+
53
+ def csp_policy
54
+ directives = []
55
+
56
+ DIRECTIVES.each do |d|
57
+ if options.key?(d)
58
+ directives << "#{d.to_s.sub(/_/, '-')} #{options[d]}"
59
+ end
60
+ end
61
+
62
+ # Set these key values to boolean 'true' to include in policy
63
+ NO_ARG_DIRECTIVES.each do |d|
64
+ if options.key?(d) && options[d].is_a?(TrueClass)
65
+ directives << d.to_s.tr('_', '-')
66
+ end
67
+ end
68
+
69
+ directives.compact.sort.join('; ')
70
+ end
71
+
72
+ def call(env)
73
+ status, headers, body = @app.call(env)
74
+ header = options[:report_only] ? 'content-security-policy-report-only' : 'content-security-policy'
75
+ headers[header] ||= csp_policy if html? headers
76
+ [status, headers, body]
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/protection'
4
+ require 'pathname'
5
+
6
+ module Rack
7
+ module Protection
8
+ ##
9
+ # Prevented attack:: Cookie Tossing
10
+ # Supported browsers:: all
11
+ # More infos:: https://github.com/blog/1466-yummy-cookies-across-domains
12
+ #
13
+ # Does not accept HTTP requests if the HTTP_COOKIE header contains more than one
14
+ # session cookie. This does not protect against a cookie overflow attack.
15
+ #
16
+ # Options:
17
+ #
18
+ # session_key:: The name of the session cookie (default: 'rack.session')
19
+ class CookieTossing < Base
20
+ default_reaction :deny
21
+
22
+ def call(env)
23
+ status, headers, body = super
24
+ response = Rack::Response.new(body, status, headers)
25
+ request = Rack::Request.new(env)
26
+ remove_bad_cookies(request, response)
27
+ response.finish
28
+ end
29
+
30
+ def accepts?(env)
31
+ cookie_header = env['HTTP_COOKIE']
32
+ cookies = Rack::Utils.parse_query(cookie_header, ';,') { |s| s }
33
+ cookies.each do |k, v|
34
+ if (k == session_key && Array(v).size > 1) ||
35
+ (k != session_key && Rack::Utils.unescape(k) == session_key)
36
+ bad_cookies << k
37
+ end
38
+ end
39
+ bad_cookies.empty?
40
+ end
41
+
42
+ def remove_bad_cookies(request, response)
43
+ return if bad_cookies.empty?
44
+
45
+ paths = cookie_paths(request.path)
46
+ bad_cookies.each do |name|
47
+ paths.each { |path| response.set_cookie name, empty_cookie(request.host, path) }
48
+ end
49
+ end
50
+
51
+ def redirect(env)
52
+ request = Request.new(env)
53
+ warn env, "attack prevented by #{self.class}"
54
+ [302, { 'content-type' => 'text/html', 'location' => request.path }, []]
55
+ end
56
+
57
+ def bad_cookies
58
+ @bad_cookies ||= []
59
+ end
60
+
61
+ def cookie_paths(path)
62
+ path = '/' if path.to_s.empty?
63
+ paths = []
64
+ Pathname.new(path).descend { |p| paths << p.to_s }
65
+ paths
66
+ end
67
+
68
+ def empty_cookie(host, path)
69
+ { value: '', domain: host, path: path, expires: Time.at(0) }
70
+ end
71
+
72
+ def session_key
73
+ @session_key ||= options[:session_key]
74
+ end
75
+ end
76
+ end
77
+ end