secure_headers 2.4.0 → 2.5.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 +1 -0
- data/Gemfile +1 -0
- data/Guardfile +12 -0
- data/README.md +3 -2
- data/lib/secure_headers/headers/content_security_policy.rb +119 -47
- data/lib/secure_headers/headers/strict_transport_security.rb +1 -0
- data/lib/secure_headers/headers/x_content_type_options.rb +1 -0
- data/lib/secure_headers/headers/x_download_options.rb +1 -0
- data/lib/secure_headers/headers/x_frame_options.rb +1 -0
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +1 -0
- data/lib/secure_headers/headers/x_xss_protection.rb +1 -0
- data/lib/secure_headers/version.rb +1 -1
- data/lib/secure_headers/view_helper.rb +0 -1
- data/lib/secure_headers.rb +15 -6
- data/secure_headers.gemspec +1 -0
- data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +0 -1
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +34 -4
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -0
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +2 -0
- data/spec/lib/secure_headers_spec.rb +24 -6
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 36b98616c83ec1049b8f1bcc25671738da8d8bed
|
|
4
|
+
data.tar.gz: 730108ec10da36aeeb59049e88122a99987d1dcc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 854d9e8c9de09d5515dad3449726b4b2308b8a25b5dc702ed80da47fd3f94f983119531a4089877b215ee07dccfa8fec2766107891b018bcca376ec16ed95e8f
|
|
7
|
+
data.tar.gz: f0ec419ac593cdeb556dc852d6e2a3b8f5fcfe83c07b34ac8392fcb912004bbe1117ea9e473c068230a38c44c96a5e036dfde630ae5e7397203c9aed8babd741
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Guardfile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
guard :rspec, cmd: "bundle exec rspec", all_on_start: true, all_after_pass: true do
|
|
2
|
+
require "guard/rspec/dsl"
|
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
|
4
|
+
|
|
5
|
+
# RSpec files
|
|
6
|
+
rspec = dsl.rspec
|
|
7
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
|
8
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
|
9
|
+
watch(rspec.spec_files)
|
|
10
|
+
|
|
11
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
|
12
|
+
end
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The gem will automatically apply several headers that are related to security.
|
|
|
8
8
|
- X-Content-Type-Options - [Prevent content type sniffing](http://msdn.microsoft.com/en-us/library/ie/gg622941\(v=vs.85\).aspx)
|
|
9
9
|
- X-Download-Options - [Prevent file downloads opening](http://msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx)
|
|
10
10
|
- X-Permitted-Cross-Domain-Policies - [Restrict Adobe Flash Player's access to data](https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html)
|
|
11
|
-
- Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorities. [Public Key
|
|
11
|
+
- Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorities. [Public Key Pinning Specification](https://tools.ietf.org/html/rfc7469)
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
@@ -61,6 +61,7 @@ The following methods are going to be called, unless they are provided in a `ski
|
|
|
61
61
|
:form_action => "'self' github.com",
|
|
62
62
|
:frame_ancestors => "'none'",
|
|
63
63
|
:plugin_types => 'application/x-shockwave-flash',
|
|
64
|
+
:block_all_mixed_content => '' # see [http://www.w3.org/TR/mixed-content/]()
|
|
64
65
|
:report_uri => '//example.com/uri-directive'
|
|
65
66
|
}
|
|
66
67
|
config.hpkp = {
|
|
@@ -99,7 +100,7 @@ Sometimes you need to override your content security policy for a given endpoint
|
|
|
99
100
|
1. Override the `secure_header_options_for` class instance method. e.g.
|
|
100
101
|
|
|
101
102
|
```ruby
|
|
102
|
-
class SomethingController < ApplicationController
|
|
103
|
+
class SomethingController < ApplicationController
|
|
103
104
|
def wumbus
|
|
104
105
|
# gets style-src override
|
|
105
106
|
end
|
|
@@ -11,7 +11,9 @@ module SecureHeaders
|
|
|
11
11
|
DEFAULT_CSP_HEADER = "default-src https: data: 'unsafe-inline' 'unsafe-eval'; frame-src https: about: javascript:; img-src data:"
|
|
12
12
|
HEADER_NAME = "Content-Security-Policy"
|
|
13
13
|
ENV_KEY = 'secure_headers.content_security_policy'
|
|
14
|
-
|
|
14
|
+
USER_AGENT_PARSER = UserAgentParser::Parser.new
|
|
15
|
+
|
|
16
|
+
DIRECTIVES_1_0 = [
|
|
15
17
|
:default_src,
|
|
16
18
|
:connect_src,
|
|
17
19
|
:font_src,
|
|
@@ -19,20 +21,53 @@ module SecureHeaders
|
|
|
19
21
|
:img_src,
|
|
20
22
|
:media_src,
|
|
21
23
|
:object_src,
|
|
24
|
+
:sandbox,
|
|
22
25
|
:script_src,
|
|
23
26
|
:style_src,
|
|
27
|
+
:report_uri
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
DIRECTIVES_2_0 = [
|
|
31
|
+
DIRECTIVES_1_0,
|
|
24
32
|
:base_uri,
|
|
25
33
|
:child_src,
|
|
26
34
|
:form_action,
|
|
27
35
|
:frame_ancestors,
|
|
28
36
|
:plugin_types
|
|
29
|
-
]
|
|
37
|
+
].flatten.freeze
|
|
30
38
|
|
|
31
|
-
OTHER = [
|
|
32
|
-
:report_uri
|
|
33
|
-
]
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
# All the directives currently under consideration for CSP level 3.
|
|
41
|
+
# https://w3c.github.io/webappsec/specs/CSP2/
|
|
42
|
+
DIRECTIVES_3_0 = [
|
|
43
|
+
DIRECTIVES_2_0,
|
|
44
|
+
:manifest_src,
|
|
45
|
+
:reflected_xss
|
|
46
|
+
].flatten.freeze
|
|
47
|
+
|
|
48
|
+
# All the directives that are not currently in a formal spec, but have
|
|
49
|
+
# been implemented somewhere.
|
|
50
|
+
DIRECTIVES_DRAFT = [
|
|
51
|
+
:block_all_mixed_content,
|
|
52
|
+
].freeze
|
|
53
|
+
|
|
54
|
+
SAFARI_DIRECTIVES = DIRECTIVES_1_0
|
|
55
|
+
|
|
56
|
+
FIREFOX_UNSUPPORTED_DIRECTIVES = [
|
|
57
|
+
:block_all_mixed_content,
|
|
58
|
+
:child_src,
|
|
59
|
+
:plugin_types
|
|
60
|
+
].freeze
|
|
61
|
+
|
|
62
|
+
FIREFOX_DIRECTIVES = (
|
|
63
|
+
DIRECTIVES_2_0 - FIREFOX_UNSUPPORTED_DIRECTIVES
|
|
64
|
+
).freeze
|
|
65
|
+
|
|
66
|
+
CHROME_DIRECTIVES = (
|
|
67
|
+
DIRECTIVES_2_0 + DIRECTIVES_DRAFT
|
|
68
|
+
).freeze
|
|
69
|
+
|
|
70
|
+
ALL_DIRECTIVES = [DIRECTIVES_1_0 + DIRECTIVES_2_0 + DIRECTIVES_3_0 + DIRECTIVES_DRAFT].flatten.uniq.sort
|
|
36
71
|
CONFIG_KEY = :csp
|
|
37
72
|
end
|
|
38
73
|
|
|
@@ -102,10 +137,18 @@ module SecureHeaders
|
|
|
102
137
|
|
|
103
138
|
# Config values can be string, array, or lamdba values
|
|
104
139
|
@config = config.inject({}) do |hash, (key, value)|
|
|
105
|
-
config_val = value.respond_to?(:call)
|
|
140
|
+
config_val = if value.respond_to?(:call)
|
|
141
|
+
warn "[DEPRECATION] secure_headers 3.x will not support procs as config values."
|
|
142
|
+
value.call(@controller)
|
|
143
|
+
else
|
|
144
|
+
value
|
|
145
|
+
end
|
|
106
146
|
|
|
107
|
-
if
|
|
108
|
-
|
|
147
|
+
if ALL_DIRECTIVES.include?(key.to_sym) # directives need to be normalized to arrays of strings
|
|
148
|
+
if config_val.is_a? String
|
|
149
|
+
warn "[DEPRECATION] A String was supplied for directive #{key}. secure_headers 3.x will require all directives to be arrays of strings."
|
|
150
|
+
config_val = config_val.split
|
|
151
|
+
end
|
|
109
152
|
if config_val.is_a?(Array)
|
|
110
153
|
config_val = config_val.map do |val|
|
|
111
154
|
translate_dir_value(val)
|
|
@@ -118,14 +161,36 @@ module SecureHeaders
|
|
|
118
161
|
end
|
|
119
162
|
|
|
120
163
|
@http_additions = @config.delete(:http_additions)
|
|
121
|
-
@app_name = @config.delete(:app_name)
|
|
122
|
-
@report_uri = @config.delete(:report_uri)
|
|
123
|
-
@enforce = !!@config.delete(:enforce)
|
|
124
164
|
@disable_img_src_data_uri = !!@config.delete(:disable_img_src_data_uri)
|
|
125
165
|
@tag_report_uri = !!@config.delete(:tag_report_uri)
|
|
126
166
|
@script_hashes = @config.delete(:script_hashes) || []
|
|
167
|
+
@app_name = @config.delete(:app_name)
|
|
168
|
+
@app_name = @app_name.call(@controller) if @app_name.respond_to?(:call)
|
|
169
|
+
@enforce = @config.delete(:enforce)
|
|
170
|
+
@enforce = @enforce.call(@controller) if @enforce.respond_to?(:call)
|
|
171
|
+
@enforce = !!@enforce
|
|
172
|
+
|
|
173
|
+
# normalize and tag the report-uri
|
|
174
|
+
if @config[:report_uri]
|
|
175
|
+
@config[:report_uri] = @config[:report_uri].map do |report_uri|
|
|
176
|
+
if report_uri.start_with?('//')
|
|
177
|
+
report_uri = if @ssl_request
|
|
178
|
+
"https:" + report_uri
|
|
179
|
+
else
|
|
180
|
+
"http:" + report_uri
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
if @tag_report_uri
|
|
185
|
+
report_uri = "#{report_uri}?enforce=#{@enforce}"
|
|
186
|
+
report_uri += "&app_name=#{@app_name}" if @app_name
|
|
187
|
+
end
|
|
188
|
+
report_uri
|
|
189
|
+
end
|
|
190
|
+
end
|
|
127
191
|
|
|
128
192
|
add_script_hashes if @script_hashes.any?
|
|
193
|
+
strip_unsupported_directives
|
|
129
194
|
end
|
|
130
195
|
|
|
131
196
|
##
|
|
@@ -160,13 +225,20 @@ module SecureHeaders
|
|
|
160
225
|
|
|
161
226
|
def to_json
|
|
162
227
|
build_value
|
|
163
|
-
@config.
|
|
228
|
+
@config.inject({}) do |hash, (key, value)|
|
|
229
|
+
if ALL_DIRECTIVES.include?(key)
|
|
230
|
+
hash[key.to_s.gsub(/(\w+)_(\w+)/, "\\1-\\2")] = value
|
|
231
|
+
end
|
|
232
|
+
hash
|
|
233
|
+
end.to_json
|
|
164
234
|
end
|
|
165
235
|
|
|
166
236
|
def self.from_json(*json_configs)
|
|
167
237
|
json_configs.inject({}) do |combined_config, one_config|
|
|
168
|
-
|
|
169
|
-
|
|
238
|
+
config = JSON.parse(one_config).inject({}) do |hash, (key, value)|
|
|
239
|
+
hash[key.gsub(/(\w+)-(\w+)/, "\\1_\\2").to_sym] = value
|
|
240
|
+
hash
|
|
241
|
+
end
|
|
170
242
|
combined_config.merge(config) do |_, lhs, rhs|
|
|
171
243
|
lhs | rhs
|
|
172
244
|
end
|
|
@@ -182,10 +254,7 @@ module SecureHeaders
|
|
|
182
254
|
def build_value
|
|
183
255
|
raise "Expected to find default_src directive value" unless @config[:default_src]
|
|
184
256
|
append_http_additions unless ssl_request?
|
|
185
|
-
|
|
186
|
-
generic_directives,
|
|
187
|
-
report_uri_directive
|
|
188
|
-
].join.strip
|
|
257
|
+
generic_directives
|
|
189
258
|
end
|
|
190
259
|
|
|
191
260
|
def append_http_additions
|
|
@@ -198,13 +267,13 @@ module SecureHeaders
|
|
|
198
267
|
|
|
199
268
|
def translate_dir_value val
|
|
200
269
|
if %w{inline eval}.include?(val)
|
|
201
|
-
warn "[DEPRECATION] using inline/eval
|
|
270
|
+
warn "[DEPRECATION] using inline/eval is not suppored in secure_headers 3.x. Instead use 'unsafe-inline'/'unsafe-eval' instead."
|
|
202
271
|
val == 'inline' ? "'unsafe-inline'" : "'unsafe-eval'"
|
|
203
272
|
elsif %{self none}.include?(val)
|
|
204
|
-
warn "[DEPRECATION] using self/none
|
|
273
|
+
warn "[DEPRECATION] using self/none is not suppored in secure_headers 3.x. Instead use 'self'/'none' instead."
|
|
205
274
|
"'#{val}'"
|
|
206
275
|
elsif val == 'nonce'
|
|
207
|
-
if supports_nonces?
|
|
276
|
+
if supports_nonces?
|
|
208
277
|
self.class.set_nonce(@controller, nonce)
|
|
209
278
|
["'nonce-#{nonce}'", "'unsafe-inline'"]
|
|
210
279
|
else
|
|
@@ -215,27 +284,9 @@ module SecureHeaders
|
|
|
215
284
|
end
|
|
216
285
|
end
|
|
217
286
|
|
|
218
|
-
|
|
219
|
-
return '' if @report_uri.nil?
|
|
220
|
-
|
|
221
|
-
if @report_uri.start_with?('//')
|
|
222
|
-
@report_uri = if @ssl_request
|
|
223
|
-
"https:" + @report_uri
|
|
224
|
-
else
|
|
225
|
-
"http:" + @report_uri
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
if @tag_report_uri
|
|
230
|
-
@report_uri = "#{@report_uri}?enforce=#{@enforce}"
|
|
231
|
-
@report_uri += "&app_name=#{@app_name}" if @app_name
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
"report-uri #{@report_uri};"
|
|
235
|
-
end
|
|
236
|
-
|
|
287
|
+
# ensures defualt_src is first and report_uri is last
|
|
237
288
|
def generic_directives
|
|
238
|
-
header_value =
|
|
289
|
+
header_value = build_directive(:default_src)
|
|
239
290
|
data_uri = @disable_img_src_data_uri ? [] : ["data:"]
|
|
240
291
|
if @config[:img_src]
|
|
241
292
|
@config[:img_src] = @config[:img_src] + data_uri unless @config[:img_src].include?('data:')
|
|
@@ -243,19 +294,40 @@ module SecureHeaders
|
|
|
243
294
|
@config[:img_src] = @config[:default_src] + data_uri
|
|
244
295
|
end
|
|
245
296
|
|
|
246
|
-
|
|
247
|
-
|
|
297
|
+
(ALL_DIRECTIVES - [:default_src, :report_uri]).each do |directive_name|
|
|
298
|
+
if @config[directive_name]
|
|
299
|
+
header_value += build_directive(directive_name)
|
|
300
|
+
end
|
|
248
301
|
end
|
|
249
302
|
|
|
250
|
-
header_value
|
|
303
|
+
header_value += build_directive(:report_uri) if @config[:report_uri]
|
|
304
|
+
|
|
305
|
+
header_value.strip
|
|
251
306
|
end
|
|
252
307
|
|
|
253
308
|
def build_directive(key)
|
|
254
309
|
"#{self.class.symbol_to_hyphen_case(key)} #{@config[key].join(" ")}; "
|
|
255
310
|
end
|
|
256
311
|
|
|
257
|
-
def
|
|
258
|
-
|
|
312
|
+
def strip_unsupported_directives
|
|
313
|
+
@config.select! { |key, _| supported_directives.include?(key) }
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def supported_directives
|
|
317
|
+
@supported_directives ||= case USER_AGENT_PARSER.parse(@ua).family
|
|
318
|
+
when "Chrome"
|
|
319
|
+
CHROME_DIRECTIVES
|
|
320
|
+
when "Safari"
|
|
321
|
+
SAFARI_DIRECTIVES
|
|
322
|
+
when "Firefox"
|
|
323
|
+
FIREFOX_DIRECTIVES
|
|
324
|
+
else
|
|
325
|
+
DIRECTIVES_1_0
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def supports_nonces?
|
|
330
|
+
parsed_ua = USER_AGENT_PARSER.parse(@ua)
|
|
259
331
|
["Chrome", "Opera", "Firefox"].include?(parsed_ua.family)
|
|
260
332
|
end
|
|
261
333
|
end
|
|
@@ -41,6 +41,7 @@ module SecureHeaders
|
|
|
41
41
|
|
|
42
42
|
def validate_config
|
|
43
43
|
if @config.is_a? Hash
|
|
44
|
+
warn "[DEPRECATION] secure_headers 3.0 will only accept string values for StrictTransportSecurity config"
|
|
44
45
|
if !@config[:max_age]
|
|
45
46
|
raise STSBuildError.new("No max-age was supplied.")
|
|
46
47
|
elsif @config[:max_age].to_s !~ /\A\d+\z/
|
|
@@ -25,6 +25,7 @@ module SecureHeaders
|
|
|
25
25
|
when String
|
|
26
26
|
@config
|
|
27
27
|
else
|
|
28
|
+
warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XXssProtection config"
|
|
28
29
|
value = @config[:value].to_s
|
|
29
30
|
value += "; mode=#{@config[:mode]}" if @config[:mode]
|
|
30
31
|
value += "; report=#{@config[:report_uri]}" if @config[:report_uri]
|
data/lib/secure_headers.rb
CHANGED
|
@@ -33,12 +33,18 @@ module SecureHeaders
|
|
|
33
33
|
:x_xss_protection, :csp, :x_download_options, :script_hashes,
|
|
34
34
|
:x_permitted_cross_domain_policies, :hpkp
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
# For preparation for the secure_headers 3.x change.
|
|
37
|
+
def default &block
|
|
37
38
|
instance_eval &block
|
|
38
39
|
if File.exists?(SCRIPT_HASH_CONFIG_FILE)
|
|
39
40
|
::SecureHeaders::Configuration.script_hashes = YAML.load(File.open(SCRIPT_HASH_CONFIG_FILE))
|
|
40
41
|
end
|
|
41
42
|
end
|
|
43
|
+
|
|
44
|
+
def configure &block
|
|
45
|
+
warn "[DEPRECATION] `configure` is removed in secure_headers 3.x. Instead use `default`."
|
|
46
|
+
default &block
|
|
47
|
+
end
|
|
42
48
|
end
|
|
43
49
|
end
|
|
44
50
|
|
|
@@ -52,21 +58,23 @@ module SecureHeaders
|
|
|
52
58
|
|
|
53
59
|
def header_hash(options = nil)
|
|
54
60
|
ALL_HEADER_CLASSES.inject({}) do |memo, klass|
|
|
55
|
-
|
|
61
|
+
# must use !options[key].nil? because 'false' represents opting out, nil
|
|
62
|
+
# represents use global default.
|
|
63
|
+
config = if options.is_a?(Hash) && !options[klass::Constants::CONFIG_KEY].nil?
|
|
56
64
|
options[klass::Constants::CONFIG_KEY]
|
|
57
65
|
else
|
|
58
66
|
::SecureHeaders::Configuration.send(klass::Constants::CONFIG_KEY)
|
|
59
67
|
end
|
|
60
68
|
|
|
61
69
|
unless klass == SecureHeaders::PublicKeyPins && !config.is_a?(Hash)
|
|
62
|
-
header = get_a_header(klass
|
|
63
|
-
memo[header.name] = header.value
|
|
70
|
+
header = get_a_header(klass, config)
|
|
71
|
+
memo[header.name] = header.value if header
|
|
64
72
|
end
|
|
65
73
|
memo
|
|
66
74
|
end
|
|
67
75
|
end
|
|
68
76
|
|
|
69
|
-
def get_a_header(
|
|
77
|
+
def get_a_header(klass, options)
|
|
70
78
|
return if options == false
|
|
71
79
|
klass.new(options)
|
|
72
80
|
end
|
|
@@ -204,13 +212,14 @@ module SecureHeaders
|
|
|
204
212
|
# we can't use ||= because I'm overloading false => disable, nil => default
|
|
205
213
|
# both of which trigger the conditional assignment
|
|
206
214
|
def secure_header_options_for(type, options)
|
|
215
|
+
warn "[DEPRECATION] secure_header_options_for will not be supported in secure_headers 3.x."
|
|
207
216
|
options.nil? ? ::SecureHeaders::Configuration.send(type) : options
|
|
208
217
|
end
|
|
209
218
|
|
|
210
219
|
def set_a_header(name, klass, options=nil)
|
|
211
220
|
options = secure_header_options_for(name, options)
|
|
212
221
|
return if options == false
|
|
213
|
-
set_header(SecureHeaders::get_a_header(
|
|
222
|
+
set_header(SecureHeaders::get_a_header(klass, options))
|
|
214
223
|
end
|
|
215
224
|
|
|
216
225
|
def set_header(name_or_header, value=nil)
|
data/secure_headers.gemspec
CHANGED
|
@@ -20,4 +20,5 @@ Gem::Specification.new do |gem|
|
|
|
20
20
|
gem.require_paths = ["lib"]
|
|
21
21
|
gem.add_development_dependency "rake"
|
|
22
22
|
gem.add_dependency "user_agent_parser"
|
|
23
|
+
gem.post_install_message = "[DEPRECATION] Development has stopped on the 2.x line of secure_headers. It will be maintained, but new features will be added to the 3.x branch. A lot has changed in secure_headers 3.x. A migration guide can be found in the documentation: https://github.com/twitter/secureheaders/blob/master/upgrading-to-3-0.md."
|
|
23
24
|
end
|
|
@@ -5,9 +5,10 @@ module SecureHeaders
|
|
|
5
5
|
let(:default_opts) do
|
|
6
6
|
{
|
|
7
7
|
:default_src => 'https:',
|
|
8
|
-
:
|
|
8
|
+
:img_src => "https: data:",
|
|
9
9
|
:script_src => "'unsafe-inline' 'unsafe-eval' https: data:",
|
|
10
|
-
:style_src => "'unsafe-inline' https: about:"
|
|
10
|
+
:style_src => "'unsafe-inline' https: about:",
|
|
11
|
+
:report_uri => '/csp_report'
|
|
11
12
|
}
|
|
12
13
|
end
|
|
13
14
|
let(:controller) { DummyClass.new }
|
|
@@ -58,7 +59,7 @@ module SecureHeaders
|
|
|
58
59
|
|
|
59
60
|
it "exports a policy to JSON" do
|
|
60
61
|
policy = ContentSecurityPolicy.new(default_opts)
|
|
61
|
-
expected = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"],"style-src":["'unsafe-inline'","https:","about:"],"
|
|
62
|
+
expected = %({"default-src":["https:"],"img-src":["https:","data:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"],"style-src":["'unsafe-inline'","https:","about:"],"report-uri":["/csp_report"]})
|
|
62
63
|
expect(policy.to_json).to eq(expected)
|
|
63
64
|
end
|
|
64
65
|
|
|
@@ -141,6 +142,35 @@ module SecureHeaders
|
|
|
141
142
|
end
|
|
142
143
|
|
|
143
144
|
describe "#value" do
|
|
145
|
+
it "does not mutate shared state" do
|
|
146
|
+
opts = default_opts.merge(enforce: true)
|
|
147
|
+
policy = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
148
|
+
expect(policy.name).to eq("Content-Security-Policy")
|
|
149
|
+
policy = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
|
150
|
+
expect(policy.name).to eq("Content-Security-Policy")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
context "browser sniffing" do
|
|
154
|
+
let(:complex_opts) do
|
|
155
|
+
ALL_DIRECTIVES.inject({}) { |memo, directive| memo[directive] = "'self'"; memo }.merge(:block_all_mixed_content => '')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "does not filter any directives for Chrome" do
|
|
159
|
+
policy = ContentSecurityPolicy.new(complex_opts, :request => request_for(CHROME))
|
|
160
|
+
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; block-all-mixed-content ; child-src 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self' data:; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self'; style-src 'self'; report-uri 'self';")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
|
|
164
|
+
policy = ContentSecurityPolicy.new(complex_opts, :request => request_for(FIREFOX))
|
|
165
|
+
expect(policy.value).to eq("default-src 'self'; base-uri 'self'; connect-src 'self'; font-src 'self'; form-action 'self'; frame-ancestors 'self'; frame-src 'self'; img-src 'self' data:; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self'; style-src 'self'; report-uri 'self';")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "filters base-uri, blocked-all-mixed-content, child-src, form-action, frame-ancestors, and plugin-types for safari" do
|
|
169
|
+
policy = ContentSecurityPolicy.new(complex_opts, :request => request_for(SAFARI))
|
|
170
|
+
expect(policy.value).to eq("default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self' data:; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self'; style-src 'self'; report-uri 'self';")
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
144
174
|
it "raises an exception when default-src is missing" do
|
|
145
175
|
csp = ContentSecurityPolicy.new({:script_src => 'anything'}, :request => request_for(CHROME))
|
|
146
176
|
expect {
|
|
@@ -248,7 +278,7 @@ module SecureHeaders
|
|
|
248
278
|
|
|
249
279
|
it "adds directive values for headers on http" do
|
|
250
280
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
251
|
-
expect(csp.value).to eq("default-src https:; frame-src http:; img-src
|
|
281
|
+
expect(csp.value).to eq("default-src https:; frame-src http:; img-src https: data: http:; script-src 'unsafe-inline' 'unsafe-eval' https: data:; style-src 'unsafe-inline' https: about:; report-uri /csp_report;")
|
|
252
282
|
end
|
|
253
283
|
|
|
254
284
|
it "does not add the directive values if requesting https" do
|
|
@@ -8,6 +8,7 @@ describe SecureHeaders do
|
|
|
8
8
|
let(:request) {double(:ssl? => true, :url => 'https://example.com')}
|
|
9
9
|
|
|
10
10
|
before(:each) do
|
|
11
|
+
reset_config
|
|
11
12
|
stub_user_agent(nil)
|
|
12
13
|
allow(headers).to receive(:[])
|
|
13
14
|
allow(subject).to receive(:response).and_return(response)
|
|
@@ -19,7 +20,7 @@ describe SecureHeaders do
|
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def reset_config
|
|
22
|
-
::SecureHeaders::Configuration.
|
|
23
|
+
::SecureHeaders::Configuration.default do |config|
|
|
23
24
|
config.hpkp = nil
|
|
24
25
|
config.hsts = nil
|
|
25
26
|
config.x_frame_options = nil
|
|
@@ -138,7 +139,7 @@ describe SecureHeaders do
|
|
|
138
139
|
|
|
139
140
|
context "when disabled by configuration settings" do
|
|
140
141
|
it "does not set any headers when disabled" do
|
|
141
|
-
::SecureHeaders::Configuration.
|
|
142
|
+
::SecureHeaders::Configuration.default do |config|
|
|
142
143
|
config.hsts = false
|
|
143
144
|
config.hpkp = false
|
|
144
145
|
config.x_frame_options = false
|
|
@@ -166,13 +167,30 @@ describe SecureHeaders do
|
|
|
166
167
|
end
|
|
167
168
|
|
|
168
169
|
it "produces a hash of headers given a hash as config" do
|
|
169
|
-
hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:"
|
|
170
|
+
hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:"})
|
|
170
171
|
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
|
|
171
172
|
expect_default_values(hash)
|
|
172
173
|
end
|
|
173
174
|
|
|
175
|
+
it "allows opting out" do
|
|
176
|
+
hash = SecureHeaders::header_hash(:csp => false, :hpkp => false)
|
|
177
|
+
expect(hash['Content-Security-Policy-Report-Only']).to be_nil
|
|
178
|
+
expect(hash['Content-Security-Policy']).to be_nil
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it "allows opting out with config" do
|
|
182
|
+
::SecureHeaders::Configuration.default do |config|
|
|
183
|
+
config.hsts = false
|
|
184
|
+
config.csp = false
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
hash = SecureHeaders::header_hash
|
|
188
|
+
expect(hash['Content-Security-Policy-Report-Only']).to be_nil
|
|
189
|
+
expect(hash['Content-Security-Policy']).to be_nil
|
|
190
|
+
end
|
|
191
|
+
|
|
174
192
|
it "produces a hash with a mix of config values, override values, and default values" do
|
|
175
|
-
::SecureHeaders::Configuration.
|
|
193
|
+
::SecureHeaders::Configuration.default do |config|
|
|
176
194
|
config.hsts = { :max_age => '123456'}
|
|
177
195
|
config.hpkp = {
|
|
178
196
|
:enforce => true,
|
|
@@ -186,8 +204,8 @@ describe SecureHeaders do
|
|
|
186
204
|
}
|
|
187
205
|
end
|
|
188
206
|
|
|
189
|
-
hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:"
|
|
190
|
-
::SecureHeaders::Configuration.
|
|
207
|
+
hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:"})
|
|
208
|
+
::SecureHeaders::Configuration.default do |config|
|
|
191
209
|
config.hsts = nil
|
|
192
210
|
config.hpkp = nil
|
|
193
211
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: secure_headers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Neil Matatall
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2016-01-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- ".ruby-version"
|
|
51
51
|
- ".travis.yml"
|
|
52
52
|
- Gemfile
|
|
53
|
+
- Guardfile
|
|
53
54
|
- LICENSE
|
|
54
55
|
- README.md
|
|
55
56
|
- Rakefile
|
|
@@ -170,7 +171,10 @@ homepage: https://github.com/twitter/secureheaders
|
|
|
170
171
|
licenses:
|
|
171
172
|
- Apache Public License 2.0
|
|
172
173
|
metadata: {}
|
|
173
|
-
post_install_message:
|
|
174
|
+
post_install_message: "[DEPRECATION] Development has stopped on the 2.x line of secure_headers.
|
|
175
|
+
It will be maintained, but new features will be added to the 3.x branch. A lot has
|
|
176
|
+
changed in secure_headers 3.x. A migration guide can be found in the documentation:
|
|
177
|
+
https://github.com/twitter/secureheaders/blob/master/upgrading-to-3-0.md."
|
|
174
178
|
rdoc_options: []
|
|
175
179
|
require_paths:
|
|
176
180
|
- lib
|