secure_headers 3.0.3 → 4.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +41 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  4. data/.gitignore +0 -9
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +3 -0
  7. data/.ruby-version +1 -1
  8. data/.travis.yml +17 -5
  9. data/CHANGELOG.md +287 -0
  10. data/CODE_OF_CONDUCT.md +46 -0
  11. data/CONTRIBUTING.md +41 -0
  12. data/Gemfile +13 -5
  13. data/Guardfile +1 -0
  14. data/LICENSE +4 -199
  15. data/README.md +85 -265
  16. data/Rakefile +22 -18
  17. data/docs/HPKP.md +17 -0
  18. data/docs/cookies.md +64 -0
  19. data/docs/hashes.md +64 -0
  20. data/docs/named_overrides_and_appends.md +107 -0
  21. data/docs/per_action_configuration.md +133 -0
  22. data/docs/sinatra.md +25 -0
  23. data/lib/secure_headers/configuration.rb +188 -59
  24. data/lib/secure_headers/hash_helper.rb +11 -0
  25. data/lib/secure_headers/headers/clear_site_data.rb +55 -0
  26. data/lib/secure_headers/headers/content_security_policy.rb +136 -325
  27. data/lib/secure_headers/headers/content_security_policy_config.rb +162 -0
  28. data/lib/secure_headers/headers/cookie.rb +144 -0
  29. data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
  30. data/lib/secure_headers/headers/policy_management.rb +410 -0
  31. data/lib/secure_headers/headers/public_key_pins.rb +5 -4
  32. data/lib/secure_headers/headers/referrer_policy.rb +37 -0
  33. data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
  34. data/lib/secure_headers/headers/x_content_type_options.rb +3 -2
  35. data/lib/secure_headers/headers/x_download_options.rb +3 -2
  36. data/lib/secure_headers/headers/x_frame_options.rb +2 -1
  37. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +3 -2
  38. data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
  39. data/lib/secure_headers/middleware.rb +44 -0
  40. data/lib/secure_headers/railtie.rb +11 -6
  41. data/lib/secure_headers/utils/cookies_config.rb +95 -0
  42. data/lib/secure_headers/view_helper.rb +76 -5
  43. data/lib/secure_headers.rb +159 -99
  44. data/lib/tasks/tasks.rake +83 -0
  45. data/secure_headers.gemspec +13 -3
  46. data/spec/lib/secure_headers/configuration_spec.rb +24 -9
  47. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +87 -0
  48. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +88 -189
  49. data/spec/lib/secure_headers/headers/cookie_spec.rb +182 -0
  50. data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
  51. data/spec/lib/secure_headers/headers/policy_management_spec.rb +228 -0
  52. data/spec/lib/secure_headers/headers/public_key_pins_spec.rb +7 -6
  53. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +73 -0
  54. data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +6 -5
  55. data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +2 -1
  56. data/spec/lib/secure_headers/headers/x_download_options_spec.rb +3 -2
  57. data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +2 -1
  58. data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +4 -3
  59. data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +4 -3
  60. data/spec/lib/secure_headers/middleware_spec.rb +96 -6
  61. data/spec/lib/secure_headers/view_helpers_spec.rb +138 -0
  62. data/spec/lib/secure_headers_spec.rb +407 -58
  63. data/spec/spec_helper.rb +19 -12
  64. data/upgrading-to-3-0.md +13 -9
  65. data/upgrading-to-4-0.md +55 -0
  66. metadata +45 -9
  67. data/lib/secure_headers/padrino.rb +0 -13
  68. data/travis.sh +0 -10
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe Configuration do
@@ -29,18 +30,24 @@ module SecureHeaders
29
30
  expect_default_values(header_hash)
30
31
  end
31
32
 
32
- it "copies all config values except for the cached headers when dup" do
33
+ it "copies config values when duping" do
33
34
  Configuration.override(:test_override, Configuration::NOOP_CONFIGURATION) do
34
35
  # do nothing, just copy it
35
36
  end
36
37
 
37
38
  config = Configuration.get(:test_override)
38
39
  noop = Configuration.get(Configuration::NOOP_CONFIGURATION)
39
- [:hsts, :x_frame_options, :x_content_type_options, :x_xss_protection,
40
- :x_download_options, :x_permitted_cross_domain_policies, :hpkp, :csp].each do |key|
40
+ [:csp, :csp_report_only, :cookies].each do |key|
41
+ expect(config.send(key)).to eq(noop.send(key))
42
+ end
43
+ end
41
44
 
42
- expect(config.send(key)).to eq(noop.send(key)), "Value not copied: #{key}."
45
+ it "regenerates cached headers when building an override" do
46
+ Configuration.override(:test_override) do |config|
47
+ config.x_content_type_options = OPT_OUT
43
48
  end
49
+
50
+ expect(Configuration.get.cached_headers).to_not eq(Configuration.get(:test_override).cached_headers)
44
51
  end
45
52
 
46
53
  it "stores an override of the global config" do
@@ -59,12 +66,12 @@ module SecureHeaders
59
66
  default = Configuration.get
60
67
  override = Configuration.get(:override)
61
68
 
62
- expect(override.csp).not_to eq(default.csp)
69
+ expect(override.csp.directive_value(:default_src)).not_to be(default.csp.directive_value(:default_src))
63
70
  end
64
71
 
65
72
  it "allows you to override an override" do
66
73
  Configuration.override(:override) do |config|
67
- config.csp = { default_src: %w('self')}
74
+ config.csp = { default_src: %w('self'), script_src: %w('self')}
68
75
  end
69
76
 
70
77
  Configuration.override(:second_override, :override) do |config|
@@ -72,9 +79,17 @@ module SecureHeaders
72
79
  end
73
80
 
74
81
  original_override = Configuration.get(:override)
75
- expect(original_override.csp).to eq(default_src: %w('self'))
82
+ expect(original_override.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self'))
76
83
  override_config = Configuration.get(:second_override)
77
- expect(override_config.csp).to eq(default_src: %w('self'), script_src: %w(example.org))
84
+ expect(override_config.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self' example.org))
85
+ end
86
+
87
+ it "deprecates the secure_cookies configuration" do
88
+ expect {
89
+ Configuration.default do |config|
90
+ config.secure_cookies = true
91
+ end
92
+ }.to raise_error(ArgumentError)
78
93
  end
79
94
  end
80
95
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ module SecureHeaders
5
+ describe ClearSiteData do
6
+ describe "make_header" do
7
+ it "returns nil with nil config" do
8
+ expect(described_class.make_header).to be_nil
9
+ end
10
+
11
+ it "returns nil with empty config" do
12
+ expect(described_class.make_header([])).to be_nil
13
+ end
14
+
15
+ it "returns nil with opt-out config" do
16
+ expect(described_class.make_header(OPT_OUT)).to be_nil
17
+ end
18
+
19
+ it "returns all types with `true` config" do
20
+ name, value = described_class.make_header(true)
21
+
22
+ expect(name).to eq(ClearSiteData::HEADER_NAME)
23
+ expect(value).to eq(
24
+ %("cache", "cookies", "storage", "executionContexts")
25
+ )
26
+ end
27
+
28
+ it "returns specified types" do
29
+ name, value = described_class.make_header(["foo", "bar"])
30
+
31
+ expect(name).to eq(ClearSiteData::HEADER_NAME)
32
+ expect(value).to eq(%("foo", "bar"))
33
+ end
34
+ end
35
+
36
+ describe "validate_config!" do
37
+ it "succeeds for `true` config" do
38
+ expect do
39
+ described_class.validate_config!(true)
40
+ end.not_to raise_error
41
+ end
42
+
43
+ it "succeeds for `nil` config" do
44
+ expect do
45
+ described_class.validate_config!(nil)
46
+ end.not_to raise_error
47
+ end
48
+
49
+ it "succeeds for opt-out config" do
50
+ expect do
51
+ described_class.validate_config!(OPT_OUT)
52
+ end.not_to raise_error
53
+ end
54
+
55
+ it "succeeds for empty config" do
56
+ expect do
57
+ described_class.validate_config!([])
58
+ end.not_to raise_error
59
+ end
60
+
61
+ it "succeeds for Array of Strings config" do
62
+ expect do
63
+ described_class.validate_config!(["foo"])
64
+ end.not_to raise_error
65
+ end
66
+
67
+ it "fails for Array of non-String config" do
68
+ expect do
69
+ described_class.validate_config!([1])
70
+ end.to raise_error(ClearSiteDataConfigError)
71
+ end
72
+
73
+ it "fails for other types of config" do
74
+ expect do
75
+ described_class.validate_config!(:cookies)
76
+ end.to raise_error(ClearSiteDataConfigError)
77
+ end
78
+ end
79
+
80
+ describe "make_header_value" do
81
+ it "returns a string of quoted values that are comma separated" do
82
+ value = described_class.make_header_value(["foo", "bar"])
83
+ expect(value).to eq(%("foo", "bar"))
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,4 +1,5 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe ContentSecurityPolicy do
@@ -14,192 +15,21 @@ module SecureHeaders
14
15
 
15
16
  describe "#name" do
16
17
  context "when in report-only mode" do
17
- specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(ContentSecurityPolicy::HEADER_NAME + "-Report-Only") }
18
+ specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME) }
18
19
  end
19
20
 
20
21
  context "when in enforce mode" do
21
- specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicy::HEADER_NAME) }
22
+ specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicyConfig::HEADER_NAME) }
22
23
  end
23
24
  end
24
25
 
25
- describe "#validate_config!" do
26
- it "accepts all keys" do
27
- # (pulled from README)
28
- config = {
29
- # "meta" values. these will shaped the header, but the values are not included in the header.
30
- report_only: true, # default: false
31
- preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
32
-
33
- # directive values: these values will directly translate into source directives
34
- default_src: %w(https: 'self'),
35
- frame_src: %w('self' *.twimg.com itunes.apple.com),
36
- connect_src: %w(wws:),
37
- font_src: %w('self' data:),
38
- img_src: %w(mycdn.com data:),
39
- media_src: %w(utoob.com),
40
- object_src: %w('self'),
41
- script_src: %w('self'),
42
- style_src: %w('unsafe-inline'),
43
- base_uri: %w('self'),
44
- child_src: %w('self'),
45
- form_action: %w('self' github.com),
46
- frame_ancestors: %w('none'),
47
- plugin_types: %w(application/x-shockwave-flash),
48
- block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
49
- upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
50
- report_uri: %w(https://example.com/uri-directive)
51
- }
52
-
53
- CSP.validate_config!(config)
54
- end
55
-
56
- it "requires a :default_src value" do
57
- expect do
58
- CSP.validate_config!(script_src: %('self'))
59
- end.to raise_error(ContentSecurityPolicyConfigError)
60
- end
61
-
62
- it "requires :report_only to be a truthy value" do
63
- expect do
64
- CSP.validate_config!(default_opts.merge(report_only: "steve"))
65
- end.to raise_error(ContentSecurityPolicyConfigError)
66
- end
67
-
68
- it "requires :preserve_schemes to be a truthy value" do
69
- expect do
70
- CSP.validate_config!(default_opts.merge(preserve_schemes: "steve"))
71
- end.to raise_error(ContentSecurityPolicyConfigError)
72
- end
73
-
74
- it "requires :block_all_mixed_content to be a boolean value" do
75
- expect do
76
- CSP.validate_config!(default_opts.merge(block_all_mixed_content: "steve"))
77
- end.to raise_error(ContentSecurityPolicyConfigError)
78
- end
79
-
80
- it "requires :upgrade_insecure_requests to be a boolean value" do
81
- expect do
82
- CSP.validate_config!(default_opts.merge(upgrade_insecure_requests: "steve"))
83
- end.to raise_error(ContentSecurityPolicyConfigError)
84
- end
85
-
86
- it "requires all source lists to be an array of strings" do
87
- expect do
88
- CSP.validate_config!(default_src: "steve")
89
- end.to raise_error(ContentSecurityPolicyConfigError)
90
- end
91
-
92
- it "allows nil values" do
93
- expect do
94
- CSP.validate_config!(default_src: %w('self'), script_src: ["https:", nil])
95
- end.to_not raise_error
96
- end
97
-
98
- it "rejects unknown directives / config" do
99
- expect do
100
- CSP.validate_config!(default_src: %w('self'), default_src_totally_mispelled: "steve")
101
- end.to raise_error(ContentSecurityPolicyConfigError)
102
- end
103
-
104
- # this is mostly to ensure people don't use the antiquated shorthands common in other configs
105
- it "performs light validation on source lists" do
106
- expect do
107
- CSP.validate_config!(default_src: %w(self none inline eval))
108
- end.to raise_error(ContentSecurityPolicyConfigError)
109
- end
110
- end
111
-
112
- describe "#combine_policies" do
113
- it "combines the default-src value with the override if the directive was unconfigured" do
114
- combined_config = CSP.combine_policies(Configuration.default.csp, script_src: %w(anothercdn.com))
115
- csp = ContentSecurityPolicy.new(combined_config)
116
- expect(csp.name).to eq(CSP::HEADER_NAME)
117
- expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
118
- end
119
-
120
- it "combines directives where the original value is nil and the hash is frozen" do
121
- Configuration.default do |config|
122
- config.csp = {
123
- default_src: %w('self'),
124
- report_only: false
125
- }.freeze
126
- end
127
- report_uri = "https://report-uri.io/asdf"
128
- combined_config = CSP.combine_policies(Configuration.get.csp, report_uri: [report_uri])
129
- csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
130
- expect(csp.value).to include("report-uri #{report_uri}")
131
- end
132
-
133
- it "does not combine the default-src value for directives that don't fall back to default sources" do
134
- Configuration.default do |config|
135
- config.csp = {
136
- default_src: %w('self'),
137
- report_only: false
138
- }.freeze
139
- end
140
- non_default_source_additions = CSP::NON_DEFAULT_SOURCES.each_with_object({}) do |directive, hash|
141
- hash[directive] = %w("http://example.org)
142
- end
143
- combined_config = CSP.combine_policies(Configuration.get.csp, non_default_source_additions)
144
-
145
- CSP::NON_DEFAULT_SOURCES.each do |directive|
146
- expect(combined_config[directive]).to eq(%w("http://example.org))
147
- end
148
-
149
- ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox]).value
150
- end
151
-
152
- it "overrides the report_only flag" do
153
- Configuration.default do |config|
154
- config.csp = {
155
- default_src: %w('self'),
156
- report_only: false
157
- }
158
- end
159
- combined_config = CSP.combine_policies(Configuration.get.csp, report_only: true)
160
- csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
161
- expect(csp.name).to eq(CSP::REPORT_ONLY)
162
- end
163
-
164
- it "overrides the :block_all_mixed_content flag" do
165
- Configuration.default do |config|
166
- config.csp = {
167
- default_src: %w(https:),
168
- block_all_mixed_content: false
169
- }
170
- end
171
- combined_config = CSP.combine_policies(Configuration.get.csp, block_all_mixed_content: true)
172
- csp = ContentSecurityPolicy.new(combined_config)
173
- expect(csp.value).to eq("default-src https:; block-all-mixed-content")
174
- end
175
-
176
- it "raises an error if appending to a OPT_OUT policy" do
177
- Configuration.default do |config|
178
- config.csp = OPT_OUT
179
- end
180
- expect do
181
- CSP.combine_policies(Configuration.get.csp, script_src: %w(anothercdn.com))
182
- end.to raise_error(ContentSecurityPolicyConfigError)
26
+ describe "#value" do
27
+ it "uses a safe but non-breaking default value" do
28
+ expect(ContentSecurityPolicy.new.value).to eq("default-src https:; form-action 'self'; img-src https: data: 'self'; object-src 'none'; script-src https:; style-src 'self' 'unsafe-inline' https:")
183
29
  end
184
- end
185
30
 
186
- describe "#idempotent_additions?" do
187
- specify { expect(ContentSecurityPolicy.idempotent_additions?(OPT_OUT, script_src: %w(b.com))).to be false }
188
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(c.com))).to be false }
189
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: %w(b.com))).to be false }
190
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(a.com b.com c.com))).to be false }
191
-
192
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com))).to be true }
193
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com a.com))).to be true }
194
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w())).to be true }
195
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: [nil])).to be true }
196
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: [nil])).to be true }
197
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: nil)).to be true }
198
- end
199
-
200
- describe "#value" do
201
31
  it "discards 'none' values if any other source expressions are present" do
202
- csp = ContentSecurityPolicy.new(default_opts.merge(frame_src: %w('self' 'none')))
32
+ csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
203
33
  expect(csp.value).not_to include("'none'")
204
34
  end
205
35
 
@@ -221,13 +51,18 @@ module SecureHeaders
221
51
  expect(csp.value).to eq("default-src example.org")
222
52
  end
223
53
 
54
+ it "does not build directives with a value of OPT_OUT (and bypasses directive requirements)" do
55
+ csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), script_src: OPT_OUT)
56
+ expect(csp.value).to eq("default-src example.org")
57
+ end
58
+
224
59
  it "does not remove schemes from report-uri values" do
225
60
  csp = ContentSecurityPolicy.new(default_src: %w(https:), report_uri: %w(https://example.org))
226
61
  expect(csp.value).to eq("default-src https:; report-uri https://example.org")
227
62
  end
228
63
 
229
64
  it "does not remove schemes when :preserve_schemes is true" do
230
- csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), :preserve_schemes => true)
65
+ csp = ContentSecurityPolicy.new(default_src: %w(https://example.org), preserve_schemes: true)
231
66
  expect(csp.value).to eq("default-src https://example.org")
232
67
  end
233
68
 
@@ -246,42 +81,106 @@ module SecureHeaders
246
81
  expect(csp.value).to eq("default-src example.org")
247
82
  end
248
83
 
84
+ it "does add a boolean directive if the value is true" do
85
+ csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: true)
86
+ expect(csp.value).to eq("default-src example.org; block-all-mixed-content; upgrade-insecure-requests")
87
+ end
88
+
89
+ it "does not add a boolean directive if the value is false" do
90
+ csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: false)
91
+ expect(csp.value).to eq("default-src example.org; block-all-mixed-content")
92
+ end
93
+
249
94
  it "deduplicates any source expressions" do
250
95
  csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org example.org))
251
96
  expect(csp.value).to eq("default-src example.org")
252
97
  end
253
98
 
99
+ it "creates maximally strict sandbox policy when passed no sandbox token values" do
100
+ csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: [])
101
+ expect(csp.value).to eq("default-src example.org; sandbox")
102
+ end
103
+
104
+ it "creates maximally strict sandbox policy when passed true" do
105
+ csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: true)
106
+ expect(csp.value).to eq("default-src example.org; sandbox")
107
+ end
108
+
109
+ it "creates sandbox policy when passed valid sandbox token values" do
110
+ csp = ContentSecurityPolicy.new(default_src: %w(example.org), sandbox: %w(allow-forms allow-scripts))
111
+ expect(csp.value).to eq("default-src example.org; sandbox allow-forms allow-scripts")
112
+ end
113
+
114
+ it "does not emit a warning when using frame-src" do
115
+ expect(Kernel).to_not receive(:warn)
116
+ ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
117
+ end
118
+
119
+ it "raises an error when child-src and frame-src are supplied but are not equal" do
120
+ expect {
121
+ ContentSecurityPolicy.new(default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)).value
122
+ }.to raise_error(ArgumentError)
123
+ end
124
+
125
+ it "supports strict-dynamic" do
126
+ csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456}, USER_AGENTS[:chrome])
127
+ expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
128
+ end
129
+
254
130
  context "browser sniffing" do
255
131
  let (:complex_opts) do
256
- ContentSecurityPolicy::ALL_DIRECTIVES.each_with_object({}) do |directive, hash|
257
- hash[directive] = %w('self')
132
+ (ContentSecurityPolicy::ALL_DIRECTIVES - [:frame_src]).each_with_object({}) do |directive, hash|
133
+ hash[directive] = ["#{directive.to_s.gsub("_", "-")}.com"]
258
134
  end.merge({
259
135
  block_all_mixed_content: true,
260
136
  upgrade_insecure_requests: true,
261
- reflected_xss: "block",
262
- script_src: %w('self'),
263
- script_nonce: 123456
137
+ script_src: %w(script-src.com),
138
+ script_nonce: 123456,
139
+ sandbox: %w(allow-forms),
140
+ plugin_types: %w(application/pdf)
264
141
  })
265
142
  end
266
143
 
267
144
  it "does not filter any directives for Chrome" do
268
145
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
269
- 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'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
146
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
270
147
  end
271
148
 
272
149
  it "does not filter any directives for Opera" do
273
150
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
274
- 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'; media-src 'self'; object-src 'self'; plugin-types 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
151
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
275
152
  end
276
153
 
277
154
  it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
278
155
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
279
- 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'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'nonce-123456'; style-src 'self'; upgrade-insecure-requests; report-uri 'self'")
156
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
157
+ end
158
+
159
+ it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
160
+ policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
161
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
162
+ end
163
+
164
+ it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for Edge" do
165
+ policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
166
+ expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
280
167
  end
281
168
 
282
- it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
169
+ it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
283
170
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
284
- expect(policy.value).to eq("default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'self'; sandbox 'self'; script-src 'self' 'unsafe-inline'; style-src 'self'; report-uri 'self'")
171
+ expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
172
+ end
173
+
174
+ it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, nonce sources, and hash sources for safari 10 and higher" do
175
+ policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari10])
176
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types application/pdf; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; report-uri report-uri.com")
177
+ end
178
+
179
+ it "falls back to standard Firefox defaults when the useragent version is not present" do
180
+ ua = USER_AGENTS[:firefox].dup
181
+ allow(ua).to receive(:version).and_return(nil)
182
+ policy = ContentSecurityPolicy.new(complex_opts, ua)
183
+ expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; manifest-src manifest-src.com; media-src media-src.com; object-src object-src.com; sandbox allow-forms; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
285
184
  end
286
185
  end
287
186
  end