secure_headers 3.4.1 → 3.5.0.pre
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.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +32 -3
- data/lib/secure_headers.rb +122 -56
- data/lib/secure_headers/configuration.rb +56 -35
- data/lib/secure_headers/headers/content_security_policy.rb +60 -35
- data/lib/secure_headers/headers/content_security_policy_config.rb +128 -0
- data/lib/secure_headers/headers/policy_management.rb +13 -21
- data/secure_headers.gemspec +1 -1
- data/spec/lib/secure_headers/configuration_spec.rb +5 -5
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +2 -2
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +25 -34
- data/spec/lib/secure_headers/middleware_spec.rb +1 -1
- data/spec/lib/secure_headers/view_helpers_spec.rb +9 -6
- data/spec/lib/secure_headers_spec.rb +236 -58
- data/spec/spec_helper.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a34fab69fada104ff674198e9ee40271bcc86aea
|
4
|
+
data.tar.gz: c95c8b38f37b47dd66cf202c83fd4923b1aa3df9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93d7bc9412154b01231352eb801f9a6a72438d6ce442dc70e8f11a7d013a466ab3fcadfdea6abda7642361e6baa509c1a4087da0630f5263d26c42b5c384a890
|
7
|
+
data.tar.gz: 403ac9a435812bfbbe7ccd4edeb96d646975ab769582680d19c58c48145609cb06ff1095a683e4f6853d74dd4688a7fb99f35f56147a583af6835b2126c957c3
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -36,7 +36,7 @@ SecureHeaders::Configuration.default do |config|
|
|
36
36
|
secure: true, # mark all cookies as "Secure"
|
37
37
|
httponly: true, # mark all cookies as "HttpOnly"
|
38
38
|
samesite: {
|
39
|
-
|
39
|
+
lax: true # mark all cookies as SameSite=lax
|
40
40
|
}
|
41
41
|
}
|
42
42
|
config.hsts = "max-age=#{20.years.to_i}; includeSubdomains; preload"
|
@@ -48,7 +48,7 @@ SecureHeaders::Configuration.default do |config|
|
|
48
48
|
config.referrer_policy = "origin-when-cross-origin"
|
49
49
|
config.csp = {
|
50
50
|
# "meta" values. these will shaped the header, but the values are not included in the header.
|
51
|
-
report_only: true, # default: false
|
51
|
+
report_only: true, # default: false [DEPRECATED: instead, configure csp_report_only]
|
52
52
|
preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
|
53
53
|
|
54
54
|
# directive values: these values will directly translate into source directives
|
@@ -69,6 +69,10 @@ SecureHeaders::Configuration.default do |config|
|
|
69
69
|
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
|
70
70
|
report_uri: %w(https://report-uri.io/example-csp)
|
71
71
|
}
|
72
|
+
config.csp_report_only = config.csp.merge({
|
73
|
+
img_src: %w(somewhereelse.com),
|
74
|
+
report_uri: %w(https://report-uri.io/example-csp-report-only)
|
75
|
+
})
|
72
76
|
config.hpkp = {
|
73
77
|
report_only: false,
|
74
78
|
max_age: 60.days.to_i,
|
@@ -92,7 +96,32 @@ use SecureHeaders::Middleware
|
|
92
96
|
|
93
97
|
## Default values
|
94
98
|
|
95
|
-
All headers except for PublicKeyPins have a default value. See the [corresponding classes for their defaults](https://github.com/twitter/secureheaders/tree/master/lib/secure_headers/headers).
|
99
|
+
All headers except for PublicKeyPins have a default value. See the [corresponding classes for their defaults](https://github.com/twitter/secureheaders/tree/master/lib/secure_headers/headers). The default set of headers is:
|
100
|
+
|
101
|
+
```
|
102
|
+
Content-Security-Policy: default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'
|
103
|
+
Strict-Transport-Security: max-age=631138519
|
104
|
+
X-Content-Type-Options: nosniff
|
105
|
+
X-Download-Options: noopen
|
106
|
+
X-Frame-Options: sameorigin
|
107
|
+
X-Permitted-Cross-Domain-Policies: none
|
108
|
+
X-Xss-Protection: 1; mode=block
|
109
|
+
```
|
110
|
+
|
111
|
+
### Default CSP
|
112
|
+
|
113
|
+
By default, the above CSP will be applied to all requests. If you **only** want to set a Report-Only header, opt-out of the default enforced header for clarity. The configuration will assume that if you only supply `csp_report_only` that you intended to opt-out of `csp` but that's for the sake of backwards compatibility and it will be removed in the future.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
Configuration.default do |config|
|
117
|
+
config.csp = SecureHeaders::OPT_OUT # If this line is omitted, we will assume you meant to opt out.
|
118
|
+
config.csp_report_only = {
|
119
|
+
default_src: %w('self')
|
120
|
+
}
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
If **
|
96
125
|
|
97
126
|
## Named Appends
|
98
127
|
|
data/lib/secure_headers.rb
CHANGED
@@ -14,18 +14,43 @@ require "secure_headers/middleware"
|
|
14
14
|
require "secure_headers/railtie"
|
15
15
|
require "secure_headers/view_helper"
|
16
16
|
require "useragent"
|
17
|
+
require "singleton"
|
17
18
|
|
18
19
|
# All headers (except for hpkp) have a default value. Provide SecureHeaders::OPT_OUT
|
19
20
|
# or ":optout_of_protection" as a config value to disable a given header
|
20
21
|
module SecureHeaders
|
21
|
-
|
22
|
+
class NoOpHeaderConfig
|
23
|
+
include Singleton
|
24
|
+
|
25
|
+
def boom(arg = nil)
|
26
|
+
raise "Illegal State: attempted to modify NoOpHeaderConfig. Create a new config instead."
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_h
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
|
33
|
+
def dup
|
34
|
+
self.class.instance
|
35
|
+
end
|
36
|
+
|
37
|
+
def opt_out?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :[], :boom
|
42
|
+
alias_method :[]=, :boom
|
43
|
+
alias_method :keys, :boom
|
44
|
+
end
|
45
|
+
|
46
|
+
OPT_OUT = NoOpHeaderConfig.instance
|
22
47
|
SECURE_HEADERS_CONFIG = "secure_headers_request_config".freeze
|
23
48
|
NONCE_KEY = "secure_headers_content_security_policy_nonce".freeze
|
24
49
|
HTTPS = "https".freeze
|
25
|
-
CSP = ContentSecurityPolicy
|
26
50
|
|
27
51
|
ALL_HEADER_CLASSES = [
|
28
|
-
|
52
|
+
ContentSecurityPolicyConfig,
|
53
|
+
ContentSecurityPolicyReportOnlyConfig,
|
29
54
|
StrictTransportSecurity,
|
30
55
|
PublicKeyPins,
|
31
56
|
ReferrerPolicy,
|
@@ -36,7 +61,10 @@ module SecureHeaders
|
|
36
61
|
XXssProtection
|
37
62
|
].freeze
|
38
63
|
|
39
|
-
ALL_HEADERS_BESIDES_CSP = (
|
64
|
+
ALL_HEADERS_BESIDES_CSP = (
|
65
|
+
ALL_HEADER_CLASSES -
|
66
|
+
[ContentSecurityPolicyConfig, ContentSecurityPolicyReportOnlyConfig]
|
67
|
+
).freeze
|
40
68
|
|
41
69
|
# Headers set on http requests (excludes STS and HPKP)
|
42
70
|
HTTP_HEADER_CLASSES =
|
@@ -50,13 +78,25 @@ module SecureHeaders
|
|
50
78
|
#
|
51
79
|
# additions - a hash containing directives. e.g.
|
52
80
|
# script_src: %w(another-host.com)
|
53
|
-
def override_content_security_policy_directives(request, additions)
|
54
|
-
config =
|
55
|
-
|
56
|
-
|
81
|
+
def override_content_security_policy_directives(request, additions, target = nil)
|
82
|
+
config, target = config_and_target(request, target)
|
83
|
+
|
84
|
+
if [:both, :enforced].include?(target)
|
85
|
+
if config.csp.opt_out?
|
86
|
+
config.csp = ContentSecurityPolicyConfig.new({})
|
87
|
+
end
|
88
|
+
|
89
|
+
config.csp.merge!(additions)
|
90
|
+
end
|
91
|
+
|
92
|
+
if [:both, :report_only].include?(target)
|
93
|
+
if config.csp_report_only.opt_out?
|
94
|
+
config.csp_report_only = ContentSecurityPolicyReportOnlyConfig.new({})
|
95
|
+
end
|
96
|
+
|
97
|
+
config.csp_report_only.merge!(additions)
|
57
98
|
end
|
58
99
|
|
59
|
-
config.dynamic_csp = config.current_csp.merge(additions)
|
60
100
|
override_secure_headers_request_config(request, config)
|
61
101
|
end
|
62
102
|
|
@@ -66,9 +106,17 @@ module SecureHeaders
|
|
66
106
|
#
|
67
107
|
# additions - a hash containing directives. e.g.
|
68
108
|
# script_src: %w(another-host.com)
|
69
|
-
def append_content_security_policy_directives(request, additions)
|
70
|
-
config =
|
71
|
-
|
109
|
+
def append_content_security_policy_directives(request, additions, target = nil)
|
110
|
+
config, target = config_and_target(request, target)
|
111
|
+
|
112
|
+
if [:both, :enforced].include?(target) && !config.csp.opt_out?
|
113
|
+
config.csp.append(additions)
|
114
|
+
end
|
115
|
+
|
116
|
+
if [:both, :report_only].include?(target) && !config.csp_report_only.opt_out?
|
117
|
+
config.csp_report_only.append(additions)
|
118
|
+
end
|
119
|
+
|
72
120
|
override_secure_headers_request_config(request, config)
|
73
121
|
end
|
74
122
|
|
@@ -112,12 +160,28 @@ module SecureHeaders
|
|
112
160
|
# returned is meant to be merged into the header value from `@app.call(env)`
|
113
161
|
# in Rack middleware.
|
114
162
|
def header_hash_for(request)
|
115
|
-
config = config_for(request)
|
116
|
-
|
117
|
-
|
163
|
+
config = config_for(request, prevent_dup = true)
|
164
|
+
headers = config.cached_headers
|
165
|
+
user_agent = UserAgent.parse(request.user_agent)
|
166
|
+
|
167
|
+
if !config.csp.opt_out? && config.csp.modified?
|
168
|
+
headers = update_cached_csp(config.csp, headers, user_agent)
|
118
169
|
end
|
119
170
|
|
120
|
-
|
171
|
+
if !config.csp_report_only.opt_out? && config.csp_report_only.modified?
|
172
|
+
headers = update_cached_csp(config.csp_report_only, headers, user_agent)
|
173
|
+
end
|
174
|
+
|
175
|
+
header_classes_for(request).each_with_object({}) do |klass, hash|
|
176
|
+
if header = headers[klass::CONFIG_KEY]
|
177
|
+
header_name, value = if [ContentSecurityPolicyConfig, ContentSecurityPolicyReportOnlyConfig].include?(klass)
|
178
|
+
csp_header_for_ua(header, user_agent)
|
179
|
+
else
|
180
|
+
header
|
181
|
+
end
|
182
|
+
hash[header_name] = value
|
183
|
+
end
|
184
|
+
end
|
121
185
|
end
|
122
186
|
|
123
187
|
# Public: specify which named override will be used for this request.
|
@@ -138,7 +202,7 @@ module SecureHeaders
|
|
138
202
|
#
|
139
203
|
# Returns the nonce
|
140
204
|
def content_security_policy_script_nonce(request)
|
141
|
-
content_security_policy_nonce(request,
|
205
|
+
content_security_policy_nonce(request, ContentSecurityPolicy::SCRIPT_SRC)
|
142
206
|
end
|
143
207
|
|
144
208
|
# Public: gets or creates a nonce for CSP.
|
@@ -147,7 +211,7 @@ module SecureHeaders
|
|
147
211
|
#
|
148
212
|
# Returns the nonce
|
149
213
|
def content_security_policy_style_nonce(request)
|
150
|
-
content_security_policy_nonce(request,
|
214
|
+
content_security_policy_nonce(request, ContentSecurityPolicy::STYLE_SRC)
|
151
215
|
end
|
152
216
|
|
153
217
|
# Public: Retreives the config for a given header type:
|
@@ -155,11 +219,15 @@ module SecureHeaders
|
|
155
219
|
# Checks to see if there is an override for this request, then
|
156
220
|
# Checks to see if a named override is used for this request, then
|
157
221
|
# Falls back to the global config
|
158
|
-
def config_for(request)
|
222
|
+
def config_for(request, prevent_dup = false)
|
159
223
|
config = request.env[SECURE_HEADERS_CONFIG] ||
|
160
224
|
Configuration.get(Configuration::DEFAULT_CONFIG)
|
161
225
|
|
162
|
-
|
226
|
+
|
227
|
+
# Global configs are frozen, per-request configs are not. When we're not
|
228
|
+
# making modifications to the config, prevent_dup ensures we don't dup
|
229
|
+
# the object unnecessarily. It's not necessarily frozen to begin with.
|
230
|
+
if config.frozen? && !prevent_dup
|
163
231
|
config.dup
|
164
232
|
else
|
165
233
|
config
|
@@ -167,13 +235,38 @@ module SecureHeaders
|
|
167
235
|
end
|
168
236
|
|
169
237
|
private
|
238
|
+
TARGETS = [:both, :enforced, :report_only]
|
239
|
+
def raise_on_unknown_target(target)
|
240
|
+
unless TARGETS.include?(target)
|
241
|
+
raise "Unrecognized target: #{target}. Must be [:both, :enforced, :report_only]"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def config_and_target(request, target)
|
246
|
+
config = config_for(request)
|
247
|
+
target = guess_target(config) unless target
|
248
|
+
raise_on_unknown_target(target)
|
249
|
+
[config, target]
|
250
|
+
end
|
251
|
+
|
252
|
+
def guess_target(config)
|
253
|
+
if !config.csp.opt_out? && !config.csp_report_only.opt_out?
|
254
|
+
:both
|
255
|
+
elsif !config.csp.opt_out?
|
256
|
+
:enforced
|
257
|
+
elsif !config.csp_report_only.opt_out?
|
258
|
+
:report_only
|
259
|
+
else
|
260
|
+
:both
|
261
|
+
end
|
262
|
+
end
|
170
263
|
|
171
264
|
# Private: gets or creates a nonce for CSP.
|
172
265
|
#
|
173
266
|
# Returns the nonce
|
174
267
|
def content_security_policy_nonce(request, script_or_style)
|
175
268
|
request.env[NONCE_KEY] ||= SecureRandom.base64(32).chomp
|
176
|
-
nonce_key = script_or_style ==
|
269
|
+
nonce_key = script_or_style == ContentSecurityPolicy::SCRIPT_SRC ? :script_nonce : :style_nonce
|
177
270
|
append_content_security_policy_directives(request, nonce_key => request.env[NONCE_KEY])
|
178
271
|
request.env[NONCE_KEY]
|
179
272
|
end
|
@@ -198,21 +291,12 @@ module SecureHeaders
|
|
198
291
|
end
|
199
292
|
end
|
200
293
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
if header = headers[klass::CONFIG_KEY]
|
208
|
-
header_name, value = if klass == CSP
|
209
|
-
csp_header_for_ua(header, request)
|
210
|
-
else
|
211
|
-
header
|
212
|
-
end
|
213
|
-
hash[header_name] = value
|
214
|
-
end
|
215
|
-
end
|
294
|
+
def update_cached_csp(config, headers, user_agent)
|
295
|
+
headers = Configuration.send(:deep_copy, headers)
|
296
|
+
headers[config.class::CONFIG_KEY] = {}
|
297
|
+
variation = ContentSecurityPolicy.ua_to_variation(user_agent)
|
298
|
+
headers[config.class::CONFIG_KEY][variation] = ContentSecurityPolicy.make_header(config, user_agent)
|
299
|
+
headers
|
216
300
|
end
|
217
301
|
|
218
302
|
# Private: chooses the applicable CSP header for the provided user agent.
|
@@ -220,26 +304,8 @@ module SecureHeaders
|
|
220
304
|
# headers - a hash of header_config_key => [header_name, header_value]
|
221
305
|
#
|
222
306
|
# Returns a CSP [header, value] array
|
223
|
-
def csp_header_for_ua(headers,
|
224
|
-
headers[
|
225
|
-
end
|
226
|
-
|
227
|
-
# Private: optionally build a header with a given configure
|
228
|
-
#
|
229
|
-
# klass - corresponding Class for a given header
|
230
|
-
# config - A string, symbol, or hash config for the header
|
231
|
-
# user_agent - A string representing the UA (only used for CSP feature sniffing)
|
232
|
-
#
|
233
|
-
# Returns a 2 element array [header_name, header_value] or nil if config
|
234
|
-
# is OPT_OUT
|
235
|
-
def make_header(klass, header_config, user_agent = nil)
|
236
|
-
unless header_config == OPT_OUT
|
237
|
-
if klass == CSP
|
238
|
-
klass.make_header(header_config, user_agent)
|
239
|
-
else
|
240
|
-
klass.make_header(header_config)
|
241
|
-
end
|
242
|
-
end
|
307
|
+
def csp_header_for_ua(headers, user_agent)
|
308
|
+
headers[ContentSecurityPolicy.ua_to_variation(user_agent)]
|
243
309
|
end
|
244
310
|
end
|
245
311
|
|
@@ -85,7 +85,6 @@ module SecureHeaders
|
|
85
85
|
ALL_HEADER_CLASSES.each do |klass|
|
86
86
|
config.send("#{klass::CONFIG_KEY}=", OPT_OUT)
|
87
87
|
end
|
88
|
-
config.dynamic_csp = OPT_OUT
|
89
88
|
end
|
90
89
|
|
91
90
|
add_configuration(NOOP_CONFIGURATION, noop_config)
|
@@ -94,6 +93,7 @@ module SecureHeaders
|
|
94
93
|
# Public: perform a basic deep dup. The shallow copy provided by dup/clone
|
95
94
|
# can lead to modifying parent objects.
|
96
95
|
def deep_copy(config)
|
96
|
+
return unless config
|
97
97
|
config.each_with_object({}) do |(key, value), hash|
|
98
98
|
hash[key] = if value.is_a?(Array)
|
99
99
|
value.dup
|
@@ -114,13 +114,11 @@ module SecureHeaders
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
-
attr_accessor :dynamic_csp
|
118
|
-
|
119
117
|
attr_writer :hsts, :x_frame_options, :x_content_type_options,
|
120
118
|
:x_xss_protection, :x_download_options, :x_permitted_cross_domain_policies,
|
121
119
|
:referrer_policy
|
122
120
|
|
123
|
-
attr_reader :cached_headers, :csp, :cookies, :hpkp, :hpkp_report_host
|
121
|
+
attr_reader :cached_headers, :csp, :cookies, :csp_report_only, :hpkp, :hpkp_report_host
|
124
122
|
|
125
123
|
HASH_CONFIG_FILE = ENV["secure_headers_generated_hashes_file"] || "config/secure_headers_generated_hashes.yml"
|
126
124
|
if File.exists?(HASH_CONFIG_FILE)
|
@@ -132,7 +130,8 @@ module SecureHeaders
|
|
132
130
|
def initialize(&block)
|
133
131
|
self.hpkp = OPT_OUT
|
134
132
|
self.referrer_policy = OPT_OUT
|
135
|
-
self.csp =
|
133
|
+
self.csp = ContentSecurityPolicyConfig.new(ContentSecurityPolicyConfig::DEFAULT)
|
134
|
+
self.csp_report_only = OPT_OUT
|
136
135
|
instance_eval &block if block_given?
|
137
136
|
end
|
138
137
|
|
@@ -142,8 +141,8 @@ module SecureHeaders
|
|
142
141
|
def dup
|
143
142
|
copy = self.class.new
|
144
143
|
copy.cookies = @cookies
|
145
|
-
copy.csp =
|
146
|
-
copy.
|
144
|
+
copy.csp = @csp.dup if @csp
|
145
|
+
copy.csp_report_only = @csp_report_only.dup if @csp_report_only
|
147
146
|
copy.cached_headers = self.class.send(:deep_copy_if_hash, @cached_headers)
|
148
147
|
copy.x_content_type_options = @x_content_type_options
|
149
148
|
copy.hsts = @hsts
|
@@ -159,9 +158,6 @@ module SecureHeaders
|
|
159
158
|
|
160
159
|
def opt_out(header)
|
161
160
|
send("#{header}=", OPT_OUT)
|
162
|
-
if header == CSP::CONFIG_KEY
|
163
|
-
dynamic_csp = OPT_OUT
|
164
|
-
end
|
165
161
|
self.cached_headers.delete(header)
|
166
162
|
end
|
167
163
|
|
@@ -170,20 +166,6 @@ module SecureHeaders
|
|
170
166
|
self.cached_headers[XFrameOptions::CONFIG_KEY] = XFrameOptions.make_header(value)
|
171
167
|
end
|
172
168
|
|
173
|
-
# Public: generated cached headers for a specific user agent.
|
174
|
-
def rebuild_csp_header_cache!(user_agent)
|
175
|
-
self.cached_headers[CSP::CONFIG_KEY] = {}
|
176
|
-
unless current_csp == OPT_OUT
|
177
|
-
user_agent = UserAgent.parse(user_agent)
|
178
|
-
variation = CSP.ua_to_variation(user_agent)
|
179
|
-
self.cached_headers[CSP::CONFIG_KEY][variation] = CSP.make_header(current_csp, user_agent)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def current_csp
|
184
|
-
@dynamic_csp || @csp
|
185
|
-
end
|
186
|
-
|
187
169
|
# Public: validates all configurations values.
|
188
170
|
#
|
189
171
|
# Raises various configuration errors if any invalid config is detected.
|
@@ -192,6 +174,7 @@ module SecureHeaders
|
|
192
174
|
def validate_config!
|
193
175
|
StrictTransportSecurity.validate_config!(@hsts)
|
194
176
|
ContentSecurityPolicy.validate_config!(@csp)
|
177
|
+
ContentSecurityPolicy.validate_config!(@csp_report_only)
|
195
178
|
ReferrerPolicy.validate_config!(@referrer_policy)
|
196
179
|
XFrameOptions.validate_config!(@x_frame_options)
|
197
180
|
XContentTypeOptions.validate_config!(@x_content_type_options)
|
@@ -207,16 +190,50 @@ module SecureHeaders
|
|
207
190
|
@cookies = (@cookies || {}).merge(secure: secure_cookies)
|
208
191
|
end
|
209
192
|
|
210
|
-
protected
|
211
|
-
|
212
193
|
def csp=(new_csp)
|
213
|
-
if
|
214
|
-
|
194
|
+
if new_csp.respond_to?(:opt_out?)
|
195
|
+
@csp = new_csp.dup
|
196
|
+
else
|
197
|
+
if new_csp[:report_only]
|
198
|
+
# Deprecated configuration implies that CSPRO should be set, CSP should not - so opt out
|
199
|
+
Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#csp=` was supplied a config with report_only: true. Use #csp_report_only="
|
200
|
+
@csp = OPT_OUT
|
201
|
+
self.csp_report_only = new_csp
|
202
|
+
else
|
203
|
+
@csp = ContentSecurityPolicyConfig.new(new_csp)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Configures the Content-Security-Policy-Report-Only header. `new_csp` cannot
|
209
|
+
# contain `report_only: false` or an error will be raised.
|
210
|
+
#
|
211
|
+
# NOTE: if csp has not been configured/has the default value when
|
212
|
+
# configuring csp_report_only, the code will assume you mean to only use
|
213
|
+
# report-only mode and you will be opted-out of enforce mode.
|
214
|
+
def csp_report_only=(new_csp)
|
215
|
+
@csp_report_only = begin
|
216
|
+
if new_csp.is_a?(ContentSecurityPolicyConfig)
|
217
|
+
new_csp.make_report_only
|
218
|
+
elsif new_csp.respond_to?(:opt_out?)
|
219
|
+
new_csp.dup
|
220
|
+
else
|
221
|
+
if new_csp[:report_only] == false # nil is a valid value on which we do not want to raise
|
222
|
+
raise ContentSecurityPolicyConfigError, "`#csp_report_only=` was supplied a config with report_only: false. Use #csp="
|
223
|
+
else
|
224
|
+
ContentSecurityPolicyReportOnlyConfig.new(new_csp)
|
225
|
+
end
|
226
|
+
end
|
215
227
|
end
|
216
228
|
|
217
|
-
@csp
|
229
|
+
if !@csp_report_only.opt_out? && @csp.to_h == ContentSecurityPolicyConfig::DEFAULT
|
230
|
+
Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#csp_report_only=` was configured before `#csp=`. It is assumed you intended to opt out of `#csp=` so be sure to add `config.csp = SecureHeaders::OPT_OUT` to your config. Ensure that #csp_report_only is configured after #csp="
|
231
|
+
@csp = OPT_OUT
|
232
|
+
end
|
218
233
|
end
|
219
234
|
|
235
|
+
protected
|
236
|
+
|
220
237
|
def cookies=(cookies)
|
221
238
|
@cookies = cookies
|
222
239
|
end
|
@@ -269,12 +286,16 @@ module SecureHeaders
|
|
269
286
|
#
|
270
287
|
# Returns nothing
|
271
288
|
def generate_csp_headers(headers)
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
289
|
+
generate_csp_headers_for_config(headers, ContentSecurityPolicyConfig::CONFIG_KEY, self.csp)
|
290
|
+
generate_csp_headers_for_config(headers, ContentSecurityPolicyReportOnlyConfig::CONFIG_KEY, self.csp_report_only)
|
291
|
+
end
|
292
|
+
|
293
|
+
def generate_csp_headers_for_config(headers, header_key, csp_config)
|
294
|
+
unless csp_config.opt_out?
|
295
|
+
headers[header_key] = {}
|
296
|
+
ContentSecurityPolicy::VARIATIONS.each do |name, _|
|
297
|
+
csp = ContentSecurityPolicy.make_header(csp_config, UserAgent.parse(name))
|
298
|
+
headers[header_key][name] = csp.freeze
|
278
299
|
end
|
279
300
|
end
|
280
301
|
end
|