secure_headers 3.9.0 → 4.0.0.alpha01
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.rspec +1 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -1
- data/.travis.yml +8 -6
- data/CHANGELOG.md +2 -34
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +7 -4
- data/Guardfile +1 -0
- data/README.md +4 -25
- data/Rakefile +22 -18
- data/docs/cookies.md +18 -5
- data/lib/secure_headers.rb +1 -2
- data/lib/secure_headers/configuration.rb +6 -16
- data/lib/secure_headers/hash_helper.rb +2 -1
- data/lib/secure_headers/headers/clear_site_data.rb +2 -1
- data/lib/secure_headers/headers/content_security_policy.rb +14 -60
- data/lib/secure_headers/headers/content_security_policy_config.rb +1 -1
- data/lib/secure_headers/headers/cookie.rb +22 -10
- data/lib/secure_headers/headers/policy_management.rb +57 -98
- data/lib/secure_headers/headers/public_key_pins.rb +4 -3
- data/lib/secure_headers/headers/referrer_policy.rb +1 -0
- data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
- data/lib/secure_headers/headers/x_content_type_options.rb +1 -0
- data/lib/secure_headers/headers/x_download_options.rb +2 -1
- data/lib/secure_headers/headers/x_frame_options.rb +1 -0
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -1
- data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
- data/lib/secure_headers/middleware.rb +10 -9
- data/lib/secure_headers/railtie.rb +7 -6
- data/lib/secure_headers/utils/cookies_config.rb +17 -18
- data/lib/secure_headers/view_helper.rb +2 -1
- data/lib/tasks/tasks.rake +2 -1
- data/secure_headers.gemspec +13 -3
- data/spec/lib/secure_headers/configuration_spec.rb +9 -8
- data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +17 -53
- data/spec/lib/secure_headers/headers/cookie_spec.rb +58 -37
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +20 -41
- data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
- data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +4 -3
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +5 -4
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
- data/spec/lib/secure_headers/middleware_spec.rb +18 -21
- data/spec/lib/secure_headers/view_helpers_spec.rb +5 -4
- data/spec/lib/secure_headers_spec.rb +92 -120
- data/spec/spec_helper.rb +9 -23
- data/upgrading-to-4-0.md +49 -0
- metadata +16 -11
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +0 -70
- data/spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb +0 -42
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SecureHeaders
|
2
3
|
module DynamicConfig
|
3
4
|
def self.included(base)
|
@@ -37,7 +38,6 @@ module SecureHeaders
|
|
37
38
|
@script_src = nil
|
38
39
|
@style_nonce = nil
|
39
40
|
@style_src = nil
|
40
|
-
@worker_src = nil
|
41
41
|
@upgrade_insecure_requests = nil
|
42
42
|
|
43
43
|
from_hash(hash)
|
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "cgi"
|
3
|
+
require "secure_headers/utils/cookies_config"
|
4
|
+
|
3
5
|
|
4
6
|
module SecureHeaders
|
5
7
|
class CookiesConfigError < StandardError; end
|
@@ -13,8 +15,18 @@ module SecureHeaders
|
|
13
15
|
|
14
16
|
attr_reader :raw_cookie, :config
|
15
17
|
|
18
|
+
COOKIE_DEFAULTS = {
|
19
|
+
httponly: true,
|
20
|
+
secure: true,
|
21
|
+
samesite: { lax: true },
|
22
|
+
}.freeze
|
23
|
+
|
16
24
|
def initialize(cookie, config)
|
17
25
|
@raw_cookie = cookie
|
26
|
+
unless config == OPT_OUT
|
27
|
+
config ||= {}
|
28
|
+
config = COOKIE_DEFAULTS.merge(config)
|
29
|
+
end
|
18
30
|
@config = config
|
19
31
|
@attributes = {
|
20
32
|
httponly: nil,
|
@@ -56,6 +68,7 @@ module SecureHeaders
|
|
56
68
|
end
|
57
69
|
|
58
70
|
def flag_cookie?(attribute)
|
71
|
+
return false if config == OPT_OUT
|
59
72
|
case config[attribute]
|
60
73
|
when TrueClass
|
61
74
|
true
|
@@ -81,13 +94,12 @@ module SecureHeaders
|
|
81
94
|
"SameSite=Lax"
|
82
95
|
elsif flag_samesite_strict?
|
83
96
|
"SameSite=Strict"
|
84
|
-
elsif flag_samesite_none?
|
85
|
-
"SameSite=None"
|
86
97
|
end
|
87
98
|
end
|
88
99
|
|
89
100
|
def flag_samesite?
|
90
|
-
|
101
|
+
return false if config == OPT_OUT || config[:samesite] == OPT_OUT
|
102
|
+
flag_samesite_lax? || flag_samesite_strict?
|
91
103
|
end
|
92
104
|
|
93
105
|
def flag_samesite_lax?
|
@@ -98,13 +110,13 @@ module SecureHeaders
|
|
98
110
|
flag_samesite_enforcement?(:strict)
|
99
111
|
end
|
100
112
|
|
101
|
-
def flag_samesite_none?
|
102
|
-
flag_samesite_enforcement?(:none)
|
103
|
-
end
|
104
|
-
|
105
113
|
def flag_samesite_enforcement?(mode)
|
106
114
|
return unless config[:samesite]
|
107
115
|
|
116
|
+
if config[:samesite].is_a?(TrueClass) && mode == :lax
|
117
|
+
return true
|
118
|
+
end
|
119
|
+
|
108
120
|
case config[:samesite][mode]
|
109
121
|
when Hash
|
110
122
|
conditionally_flag?(config[:samesite][mode])
|
@@ -119,7 +131,7 @@ module SecureHeaders
|
|
119
131
|
return unless cookie
|
120
132
|
|
121
133
|
cookie.split(/[;,]\s?/).each do |pairs|
|
122
|
-
name, values = pairs.split(
|
134
|
+
name, values = pairs.split("=", 2)
|
123
135
|
name = CGI.unescape(name)
|
124
136
|
|
125
137
|
attribute = name.downcase.to_sym
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SecureHeaders
|
2
3
|
module PolicyManagement
|
3
4
|
def self.included(base)
|
@@ -5,8 +6,14 @@ module SecureHeaders
|
|
5
6
|
end
|
6
7
|
|
7
8
|
MODERN_BROWSERS = %w(Chrome Opera Firefox)
|
8
|
-
|
9
|
-
|
9
|
+
DEFAULT_CONFIG = {
|
10
|
+
default_src: %w(https:),
|
11
|
+
img_src: %w(https: data: 'self'),
|
12
|
+
object_src: %w('none'),
|
13
|
+
script_src: %w(https:),
|
14
|
+
style_src: %w('self' 'unsafe-inline' https:),
|
15
|
+
form_action: %w('self')
|
16
|
+
}.freeze
|
10
17
|
DATA_PROTOCOL = "data:".freeze
|
11
18
|
BLOB_PROTOCOL = "blob:".freeze
|
12
19
|
SELF = "'self'".freeze
|
@@ -65,13 +72,10 @@ module SecureHeaders
|
|
65
72
|
BLOCK_ALL_MIXED_CONTENT = :block_all_mixed_content
|
66
73
|
MANIFEST_SRC = :manifest_src
|
67
74
|
UPGRADE_INSECURE_REQUESTS = :upgrade_insecure_requests
|
68
|
-
WORKER_SRC = :worker_src
|
69
|
-
|
70
75
|
DIRECTIVES_3_0 = [
|
71
76
|
DIRECTIVES_2_0,
|
72
77
|
BLOCK_ALL_MIXED_CONTENT,
|
73
78
|
MANIFEST_SRC,
|
74
|
-
WORKER_SRC,
|
75
79
|
UPGRADE_INSECURE_REQUESTS
|
76
80
|
].flatten.freeze
|
77
81
|
|
@@ -82,7 +86,6 @@ module SecureHeaders
|
|
82
86
|
FIREFOX_UNSUPPORTED_DIRECTIVES = [
|
83
87
|
BLOCK_ALL_MIXED_CONTENT,
|
84
88
|
CHILD_SRC,
|
85
|
-
WORKER_SRC,
|
86
89
|
PLUGIN_TYPES
|
87
90
|
].freeze
|
88
91
|
|
@@ -92,7 +95,6 @@ module SecureHeaders
|
|
92
95
|
|
93
96
|
FIREFOX_46_UNSUPPORTED_DIRECTIVES = [
|
94
97
|
BLOCK_ALL_MIXED_CONTENT,
|
95
|
-
WORKER_SRC,
|
96
98
|
PLUGIN_TYPES
|
97
99
|
].freeze
|
98
100
|
|
@@ -114,6 +116,18 @@ module SecureHeaders
|
|
114
116
|
# everything else is in between.
|
115
117
|
BODY_DIRECTIVES = ALL_DIRECTIVES - [DEFAULT_SRC, REPORT_URI]
|
116
118
|
|
119
|
+
# These are directives that do not inherit the default-src value. This is
|
120
|
+
# useful when calling #combine_policies.
|
121
|
+
NON_FETCH_SOURCES = [
|
122
|
+
BASE_URI,
|
123
|
+
FORM_ACTION,
|
124
|
+
FRAME_ANCESTORS,
|
125
|
+
PLUGIN_TYPES,
|
126
|
+
REPORT_URI
|
127
|
+
]
|
128
|
+
|
129
|
+
FETCH_SOURCES = ALL_DIRECTIVES - NON_FETCH_SOURCES
|
130
|
+
|
117
131
|
VARIATIONS = {
|
118
132
|
"Chrome" => CHROME_DIRECTIVES,
|
119
133
|
"Opera" => CHROME_DIRECTIVES,
|
@@ -141,31 +155,14 @@ module SecureHeaders
|
|
141
155
|
MANIFEST_SRC => :source_list,
|
142
156
|
MEDIA_SRC => :source_list,
|
143
157
|
OBJECT_SRC => :source_list,
|
144
|
-
PLUGIN_TYPES => :
|
158
|
+
PLUGIN_TYPES => :source_list,
|
145
159
|
REPORT_URI => :source_list,
|
146
|
-
SANDBOX => :
|
160
|
+
SANDBOX => :source_list,
|
147
161
|
SCRIPT_SRC => :source_list,
|
148
162
|
STYLE_SRC => :source_list,
|
149
|
-
WORKER_SRC => :source_list,
|
150
163
|
UPGRADE_INSECURE_REQUESTS => :boolean
|
151
164
|
}.freeze
|
152
165
|
|
153
|
-
# These are directives that don't have use a source list, and hence do not
|
154
|
-
# inherit the default-src value.
|
155
|
-
NON_SOURCE_LIST_SOURCES = DIRECTIVE_VALUE_TYPES.select do |_, type|
|
156
|
-
type != :source_list
|
157
|
-
end.keys.freeze
|
158
|
-
|
159
|
-
# These are directives that take a source list, but that do not inherit
|
160
|
-
# the default-src value.
|
161
|
-
NON_FETCH_SOURCES = [
|
162
|
-
BASE_URI,
|
163
|
-
FORM_ACTION,
|
164
|
-
FRAME_ANCESTORS,
|
165
|
-
REPORT_URI
|
166
|
-
]
|
167
|
-
|
168
|
-
FETCH_SOURCES = ALL_DIRECTIVES - NON_FETCH_SOURCES - NON_SOURCE_LIST_SOURCES
|
169
166
|
|
170
167
|
STAR_REGEXP = Regexp.new(Regexp.escape(STAR))
|
171
168
|
HTTP_SCHEME_REGEX = %r{\Ahttps?://}
|
@@ -205,6 +202,7 @@ module SecureHeaders
|
|
205
202
|
def validate_config!(config)
|
206
203
|
return if config.nil? || config.opt_out?
|
207
204
|
raise ContentSecurityPolicyConfigError.new(":default_src is required") unless config.directive_value(:default_src)
|
205
|
+
raise ContentSecurityPolicyConfigError.new(":script_src is required, falling back to default-src is too dangerous") unless config.directive_value(:script_src)
|
208
206
|
ContentSecurityPolicyConfig.attrs.each do |key|
|
209
207
|
value = config.directive_value(key)
|
210
208
|
next unless value
|
@@ -263,7 +261,7 @@ module SecureHeaders
|
|
263
261
|
# when each hash contains a value for a given key.
|
264
262
|
def merge_policy_additions(original, additions)
|
265
263
|
original.merge(additions) do |directive, lhs, rhs|
|
266
|
-
if
|
264
|
+
if source_list?(directive)
|
267
265
|
(lhs.to_a + rhs.to_a).compact.uniq
|
268
266
|
else
|
269
267
|
rhs
|
@@ -271,27 +269,20 @@ module SecureHeaders
|
|
271
269
|
end.reject { |_, value| value.nil? || value == [] } # this mess prevents us from adding empty directives.
|
272
270
|
end
|
273
271
|
|
274
|
-
# Returns True if a directive expects a list of values and False otherwise.
|
275
|
-
def list_directive?(directive)
|
276
|
-
source_list?(directive) ||
|
277
|
-
sandbox_list?(directive) ||
|
278
|
-
media_type_list?(directive)
|
279
|
-
end
|
280
|
-
|
281
272
|
# For each directive in additions that does not exist in the original config,
|
282
273
|
# copy the default-src value to the original config. This modifies the original hash.
|
283
274
|
def populate_fetch_source_with_default!(original, additions)
|
284
275
|
# in case we would be appending to an empty directive, fill it with the default-src value
|
285
276
|
additions.each_key do |directive|
|
286
|
-
directive
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
277
|
+
if !original[directive] && ((source_list?(directive) && FETCH_SOURCES.include?(directive)) || nonce_added?(original, additions))
|
278
|
+
if nonce_added?(original, additions)
|
279
|
+
inferred_directive = directive.to_s.gsub(/_nonce/, "_src").to_sym
|
280
|
+
unless original[inferred_directive] || NON_FETCH_SOURCES.include?(inferred_directive)
|
281
|
+
original[inferred_directive] = default_for(directive, original)
|
282
|
+
end
|
283
|
+
else
|
284
|
+
original[directive] = default_for(directive, original)
|
285
|
+
end
|
295
286
|
end
|
296
287
|
end
|
297
288
|
end
|
@@ -302,77 +293,45 @@ module SecureHeaders
|
|
302
293
|
original[DEFAULT_SRC]
|
303
294
|
end
|
304
295
|
|
305
|
-
def
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
296
|
+
def nonce_added?(original, additions)
|
297
|
+
[:script_nonce, :style_nonce].each do |nonce|
|
298
|
+
if additions[nonce] && !original[nonce]
|
299
|
+
return true
|
300
|
+
end
|
301
|
+
end
|
311
302
|
end
|
312
303
|
|
313
|
-
def
|
314
|
-
DIRECTIVE_VALUE_TYPES[directive] == :
|
304
|
+
def source_list?(directive)
|
305
|
+
DIRECTIVE_VALUE_TYPES[directive] == :source_list
|
315
306
|
end
|
316
307
|
|
317
308
|
# Private: Validates that the configuration has a valid type, or that it is a valid
|
318
309
|
# source expression.
|
319
|
-
def validate_directive!(directive,
|
320
|
-
ensure_valid_directive!(directive)
|
310
|
+
def validate_directive!(directive, source_expression)
|
321
311
|
case ContentSecurityPolicy::DIRECTIVE_VALUE_TYPES[directive]
|
322
312
|
when :boolean
|
323
|
-
unless boolean?(
|
324
|
-
raise ContentSecurityPolicyConfigError.new("#{directive} must be a boolean
|
313
|
+
unless boolean?(source_expression)
|
314
|
+
raise ContentSecurityPolicyConfigError.new("#{directive} must be a boolean value")
|
315
|
+
end
|
316
|
+
when :string
|
317
|
+
unless source_expression.is_a?(String)
|
318
|
+
raise ContentSecurityPolicyConfigError.new("#{directive} Must be a string. Found #{config.class}: #{config} value")
|
325
319
|
end
|
326
|
-
when :sandbox_list
|
327
|
-
validate_sandbox_expression!(directive, value)
|
328
|
-
when :media_type_list
|
329
|
-
validate_media_type_expression!(directive, value)
|
330
|
-
when :source_list
|
331
|
-
validate_source_expression!(directive, value)
|
332
320
|
else
|
333
|
-
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
# Private: validates that a sandbox token expression:
|
338
|
-
# 1. is an array of strings or optionally `true` (to enable maximal sandboxing)
|
339
|
-
# 2. For arrays, each element is of the form allow-*
|
340
|
-
def validate_sandbox_expression!(directive, sandbox_token_expression)
|
341
|
-
# We support sandbox: true to indicate a maximally secure sandbox.
|
342
|
-
return if boolean?(sandbox_token_expression) && sandbox_token_expression == true
|
343
|
-
ensure_array_of_strings!(directive, sandbox_token_expression)
|
344
|
-
valid = sandbox_token_expression.compact.all? do |v|
|
345
|
-
v.is_a?(String) && v.start_with?("allow-")
|
346
|
-
end
|
347
|
-
if !valid
|
348
|
-
raise ContentSecurityPolicyConfigError.new("#{directive} must be True or an array of zero or more sandbox token strings (ex. allow-forms)")
|
349
|
-
end
|
350
|
-
end
|
351
|
-
|
352
|
-
# Private: validates that a media type expression:
|
353
|
-
# 1. is an array of strings
|
354
|
-
# 2. each element is of the form type/subtype
|
355
|
-
def validate_media_type_expression!(directive, media_type_expression)
|
356
|
-
ensure_array_of_strings!(directive, media_type_expression)
|
357
|
-
valid = media_type_expression.compact.all? do |v|
|
358
|
-
# All media types are of the form: <type from RFC 2045> "/" <subtype from RFC 2045>.
|
359
|
-
v =~ /\A.+\/.+\z/
|
360
|
-
end
|
361
|
-
if !valid
|
362
|
-
raise ContentSecurityPolicyConfigError.new("#{directive} must be an array of valid media types (ex. application/pdf)")
|
321
|
+
validate_source_expression!(directive, source_expression)
|
363
322
|
end
|
364
323
|
end
|
365
324
|
|
366
325
|
# Private: validates that a source expression:
|
367
|
-
# 1.
|
368
|
-
# 2.
|
326
|
+
# 1. has a valid name
|
327
|
+
# 2. is an array of strings
|
328
|
+
# 3. does not contain any depreated, now invalid values (inline, eval, self, none)
|
369
329
|
#
|
370
330
|
# Does not validate the invididual values of the source expression (e.g.
|
371
331
|
# script_src => h*t*t*p: will not raise an exception)
|
372
332
|
def validate_source_expression!(directive, source_expression)
|
373
|
-
|
374
|
-
|
375
|
-
end
|
333
|
+
ensure_valid_directive!(directive)
|
334
|
+
ensure_array_of_strings!(directive, source_expression)
|
376
335
|
ensure_valid_sources!(directive, source_expression)
|
377
336
|
end
|
378
337
|
|
@@ -382,8 +341,8 @@ module SecureHeaders
|
|
382
341
|
end
|
383
342
|
end
|
384
343
|
|
385
|
-
def ensure_array_of_strings!(directive,
|
386
|
-
|
344
|
+
def ensure_array_of_strings!(directive, source_expression)
|
345
|
+
unless source_expression.is_a?(Array) && source_expression.compact.all? { |v| v.is_a?(String) }
|
387
346
|
raise ContentSecurityPolicyConfigError.new("#{directive} must be an array of strings")
|
388
347
|
end
|
389
348
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SecureHeaders
|
2
3
|
class PublicKeyPinsConfigError < StandardError; end
|
3
4
|
class PublicKeyPins
|
@@ -54,7 +55,7 @@ module SecureHeaders
|
|
54
55
|
pin_directives,
|
55
56
|
report_uri_directive,
|
56
57
|
subdomain_directive
|
57
|
-
].compact.join(
|
58
|
+
].compact.join("; ").strip
|
58
59
|
end
|
59
60
|
|
60
61
|
def pin_directives
|
@@ -63,7 +64,7 @@ module SecureHeaders
|
|
63
64
|
pin.map do |token, hash|
|
64
65
|
"pin-#{token}=\"#{hash}\"" if HASH_ALGORITHMS.include?(token)
|
65
66
|
end
|
66
|
-
end.join(
|
67
|
+
end.join("; ")
|
67
68
|
end
|
68
69
|
|
69
70
|
def max_age_directive
|
@@ -75,7 +76,7 @@ module SecureHeaders
|
|
75
76
|
end
|
76
77
|
|
77
78
|
def subdomain_directive
|
78
|
-
@include_subdomains ?
|
79
|
+
@include_subdomains ? "includeSubDomains" : nil
|
79
80
|
end
|
80
81
|
end
|
81
82
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SecureHeaders
|
2
3
|
class STSConfigError < StandardError; end
|
3
4
|
|
4
5
|
class StrictTransportSecurity
|
5
|
-
HEADER_NAME =
|
6
|
+
HEADER_NAME = "Strict-Transport-Security".freeze
|
6
7
|
HSTS_MAX_AGE = "631138519"
|
7
8
|
DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
|
8
9
|
VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SecureHeaders
|
2
3
|
class XDOConfigError < StandardError; end
|
3
4
|
class XDownloadOptions
|
4
5
|
HEADER_NAME = "X-Download-Options".freeze
|
5
|
-
DEFAULT_VALUE =
|
6
|
+
DEFAULT_VALUE = "noopen"
|
6
7
|
CONFIG_KEY = :x_download_options
|
7
8
|
|
8
9
|
class << self
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SecureHeaders
|
2
3
|
class XPCDPConfigError < StandardError; end
|
3
4
|
class XPermittedCrossDomainPolicies
|
4
5
|
HEADER_NAME = "X-Permitted-Cross-Domain-Policies".freeze
|
5
|
-
DEFAULT_VALUE =
|
6
|
+
DEFAULT_VALUE = "none"
|
6
7
|
VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
|
7
8
|
CONFIG_KEY = :x_permitted_cross_domain_policies
|
8
9
|
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SecureHeaders
|
2
3
|
class XXssProtectionConfigError < StandardError; end
|
3
4
|
class XXssProtection
|
4
|
-
HEADER_NAME =
|
5
|
+
HEADER_NAME = "X-XSS-Protection".freeze
|
5
6
|
DEFAULT_VALUE = "1; mode=block"
|
6
7
|
VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
|
7
8
|
CONFIG_KEY = :x_xss_protection
|