secure_headers 5.2.0 → 6.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 +4 -4
- data/.travis.yml +8 -4
- data/CHANGELOG.md +3 -7
- data/Gemfile +1 -1
- data/README.md +2 -2
- data/docs/upgrading-to-6-0.md +50 -0
- data/lib/secure_headers/configuration.rb +114 -164
- data/lib/secure_headers/headers/clear_site_data.rb +1 -3
- data/lib/secure_headers/headers/content_security_policy.rb +8 -74
- data/lib/secure_headers/headers/content_security_policy_config.rb +3 -13
- data/lib/secure_headers/headers/expect_certificate_transparency.rb +2 -3
- data/lib/secure_headers/headers/policy_management.rb +14 -65
- data/lib/secure_headers/headers/public_key_pins.rb +2 -3
- data/lib/secure_headers/headers/referrer_policy.rb +2 -2
- data/lib/secure_headers/headers/strict_transport_security.rb +2 -2
- data/lib/secure_headers/headers/x_content_type_options.rb +2 -2
- data/lib/secure_headers/headers/x_download_options.rb +2 -2
- data/lib/secure_headers/headers/x_frame_options.rb +1 -2
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -2
- data/lib/secure_headers/headers/x_xss_protection.rb +3 -3
- data/lib/secure_headers/view_helper.rb +9 -8
- data/lib/secure_headers.rb +14 -78
- data/secure_headers.gemspec +1 -2
- data/spec/lib/secure_headers/configuration_spec.rb +15 -70
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +2 -75
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +35 -9
- data/spec/lib/secure_headers/middleware_spec.rb +7 -1
- data/spec/lib/secure_headers/view_helpers_spec.rb +29 -0
- data/spec/lib/secure_headers_spec.rb +38 -76
- data/spec/spec_helper.rb +7 -3
- metadata +3 -16
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require_relative "policy_management"
|
|
3
3
|
require_relative "content_security_policy_config"
|
|
4
|
-
require "useragent"
|
|
5
4
|
|
|
6
5
|
module SecureHeaders
|
|
7
6
|
class ContentSecurityPolicy
|
|
8
7
|
include PolicyManagement
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
VERSION_46 = ::UserAgent::Version.new("46")
|
|
12
|
-
VERSION_10 = ::UserAgent::Version.new("10")
|
|
13
|
-
FALLBACK_VERSION = ::UserAgent::Version.new("0")
|
|
14
|
-
|
|
15
|
-
def initialize(config = nil, user_agent = OTHER)
|
|
9
|
+
def initialize(config = nil)
|
|
16
10
|
@config = if config.is_a?(Hash)
|
|
17
11
|
if config[:report_only]
|
|
18
12
|
ContentSecurityPolicyReportOnlyConfig.new(config || DEFAULT_CONFIG)
|
|
@@ -25,12 +19,6 @@ module SecureHeaders
|
|
|
25
19
|
config
|
|
26
20
|
end
|
|
27
21
|
|
|
28
|
-
@parsed_ua = if user_agent.is_a?(UserAgent::Browsers::Base)
|
|
29
|
-
user_agent
|
|
30
|
-
else
|
|
31
|
-
UserAgent.parse(user_agent)
|
|
32
|
-
end
|
|
33
|
-
@frame_src = normalize_child_frame_src
|
|
34
22
|
@preserve_schemes = @config.preserve_schemes
|
|
35
23
|
@script_nonce = @config.script_nonce
|
|
36
24
|
@style_nonce = @config.style_nonce
|
|
@@ -55,20 +43,10 @@ module SecureHeaders
|
|
|
55
43
|
|
|
56
44
|
private
|
|
57
45
|
|
|
58
|
-
def normalize_child_frame_src
|
|
59
|
-
if @config.frame_src && @config.child_src && @config.frame_src != @config.child_src
|
|
60
|
-
raise ArgumentError, "#{Kernel.caller.first}: both :child_src and :frame_src supplied and do not match. This can lead to inconsistent behavior across browsers."
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
@config.frame_src || @config.child_src
|
|
64
|
-
end
|
|
65
|
-
|
|
66
46
|
# Private: converts the config object into a string representing a policy.
|
|
67
47
|
# Places default-src at the first directive and report-uri as the last. All
|
|
68
48
|
# others are presented in alphabetical order.
|
|
69
49
|
#
|
|
70
|
-
# Unsupported directives are filtered based on the user agent.
|
|
71
|
-
#
|
|
72
50
|
# Returns a content security policy header value.
|
|
73
51
|
def build_value
|
|
74
52
|
directives.map do |directive_name|
|
|
@@ -124,28 +102,11 @@ module SecureHeaders
|
|
|
124
102
|
#
|
|
125
103
|
# Returns a string representing a directive.
|
|
126
104
|
def build_source_list_directive(directive)
|
|
127
|
-
source_list =
|
|
128
|
-
when :child_src
|
|
129
|
-
if supported_directives.include?(:child_src)
|
|
130
|
-
@frame_src
|
|
131
|
-
end
|
|
132
|
-
when :frame_src
|
|
133
|
-
unless supported_directives.include?(:child_src)
|
|
134
|
-
@frame_src
|
|
135
|
-
end
|
|
136
|
-
else
|
|
137
|
-
@config.directive_value(directive)
|
|
138
|
-
end
|
|
105
|
+
source_list = @config.directive_value(directive)
|
|
139
106
|
|
|
140
107
|
if source_list != OPT_OUT && source_list && source_list.any?
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if minified_source_list =~ /(\n|;)/
|
|
144
|
-
Kernel.warn("#{directive} contains a #{$1} in #{minified_source_list.inspect} which will raise an error in future versions. It has been replaced with a blank space.")
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
escaped_source_list = minified_source_list.gsub(/[\n;]/, " ")
|
|
148
|
-
[symbol_to_hyphen_case(directive), escaped_source_list].join(" ").strip
|
|
108
|
+
normalized_source_list = minify_source_list(directive, source_list)
|
|
109
|
+
[symbol_to_hyphen_case(directive), normalized_source_list].join(" ")
|
|
149
110
|
end
|
|
150
111
|
end
|
|
151
112
|
|
|
@@ -218,23 +179,19 @@ module SecureHeaders
|
|
|
218
179
|
# unsafe-inline, this is more concise.
|
|
219
180
|
def append_nonce(source_list, nonce)
|
|
220
181
|
if nonce
|
|
221
|
-
|
|
222
|
-
source_list << "'nonce-#{nonce}'"
|
|
223
|
-
else
|
|
224
|
-
source_list << UNSAFE_INLINE
|
|
225
|
-
end
|
|
182
|
+
source_list.push("'nonce-#{nonce}'", UNSAFE_INLINE)
|
|
226
183
|
end
|
|
227
184
|
|
|
228
185
|
source_list
|
|
229
186
|
end
|
|
230
187
|
|
|
231
|
-
# Private: return the list of directives
|
|
188
|
+
# Private: return the list of directives,
|
|
232
189
|
# starting with default-src and ending with report-uri.
|
|
233
190
|
def directives
|
|
234
191
|
[
|
|
235
192
|
DEFAULT_SRC,
|
|
236
|
-
BODY_DIRECTIVES
|
|
237
|
-
REPORT_URI
|
|
193
|
+
BODY_DIRECTIVES,
|
|
194
|
+
REPORT_URI,
|
|
238
195
|
].flatten
|
|
239
196
|
end
|
|
240
197
|
|
|
@@ -243,29 +200,6 @@ module SecureHeaders
|
|
|
243
200
|
source_list.map { |source_expression| source_expression.sub(HTTP_SCHEME_REGEX, "") }
|
|
244
201
|
end
|
|
245
202
|
|
|
246
|
-
# Private: determine which directives are supported for the given user agent.
|
|
247
|
-
#
|
|
248
|
-
# Add UA-sniffing special casing here.
|
|
249
|
-
#
|
|
250
|
-
# Returns an array of symbols representing the directives.
|
|
251
|
-
def supported_directives
|
|
252
|
-
@supported_directives ||= if VARIATIONS[@parsed_ua.browser]
|
|
253
|
-
if @parsed_ua.browser == "Firefox" && ((@parsed_ua.version || FALLBACK_VERSION) >= VERSION_46)
|
|
254
|
-
VARIATIONS["FirefoxTransitional"]
|
|
255
|
-
elsif @parsed_ua.browser == "Safari" && ((@parsed_ua.version || FALLBACK_VERSION) >= VERSION_10)
|
|
256
|
-
VARIATIONS["SafariTransitional"]
|
|
257
|
-
else
|
|
258
|
-
VARIATIONS[@parsed_ua.browser]
|
|
259
|
-
end
|
|
260
|
-
else
|
|
261
|
-
VARIATIONS[OTHER]
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def nonces_supported?
|
|
266
|
-
@nonces_supported ||= self.class.nonces_supported?(@parsed_ua)
|
|
267
|
-
end
|
|
268
|
-
|
|
269
203
|
def symbol_to_hyphen_case(sym)
|
|
270
204
|
sym.to_s.tr("_", "-")
|
|
271
205
|
end
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
module SecureHeaders
|
|
3
3
|
module DynamicConfig
|
|
4
4
|
def self.included(base)
|
|
5
|
-
base.send(:attr_writer, :modified)
|
|
6
5
|
base.send(:attr_reader, *base.attrs)
|
|
7
6
|
base.attrs.each do |attr|
|
|
8
7
|
base.send(:define_method, "#{attr}=") do |value|
|
|
@@ -42,7 +41,6 @@ module SecureHeaders
|
|
|
42
41
|
@upgrade_insecure_requests = nil
|
|
43
42
|
|
|
44
43
|
from_hash(hash)
|
|
45
|
-
@modified = false
|
|
46
44
|
end
|
|
47
45
|
|
|
48
46
|
def update_directive(directive, value)
|
|
@@ -55,12 +53,10 @@ module SecureHeaders
|
|
|
55
53
|
end
|
|
56
54
|
end
|
|
57
55
|
|
|
58
|
-
def modified?
|
|
59
|
-
@modified
|
|
60
|
-
end
|
|
61
|
-
|
|
62
56
|
def merge(new_hash)
|
|
63
|
-
|
|
57
|
+
new_config = self.dup
|
|
58
|
+
new_config.send(:from_hash, new_hash)
|
|
59
|
+
new_config
|
|
64
60
|
end
|
|
65
61
|
|
|
66
62
|
def merge!(new_hash)
|
|
@@ -109,17 +105,12 @@ module SecureHeaders
|
|
|
109
105
|
def write_attribute(attr, value)
|
|
110
106
|
value = value.dup if PolicyManagement::DIRECTIVE_VALUE_TYPES[attr] == :source_list
|
|
111
107
|
attr_variable = "@#{attr}"
|
|
112
|
-
prev_value = self.instance_variable_get(attr_variable)
|
|
113
108
|
self.instance_variable_set(attr_variable, value)
|
|
114
|
-
if prev_value != value
|
|
115
|
-
@modified = true
|
|
116
|
-
end
|
|
117
109
|
end
|
|
118
110
|
end
|
|
119
111
|
|
|
120
112
|
class ContentSecurityPolicyConfigError < StandardError; end
|
|
121
113
|
class ContentSecurityPolicyConfig
|
|
122
|
-
CONFIG_KEY = :csp
|
|
123
114
|
HEADER_NAME = "Content-Security-Policy".freeze
|
|
124
115
|
|
|
125
116
|
ATTRS = PolicyManagement::ALL_DIRECTIVES + PolicyManagement::META_CONFIGS + PolicyManagement::NONCES
|
|
@@ -149,7 +140,6 @@ module SecureHeaders
|
|
|
149
140
|
end
|
|
150
141
|
|
|
151
142
|
class ContentSecurityPolicyReportOnlyConfig < ContentSecurityPolicyConfig
|
|
152
|
-
CONFIG_KEY = :csp_report_only
|
|
153
143
|
HEADER_NAME = "Content-Security-Policy-Report-Only".freeze
|
|
154
144
|
|
|
155
145
|
def report_only?
|
|
@@ -4,7 +4,6 @@ module SecureHeaders
|
|
|
4
4
|
|
|
5
5
|
class ExpectCertificateTransparency
|
|
6
6
|
HEADER_NAME = "Expect-CT".freeze
|
|
7
|
-
CONFIG_KEY = :expect_certificate_transparency
|
|
8
7
|
INVALID_CONFIGURATION_ERROR = "config must be a hash.".freeze
|
|
9
8
|
INVALID_ENFORCE_VALUE_ERROR = "enforce must be a boolean".freeze
|
|
10
9
|
REQUIRED_MAX_AGE_ERROR = "max-age is a required directive.".freeze
|
|
@@ -15,8 +14,8 @@ module SecureHeaders
|
|
|
15
14
|
#
|
|
16
15
|
# Returns nil if not configured, returns header name and value if
|
|
17
16
|
# configured.
|
|
18
|
-
def make_header(config)
|
|
19
|
-
return if config.nil?
|
|
17
|
+
def make_header(config, use_agent = nil)
|
|
18
|
+
return if config.nil? || config == OPT_OUT
|
|
20
19
|
|
|
21
20
|
header = new(config)
|
|
22
21
|
[HEADER_NAME, header.value]
|
|
@@ -5,7 +5,6 @@ module SecureHeaders
|
|
|
5
5
|
base.extend(ClassMethods)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
MODERN_BROWSERS = %w(Chrome Opera Firefox)
|
|
9
8
|
DEFAULT_CONFIG = {
|
|
10
9
|
default_src: %w(https:),
|
|
11
10
|
img_src: %w(https: data: 'self'),
|
|
@@ -82,58 +81,12 @@ module SecureHeaders
|
|
|
82
81
|
UPGRADE_INSECURE_REQUESTS
|
|
83
82
|
].flatten.freeze
|
|
84
83
|
|
|
85
|
-
EDGE_DIRECTIVES = DIRECTIVES_1_0
|
|
86
|
-
SAFARI_DIRECTIVES = DIRECTIVES_1_0
|
|
87
|
-
SAFARI_10_DIRECTIVES = DIRECTIVES_2_0
|
|
88
|
-
|
|
89
|
-
FIREFOX_UNSUPPORTED_DIRECTIVES = [
|
|
90
|
-
BLOCK_ALL_MIXED_CONTENT,
|
|
91
|
-
CHILD_SRC,
|
|
92
|
-
WORKER_SRC,
|
|
93
|
-
PLUGIN_TYPES
|
|
94
|
-
].freeze
|
|
95
|
-
|
|
96
|
-
FIREFOX_46_DEPRECATED_DIRECTIVES = [
|
|
97
|
-
FRAME_SRC
|
|
98
|
-
].freeze
|
|
99
|
-
|
|
100
|
-
FIREFOX_46_UNSUPPORTED_DIRECTIVES = [
|
|
101
|
-
BLOCK_ALL_MIXED_CONTENT,
|
|
102
|
-
WORKER_SRC,
|
|
103
|
-
PLUGIN_TYPES
|
|
104
|
-
].freeze
|
|
105
|
-
|
|
106
|
-
FIREFOX_DIRECTIVES = (
|
|
107
|
-
DIRECTIVES_3_0 - FIREFOX_UNSUPPORTED_DIRECTIVES
|
|
108
|
-
).freeze
|
|
109
|
-
|
|
110
|
-
FIREFOX_46_DIRECTIVES = (
|
|
111
|
-
DIRECTIVES_3_0 - FIREFOX_46_UNSUPPORTED_DIRECTIVES - FIREFOX_46_DEPRECATED_DIRECTIVES
|
|
112
|
-
).freeze
|
|
113
|
-
|
|
114
|
-
CHROME_DIRECTIVES = (
|
|
115
|
-
DIRECTIVES_3_0
|
|
116
|
-
).freeze
|
|
117
|
-
|
|
118
84
|
ALL_DIRECTIVES = (DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0).uniq.sort
|
|
119
85
|
|
|
120
86
|
# Think of default-src and report-uri as the beginning and end respectively,
|
|
121
87
|
# everything else is in between.
|
|
122
88
|
BODY_DIRECTIVES = ALL_DIRECTIVES - [DEFAULT_SRC, REPORT_URI]
|
|
123
89
|
|
|
124
|
-
VARIATIONS = {
|
|
125
|
-
"Chrome" => CHROME_DIRECTIVES,
|
|
126
|
-
"Opera" => CHROME_DIRECTIVES,
|
|
127
|
-
"Firefox" => FIREFOX_DIRECTIVES,
|
|
128
|
-
"FirefoxTransitional" => FIREFOX_46_DIRECTIVES,
|
|
129
|
-
"Safari" => SAFARI_DIRECTIVES,
|
|
130
|
-
"SafariTransitional" => SAFARI_10_DIRECTIVES,
|
|
131
|
-
"Edge" => EDGE_DIRECTIVES,
|
|
132
|
-
"Other" => CHROME_DIRECTIVES
|
|
133
|
-
}.freeze
|
|
134
|
-
|
|
135
|
-
OTHER = "Other".freeze
|
|
136
|
-
|
|
137
90
|
DIRECTIVE_VALUE_TYPES = {
|
|
138
91
|
BASE_URI => :source_list,
|
|
139
92
|
BLOCK_ALL_MIXED_CONTENT => :boolean,
|
|
@@ -200,8 +153,9 @@ module SecureHeaders
|
|
|
200
153
|
#
|
|
201
154
|
# Returns a default policy if no configuration is provided, or a
|
|
202
155
|
# header name and value based on the config.
|
|
203
|
-
def make_header(config
|
|
204
|
-
|
|
156
|
+
def make_header(config)
|
|
157
|
+
return if config.nil? || config == OPT_OUT
|
|
158
|
+
header = new(config)
|
|
205
159
|
[header.name, header.value]
|
|
206
160
|
end
|
|
207
161
|
|
|
@@ -215,27 +169,28 @@ module SecureHeaders
|
|
|
215
169
|
if config.directive_value(:script_src).nil?
|
|
216
170
|
raise ContentSecurityPolicyConfigError.new(":script_src is required, falling back to default-src is too dangerous. Use `script_src: OPT_OUT` to override")
|
|
217
171
|
end
|
|
172
|
+
if !config.report_only? && config.directive_value(:report_only)
|
|
173
|
+
raise ContentSecurityPolicyConfigError.new("Only the csp_report_only config should set :report_only to true")
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if config.report_only? && config.directive_value(:report_only) == false
|
|
177
|
+
raise ContentSecurityPolicyConfigError.new("csp_report_only config must have :report_only set to true")
|
|
178
|
+
end
|
|
218
179
|
|
|
219
180
|
ContentSecurityPolicyConfig.attrs.each do |key|
|
|
220
181
|
value = config.directive_value(key)
|
|
221
182
|
next unless value
|
|
183
|
+
|
|
222
184
|
if META_CONFIGS.include?(key)
|
|
223
185
|
raise ContentSecurityPolicyConfigError.new("#{key} must be a boolean value") unless boolean?(value) || value.nil?
|
|
186
|
+
elsif NONCES.include?(key)
|
|
187
|
+
raise ContentSecurityPolicyConfigError.new("#{key} must be a non-nil value") if value.nil?
|
|
224
188
|
else
|
|
225
189
|
validate_directive!(key, value)
|
|
226
190
|
end
|
|
227
191
|
end
|
|
228
192
|
end
|
|
229
193
|
|
|
230
|
-
# Public: check if a user agent supports CSP nonces
|
|
231
|
-
#
|
|
232
|
-
# user_agent - a String or a UserAgent object
|
|
233
|
-
def nonces_supported?(user_agent)
|
|
234
|
-
user_agent = UserAgent.parse(user_agent) if user_agent.is_a?(String)
|
|
235
|
-
MODERN_BROWSERS.include?(user_agent.browser) ||
|
|
236
|
-
user_agent.browser == "Safari" && (user_agent.version || CSP::FALLBACK_VERSION) >= CSP::VERSION_10
|
|
237
|
-
end
|
|
238
|
-
|
|
239
194
|
# Public: combine the values from two different configs.
|
|
240
195
|
#
|
|
241
196
|
# original - the main config
|
|
@@ -302,17 +257,11 @@ module SecureHeaders
|
|
|
302
257
|
# Don't set a default if directive has an existing value
|
|
303
258
|
next if original[directive]
|
|
304
259
|
if FETCH_SOURCES.include?(directive)
|
|
305
|
-
original[directive] =
|
|
260
|
+
original[directive] = original[DEFAULT_SRC]
|
|
306
261
|
end
|
|
307
262
|
end
|
|
308
263
|
end
|
|
309
264
|
|
|
310
|
-
def default_for(directive, original)
|
|
311
|
-
return original[FRAME_SRC] if directive == CHILD_SRC && original[FRAME_SRC]
|
|
312
|
-
return original[CHILD_SRC] if directive == FRAME_SRC && original[CHILD_SRC]
|
|
313
|
-
original[DEFAULT_SRC]
|
|
314
|
-
end
|
|
315
|
-
|
|
316
265
|
def source_list?(directive)
|
|
317
266
|
DIRECTIVE_VALUE_TYPES[directive] == :source_list
|
|
318
267
|
end
|
|
@@ -5,15 +5,14 @@ module SecureHeaders
|
|
|
5
5
|
HEADER_NAME = "Public-Key-Pins".freeze
|
|
6
6
|
REPORT_ONLY = "Public-Key-Pins-Report-Only".freeze
|
|
7
7
|
HASH_ALGORITHMS = [:sha256].freeze
|
|
8
|
-
CONFIG_KEY = :hpkp
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
class << self
|
|
12
11
|
# Public: make an hpkp header name, value pair
|
|
13
12
|
#
|
|
14
13
|
# Returns nil if not configured, returns header name and value if configured.
|
|
15
|
-
def make_header(config)
|
|
16
|
-
return if config.nil?
|
|
14
|
+
def make_header(config, user_agent = nil)
|
|
15
|
+
return if config.nil? || config == OPT_OUT
|
|
17
16
|
header = new(config)
|
|
18
17
|
[header.name, header.value]
|
|
19
18
|
end
|
|
@@ -14,14 +14,14 @@ module SecureHeaders
|
|
|
14
14
|
origin-when-cross-origin
|
|
15
15
|
unsafe-url
|
|
16
16
|
)
|
|
17
|
-
CONFIG_KEY = :referrer_policy
|
|
18
17
|
|
|
19
18
|
class << self
|
|
20
19
|
# Public: generate an Referrer Policy header.
|
|
21
20
|
#
|
|
22
21
|
# Returns a default header if no configuration is provided, or a
|
|
23
22
|
# header name and value based on the config.
|
|
24
|
-
def make_header(config = nil)
|
|
23
|
+
def make_header(config = nil, user_agent = nil)
|
|
24
|
+
return if config == OPT_OUT
|
|
25
25
|
config ||= DEFAULT_VALUE
|
|
26
26
|
[HEADER_NAME, Array(config).join(", ")]
|
|
27
27
|
end
|
|
@@ -8,14 +8,14 @@ module SecureHeaders
|
|
|
8
8
|
DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
|
|
9
9
|
VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
|
|
10
10
|
MESSAGE = "The config value supplied for the HSTS header was invalid. Must match #{VALID_STS_HEADER}"
|
|
11
|
-
CONFIG_KEY = :hsts
|
|
12
11
|
|
|
13
12
|
class << self
|
|
14
13
|
# Public: generate an hsts header name, value pair.
|
|
15
14
|
#
|
|
16
15
|
# Returns a default header if no configuration is provided, or a
|
|
17
16
|
# header name and value based on the config.
|
|
18
|
-
def make_header(config = nil)
|
|
17
|
+
def make_header(config = nil, user_agent = nil)
|
|
18
|
+
return if config == OPT_OUT
|
|
19
19
|
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -5,14 +5,14 @@ module SecureHeaders
|
|
|
5
5
|
class XContentTypeOptions
|
|
6
6
|
HEADER_NAME = "X-Content-Type-Options".freeze
|
|
7
7
|
DEFAULT_VALUE = "nosniff"
|
|
8
|
-
CONFIG_KEY = :x_content_type_options
|
|
9
8
|
|
|
10
9
|
class << self
|
|
11
10
|
# Public: generate an X-Content-Type-Options header.
|
|
12
11
|
#
|
|
13
12
|
# Returns a default header if no configuration is provided, or a
|
|
14
13
|
# header name and value based on the config.
|
|
15
|
-
def make_header(config = nil)
|
|
14
|
+
def make_header(config = nil, user_agent = nil)
|
|
15
|
+
return if config == OPT_OUT
|
|
16
16
|
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -4,14 +4,14 @@ module SecureHeaders
|
|
|
4
4
|
class XDownloadOptions
|
|
5
5
|
HEADER_NAME = "X-Download-Options".freeze
|
|
6
6
|
DEFAULT_VALUE = "noopen"
|
|
7
|
-
CONFIG_KEY = :x_download_options
|
|
8
7
|
|
|
9
8
|
class << self
|
|
10
9
|
# Public: generate an X-Download-Options header.
|
|
11
10
|
#
|
|
12
11
|
# Returns a default header if no configuration is provided, or a
|
|
13
12
|
# header name and value based on the config.
|
|
14
|
-
def make_header(config = nil)
|
|
13
|
+
def make_header(config = nil, user_agent = nil)
|
|
14
|
+
return if config == OPT_OUT
|
|
15
15
|
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -3,7 +3,6 @@ module SecureHeaders
|
|
|
3
3
|
class XFOConfigError < StandardError; end
|
|
4
4
|
class XFrameOptions
|
|
5
5
|
HEADER_NAME = "X-Frame-Options".freeze
|
|
6
|
-
CONFIG_KEY = :x_frame_options
|
|
7
6
|
SAMEORIGIN = "sameorigin"
|
|
8
7
|
DENY = "deny"
|
|
9
8
|
ALLOW_FROM = "allow-from"
|
|
@@ -16,7 +15,7 @@ module SecureHeaders
|
|
|
16
15
|
#
|
|
17
16
|
# Returns a default header if no configuration is provided, or a
|
|
18
17
|
# header name and value based on the config.
|
|
19
|
-
def make_header(config = nil)
|
|
18
|
+
def make_header(config = nil, user_agent = nil)
|
|
20
19
|
return if config == OPT_OUT
|
|
21
20
|
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
22
21
|
end
|
|
@@ -5,14 +5,14 @@ module SecureHeaders
|
|
|
5
5
|
HEADER_NAME = "X-Permitted-Cross-Domain-Policies".freeze
|
|
6
6
|
DEFAULT_VALUE = "none"
|
|
7
7
|
VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
|
|
8
|
-
CONFIG_KEY = :x_permitted_cross_domain_policies
|
|
9
8
|
|
|
10
9
|
class << self
|
|
11
10
|
# Public: generate an X-Permitted-Cross-Domain-Policies header.
|
|
12
11
|
#
|
|
13
12
|
# Returns a default header if no configuration is provided, or a
|
|
14
13
|
# header name and value based on the config.
|
|
15
|
-
def make_header(config = nil)
|
|
14
|
+
def make_header(config = nil, user_agent = nil)
|
|
15
|
+
return if config == OPT_OUT
|
|
16
16
|
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -4,15 +4,15 @@ module SecureHeaders
|
|
|
4
4
|
class XXssProtection
|
|
5
5
|
HEADER_NAME = "X-XSS-Protection".freeze
|
|
6
6
|
DEFAULT_VALUE = "1; mode=block"
|
|
7
|
-
VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/
|
|
8
|
-
CONFIG_KEY = :x_xss_protection
|
|
7
|
+
VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/
|
|
9
8
|
|
|
10
9
|
class << self
|
|
11
10
|
# Public: generate an X-Xss-Protection header.
|
|
12
11
|
#
|
|
13
12
|
# Returns a default header if no configuration is provided, or a
|
|
14
13
|
# header name and value based on the config.
|
|
15
|
-
def make_header(config = nil)
|
|
14
|
+
def make_header(config = nil, user_agent = nil)
|
|
15
|
+
return if config == OPT_OUT
|
|
16
16
|
[HEADER_NAME, config || DEFAULT_VALUE]
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -19,7 +19,7 @@ module SecureHeaders
|
|
|
19
19
|
#
|
|
20
20
|
# Returns an html-safe link tag with the nonce attribute.
|
|
21
21
|
def nonced_stylesheet_link_tag(*args, &block)
|
|
22
|
-
opts = extract_options(args).merge(nonce:
|
|
22
|
+
opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:style))
|
|
23
23
|
|
|
24
24
|
stylesheet_link_tag(*args, opts, &block)
|
|
25
25
|
end
|
|
@@ -37,7 +37,7 @@ module SecureHeaders
|
|
|
37
37
|
#
|
|
38
38
|
# Returns an html-safe script tag with the nonce attribute.
|
|
39
39
|
def nonced_javascript_include_tag(*args, &block)
|
|
40
|
-
opts = extract_options(args).merge(nonce:
|
|
40
|
+
opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:script))
|
|
41
41
|
|
|
42
42
|
javascript_include_tag(*args, opts, &block)
|
|
43
43
|
end
|
|
@@ -47,7 +47,7 @@ module SecureHeaders
|
|
|
47
47
|
#
|
|
48
48
|
# Returns an html-safe script tag with the nonce attribute.
|
|
49
49
|
def nonced_javascript_pack_tag(*args, &block)
|
|
50
|
-
opts = extract_options(args).merge(nonce:
|
|
50
|
+
opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:script))
|
|
51
51
|
|
|
52
52
|
javascript_pack_tag(*args, opts, &block)
|
|
53
53
|
end
|
|
@@ -57,7 +57,7 @@ module SecureHeaders
|
|
|
57
57
|
#
|
|
58
58
|
# Returns an html-safe link tag with the nonce attribute.
|
|
59
59
|
def nonced_stylesheet_pack_tag(*args, &block)
|
|
60
|
-
opts = extract_options(args).merge(nonce:
|
|
60
|
+
opts = extract_options(args).merge(nonce: _content_security_policy_nonce(:style))
|
|
61
61
|
|
|
62
62
|
stylesheet_pack_tag(*args, opts, &block)
|
|
63
63
|
end
|
|
@@ -66,7 +66,7 @@ module SecureHeaders
|
|
|
66
66
|
# Instructs secure_headers to append a nonce to style/script-src directives.
|
|
67
67
|
#
|
|
68
68
|
# Returns a non-html-safe nonce value.
|
|
69
|
-
def
|
|
69
|
+
def _content_security_policy_nonce(type)
|
|
70
70
|
case type
|
|
71
71
|
when :script
|
|
72
72
|
SecureHeaders.content_security_policy_script_nonce(@_request)
|
|
@@ -74,13 +74,14 @@ module SecureHeaders
|
|
|
74
74
|
SecureHeaders.content_security_policy_style_nonce(@_request)
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
|
+
alias_method :content_security_policy_nonce, :_content_security_policy_nonce
|
|
77
78
|
|
|
78
79
|
def content_security_policy_script_nonce
|
|
79
|
-
|
|
80
|
+
_content_security_policy_nonce(:script)
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
def content_security_policy_style_nonce
|
|
83
|
-
|
|
84
|
+
_content_security_policy_nonce(:style)
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
##
|
|
@@ -152,7 +153,7 @@ module SecureHeaders
|
|
|
152
153
|
else
|
|
153
154
|
content_or_options.html_safe # :'(
|
|
154
155
|
end
|
|
155
|
-
content_tag type, content, options.merge(nonce:
|
|
156
|
+
content_tag type, content, options.merge(nonce: _content_security_policy_nonce(type))
|
|
156
157
|
end
|
|
157
158
|
|
|
158
159
|
def extract_options(args)
|