secure_headers 3.2.0 → 3.7.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 (50) 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/.rspec +1 -0
  5. data/.ruby-version +1 -1
  6. data/.travis.yml +2 -0
  7. data/CHANGELOG.md +140 -1
  8. data/CODE_OF_CONDUCT.md +46 -0
  9. data/CONTRIBUTING.md +41 -0
  10. data/Gemfile +3 -1
  11. data/LICENSE +4 -199
  12. data/README.md +74 -334
  13. data/docs/HPKP.md +17 -0
  14. data/docs/cookies.md +50 -0
  15. data/docs/hashes.md +64 -0
  16. data/docs/named_overrides_and_appends.md +107 -0
  17. data/docs/per_action_configuration.md +105 -0
  18. data/docs/sinatra.md +25 -0
  19. data/lib/secure_headers/configuration.rb +120 -39
  20. data/lib/secure_headers/headers/clear_site_data.rb +54 -0
  21. data/lib/secure_headers/headers/content_security_policy.rb +89 -32
  22. data/lib/secure_headers/headers/content_security_policy_config.rb +161 -0
  23. data/lib/secure_headers/headers/expect_certificate_transparency.rb +70 -0
  24. data/lib/secure_headers/headers/policy_management.rb +84 -49
  25. data/lib/secure_headers/headers/public_key_pins.rb +1 -1
  26. data/lib/secure_headers/headers/referrer_policy.rb +36 -0
  27. data/lib/secure_headers/headers/strict_transport_security.rb +1 -1
  28. data/lib/secure_headers/headers/x_content_type_options.rb +1 -1
  29. data/lib/secure_headers/headers/x_download_options.rb +1 -1
  30. data/lib/secure_headers/headers/x_frame_options.rb +1 -1
  31. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +1 -1
  32. data/lib/secure_headers/headers/x_xss_protection.rb +1 -1
  33. data/lib/secure_headers/middleware.rb +6 -0
  34. data/lib/secure_headers/railtie.rb +1 -1
  35. data/lib/secure_headers/view_helper.rb +13 -7
  36. data/lib/secure_headers.rb +138 -55
  37. data/lib/tasks/tasks.rake +5 -4
  38. data/secure_headers.gemspec +2 -2
  39. data/spec/lib/secure_headers/configuration_spec.rb +5 -5
  40. data/spec/lib/secure_headers/headers/clear_site_data_spec.rb +86 -0
  41. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +69 -12
  42. data/spec/lib/secure_headers/headers/expect_certificate_spec.rb +42 -0
  43. data/spec/lib/secure_headers/headers/policy_management_spec.rb +28 -36
  44. data/spec/lib/secure_headers/headers/referrer_policy_spec.rb +72 -0
  45. data/spec/lib/secure_headers/middleware_spec.rb +31 -1
  46. data/spec/lib/secure_headers/view_helpers_spec.rb +19 -7
  47. data/spec/lib/secure_headers_spec.rb +344 -44
  48. data/spec/spec_helper.rb +8 -3
  49. data/upgrading-to-3-0.md +13 -10
  50. metadata +24 -4
@@ -14,17 +14,17 @@ module SecureHeaders
14
14
 
15
15
  describe "#name" do
16
16
  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") }
17
+ specify { expect(ContentSecurityPolicy.new(default_opts.merge(report_only: true)).name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME) }
18
18
  end
19
19
 
20
20
  context "when in enforce mode" do
21
- specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicy::HEADER_NAME) }
21
+ specify { expect(ContentSecurityPolicy.new(default_opts).name).to eq(ContentSecurityPolicyConfig::HEADER_NAME) }
22
22
  end
23
23
  end
24
24
 
25
25
  describe "#value" do
26
26
  it "discards 'none' values if any other source expressions are present" do
27
- csp = ContentSecurityPolicy.new(default_opts.merge(frame_src: %w('self' 'none')))
27
+ csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
28
28
  expect(csp.value).not_to include("'none'")
29
29
  end
30
30
 
@@ -71,42 +71,99 @@ module SecureHeaders
71
71
  expect(csp.value).to eq("default-src example.org")
72
72
  end
73
73
 
74
+ it "does add a boolean directive if the value is true" do
75
+ csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: true)
76
+ expect(csp.value).to eq("default-src example.org; block-all-mixed-content; upgrade-insecure-requests")
77
+ end
78
+
79
+ it "does not add a boolean directive if the value is false" do
80
+ csp = ContentSecurityPolicy.new(default_src: ["https://example.org"], block_all_mixed_content: true, upgrade_insecure_requests: false)
81
+ expect(csp.value).to eq("default-src example.org; block-all-mixed-content")
82
+ end
83
+
74
84
  it "deduplicates any source expressions" do
75
85
  csp = ContentSecurityPolicy.new(default_src: %w(example.org example.org example.org))
76
86
  expect(csp.value).to eq("default-src example.org")
77
87
  end
78
88
 
89
+ it "does not emit a warning when using frame-src" do
90
+ expect(Kernel).to_not receive(:warn)
91
+ ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
92
+ end
93
+
94
+ it "emits a warning when child-src and frame-src are supplied but are not equal" do
95
+ expect(Kernel).to receive(:warn).with(/both :child_src and :frame_src supplied and do not match./)
96
+ ContentSecurityPolicy.new(default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)).value
97
+ end
98
+
99
+ it "will still set inconsistent child/frame-src values to be less surprising" do
100
+ expect(Kernel).to receive(:warn).at_least(:once)
101
+ firefox = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox]).value
102
+ firefox_transitional = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox46]).value
103
+ expect(firefox).not_to eq(firefox_transitional)
104
+ expect(firefox).to match(/frame-src/)
105
+ expect(firefox).not_to match(/child-src/)
106
+ expect(firefox_transitional).to match(/child-src/)
107
+ expect(firefox_transitional).not_to match(/frame-src/)
108
+ end
109
+
110
+ it "supports strict-dynamic" do
111
+ csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456}, USER_AGENTS[:chrome])
112
+ expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
113
+ end
114
+
79
115
  context "browser sniffing" do
80
116
  let (:complex_opts) do
81
- ContentSecurityPolicy::ALL_DIRECTIVES.each_with_object({}) do |directive, hash|
82
- hash[directive] = %w('self')
117
+ (ContentSecurityPolicy::ALL_DIRECTIVES - [:frame_src]).each_with_object({}) do |directive, hash|
118
+ hash[directive] = ["#{directive.to_s.gsub("_", "-")}.com"]
83
119
  end.merge({
84
120
  block_all_mixed_content: true,
85
121
  upgrade_insecure_requests: true,
86
- reflected_xss: "block",
87
- script_src: %w('self'),
122
+ script_src: %w(script-src.com),
88
123
  script_nonce: 123456
89
124
  })
90
125
  end
91
126
 
92
127
  it "does not filter any directives for Chrome" do
93
128
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
94
- 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'")
129
+ 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 plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
95
130
  end
96
131
 
97
132
  it "does not filter any directives for Opera" do
98
133
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
99
- 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'")
134
+ 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 plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
100
135
  end
101
136
 
102
137
  it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
103
138
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
104
- 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'")
139
+ 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 sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
140
+ end
141
+
142
+ it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
143
+ policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
144
+ 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 sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
145
+ end
146
+
147
+ 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
148
+ policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
149
+ 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 sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
105
150
  end
106
151
 
107
- 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
152
+ 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
108
153
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
109
- 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'")
154
+ 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 sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
155
+ end
156
+
157
+ it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, nonce sources, and hash sources for safari 10 and higher" do
158
+ policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari10])
159
+ 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 plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; report-uri report-uri.com")
160
+ end
161
+
162
+ it "falls back to standard Firefox defaults when the useragent version is not present" do
163
+ ua = USER_AGENTS[:firefox].dup
164
+ allow(ua).to receive(:version).and_return(nil)
165
+ policy = ContentSecurityPolicy.new(complex_opts, ua)
166
+ 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 sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
110
167
  end
111
168
  end
112
169
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ module SecureHeaders
5
+ describe ExpectCertificateTransparency do
6
+ specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: true).value).to eq("enforce; max-age=1234") }
7
+ specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: false).value).to eq("max-age=1234") }
8
+ specify { expect(ExpectCertificateTransparency.new(max_age: 1234, enforce: "yolocopter").value).to eq("max-age=1234") }
9
+ specify { expect(ExpectCertificateTransparency.new(max_age: 1234, report_uri: "https://report-uri.io/expect-ct").value).to eq("max-age=1234; report-uri=\"https://report-uri.io/expect-ct\"") }
10
+ specify do
11
+ config = { enforce: true, max_age: 1234, report_uri: "https://report-uri.io/expect-ct" }
12
+ header_value = "enforce; max-age=1234; report-uri=\"https://report-uri.io/expect-ct\""
13
+ expect(ExpectCertificateTransparency.new(config).value).to eq(header_value)
14
+ end
15
+
16
+ context "with an invalid configuration" do
17
+ it "raises an exception when configuration isn't a hash" do
18
+ expect do
19
+ ExpectCertificateTransparency.validate_config!(%w(a))
20
+ end.to raise_error(ExpectCertificateTransparencyConfigError)
21
+ end
22
+
23
+ it "raises an exception when max-age is not provided" do
24
+ expect do
25
+ ExpectCertificateTransparency.validate_config!(foo: "bar")
26
+ end.to raise_error(ExpectCertificateTransparencyConfigError)
27
+ end
28
+
29
+ it "raises an exception with an invalid max-age" do
30
+ expect do
31
+ ExpectCertificateTransparency.validate_config!(max_age: "abc123")
32
+ end.to raise_error(ExpectCertificateTransparencyConfigError)
33
+ end
34
+
35
+ it "raises an exception with an invalid enforce value" do
36
+ expect do
37
+ ExpectCertificateTransparency.validate_config!(enforce: "brokenstring")
38
+ end.to raise_error(ExpectCertificateTransparencyConfigError)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -23,15 +23,16 @@ module SecureHeaders
23
23
  # directive values: these values will directly translate into source directives
24
24
  default_src: %w(https: 'self'),
25
25
  frame_src: %w('self' *.twimg.com itunes.apple.com),
26
- connect_src: %w(wws:),
26
+ child_src: %w('self' *.twimg.com itunes.apple.com),
27
+ connect_src: %w(wss:),
27
28
  font_src: %w('self' data:),
28
29
  img_src: %w(mycdn.com data:),
30
+ manifest_src: %w(manifest.com),
29
31
  media_src: %w(utoob.com),
30
32
  object_src: %w('self'),
31
33
  script_src: %w('self'),
32
34
  style_src: %w('unsafe-inline'),
33
35
  base_uri: %w('self'),
34
- child_src: %w('self'),
35
36
  form_action: %w('self' github.com),
36
37
  frame_ancestors: %w('none'),
37
38
  plugin_types: %w(application/x-shockwave-flash),
@@ -40,70 +41,75 @@ module SecureHeaders
40
41
  report_uri: %w(https://example.com/uri-directive)
41
42
  }
42
43
 
43
- CSP.validate_config!(config)
44
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
44
45
  end
45
46
 
46
47
  it "requires a :default_src value" do
47
48
  expect do
48
- CSP.validate_config!(script_src: %('self'))
49
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %('self')))
49
50
  end.to raise_error(ContentSecurityPolicyConfigError)
50
51
  end
51
52
 
52
53
  it "requires :report_only to be a truthy value" do
53
54
  expect do
54
- CSP.validate_config!(default_opts.merge(report_only: "steve"))
55
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
55
56
  end.to raise_error(ContentSecurityPolicyConfigError)
56
57
  end
57
58
 
58
59
  it "requires :preserve_schemes to be a truthy value" do
59
60
  expect do
60
- CSP.validate_config!(default_opts.merge(preserve_schemes: "steve"))
61
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(preserve_schemes: "steve")))
61
62
  end.to raise_error(ContentSecurityPolicyConfigError)
62
63
  end
63
64
 
64
65
  it "requires :block_all_mixed_content to be a boolean value" do
65
66
  expect do
66
- CSP.validate_config!(default_opts.merge(block_all_mixed_content: "steve"))
67
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(block_all_mixed_content: "steve")))
67
68
  end.to raise_error(ContentSecurityPolicyConfigError)
68
69
  end
69
70
 
70
71
  it "requires :upgrade_insecure_requests to be a boolean value" do
71
72
  expect do
72
- CSP.validate_config!(default_opts.merge(upgrade_insecure_requests: "steve"))
73
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(upgrade_insecure_requests: "steve")))
73
74
  end.to raise_error(ContentSecurityPolicyConfigError)
74
75
  end
75
76
 
76
77
  it "requires all source lists to be an array of strings" do
77
78
  expect do
78
- CSP.validate_config!(default_src: "steve")
79
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: "steve"))
79
80
  end.to raise_error(ContentSecurityPolicyConfigError)
80
81
  end
81
82
 
82
83
  it "allows nil values" do
83
84
  expect do
84
- CSP.validate_config!(default_src: %w('self'), script_src: ["https:", nil])
85
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: ["https:", nil]))
85
86
  end.to_not raise_error
86
87
  end
87
88
 
88
89
  it "rejects unknown directives / config" do
89
90
  expect do
90
- CSP.validate_config!(default_src: %w('self'), default_src_totally_mispelled: "steve")
91
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), default_src_totally_mispelled: "steve"))
91
92
  end.to raise_error(ContentSecurityPolicyConfigError)
92
93
  end
93
94
 
94
95
  # this is mostly to ensure people don't use the antiquated shorthands common in other configs
95
96
  it "performs light validation on source lists" do
96
97
  expect do
97
- CSP.validate_config!(default_src: %w(self none inline eval))
98
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval)))
98
99
  end.to raise_error(ContentSecurityPolicyConfigError)
99
100
  end
100
101
  end
101
102
 
102
103
  describe "#combine_policies" do
103
104
  it "combines the default-src value with the override if the directive was unconfigured" do
104
- combined_config = CSP.combine_policies(Configuration.default.csp, script_src: %w(anothercdn.com))
105
+ Configuration.default do |config|
106
+ config.csp = {
107
+ default_src: %w(https:)
108
+ }
109
+ end
110
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
105
111
  csp = ContentSecurityPolicy.new(combined_config)
106
- expect(csp.name).to eq(CSP::HEADER_NAME)
112
+ expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
107
113
  expect(csp.value).to eq("default-src https:; script-src https: anothercdn.com")
108
114
  end
109
115
 
@@ -115,7 +121,7 @@ module SecureHeaders
115
121
  }.freeze
116
122
  end
117
123
  report_uri = "https://report-uri.io/asdf"
118
- combined_config = CSP.combine_policies(Configuration.get.csp, report_uri: [report_uri])
124
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_uri: [report_uri])
119
125
  csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
120
126
  expect(csp.value).to include("report-uri #{report_uri}")
121
127
  end
@@ -127,12 +133,12 @@ module SecureHeaders
127
133
  report_only: false
128
134
  }.freeze
129
135
  end
130
- non_default_source_additions = CSP::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
136
+ non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
131
137
  hash[directive] = %w("http://example.org)
132
138
  end
133
- combined_config = CSP.combine_policies(Configuration.get.csp, non_default_source_additions)
139
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
134
140
 
135
- CSP::NON_FETCH_SOURCES.each do |directive|
141
+ ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
136
142
  expect(combined_config[directive]).to eq(%w("http://example.org))
137
143
  end
138
144
 
@@ -146,9 +152,9 @@ module SecureHeaders
146
152
  report_only: false
147
153
  }
148
154
  end
149
- combined_config = CSP.combine_policies(Configuration.get.csp, report_only: true)
155
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_only: true)
150
156
  csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
151
- expect(csp.name).to eq(CSP::REPORT_ONLY)
157
+ expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
152
158
  end
153
159
 
154
160
  it "overrides the :block_all_mixed_content flag" do
@@ -158,7 +164,7 @@ module SecureHeaders
158
164
  block_all_mixed_content: false
159
165
  }
160
166
  end
161
- combined_config = CSP.combine_policies(Configuration.get.csp, block_all_mixed_content: true)
167
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
162
168
  csp = ContentSecurityPolicy.new(combined_config)
163
169
  expect(csp.value).to eq("default-src https:; block-all-mixed-content")
164
170
  end
@@ -168,23 +174,9 @@ module SecureHeaders
168
174
  config.csp = OPT_OUT
169
175
  end
170
176
  expect do
171
- CSP.combine_policies(Configuration.get.csp, script_src: %w(anothercdn.com))
177
+ ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
172
178
  end.to raise_error(ContentSecurityPolicyConfigError)
173
179
  end
174
180
  end
175
-
176
- describe "#idempotent_additions?" do
177
- specify { expect(ContentSecurityPolicy.idempotent_additions?(OPT_OUT, script_src: %w(b.com))).to be false }
178
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(c.com))).to be false }
179
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: %w(b.com))).to be false }
180
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(a.com b.com c.com))).to be false }
181
-
182
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com))).to be true }
183
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w(b.com a.com))).to be true }
184
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: %w())).to be true }
185
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, script_src: [nil])).to be true }
186
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: [nil])).to be true }
187
- specify { expect(ContentSecurityPolicy.idempotent_additions?({script_src: %w(a.com b.com)}, style_src: nil)).to be true }
188
- end
189
181
  end
190
182
  end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ module SecureHeaders
4
+ describe ReferrerPolicy do
5
+ specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
6
+ specify { expect(ReferrerPolicy.make_header('no-referrer')).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
7
+
8
+ context "valid configuration values" do
9
+ it "accepts 'no-referrer'" do
10
+ expect do
11
+ ReferrerPolicy.validate_config!("no-referrer")
12
+ end.not_to raise_error
13
+ end
14
+
15
+ it "accepts 'no-referrer-when-downgrade'" do
16
+ expect do
17
+ ReferrerPolicy.validate_config!("no-referrer-when-downgrade")
18
+ end.not_to raise_error
19
+ end
20
+
21
+ it "accepts 'same-origin'" do
22
+ expect do
23
+ ReferrerPolicy.validate_config!("same-origin")
24
+ end.not_to raise_error
25
+ end
26
+
27
+ it "accepts 'strict-origin'" do
28
+ expect do
29
+ ReferrerPolicy.validate_config!("strict-origin")
30
+ end.not_to raise_error
31
+ end
32
+
33
+ it "accepts 'strict-origin-when-cross-origin'" do
34
+ expect do
35
+ ReferrerPolicy.validate_config!("strict-origin-when-cross-origin")
36
+ end.not_to raise_error
37
+ end
38
+
39
+ it "accepts 'origin'" do
40
+ expect do
41
+ ReferrerPolicy.validate_config!("origin")
42
+ end.not_to raise_error
43
+ end
44
+
45
+ it "accepts 'origin-when-cross-origin'" do
46
+ expect do
47
+ ReferrerPolicy.validate_config!("origin-when-cross-origin")
48
+ end.not_to raise_error
49
+ end
50
+
51
+ it "accepts 'unsafe-url'" do
52
+ expect do
53
+ ReferrerPolicy.validate_config!("unsafe-url")
54
+ end.not_to raise_error
55
+ end
56
+
57
+ it "accepts nil" do
58
+ expect do
59
+ ReferrerPolicy.validate_config!(nil)
60
+ end.not_to raise_error
61
+ end
62
+ end
63
+
64
+ context 'invlaid configuration values' do
65
+ it "doesn't accept invalid values" do
66
+ expect do
67
+ ReferrerPolicy.validate_config!("open")
68
+ end.to raise_error(ReferrerPolicyConfigError)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -13,6 +13,24 @@ module SecureHeaders
13
13
  Configuration.default
14
14
  end
15
15
 
16
+ it "warns if the hpkp report-uri host is the same as the current host" do
17
+ report_host = "report-uri.io"
18
+ Configuration.default do |config|
19
+ config.hpkp = {
20
+ max_age: 10000000,
21
+ pins: [
22
+ {sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
23
+ {sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
24
+ ],
25
+ report_uri: "https://#{report_host}/example-hpkp"
26
+ }
27
+ end
28
+
29
+ expect(Kernel).to receive(:warn).with(Middleware::HPKP_SAME_HOST_WARNING)
30
+
31
+ middleware.call(Rack::MockRequest.env_for("https://#{report_host}", {}))
32
+ end
33
+
16
34
  it "sets the headers" do
17
35
  _, env = middleware.call(Rack::MockRequest.env_for("https://looocalhost", {}))
18
36
  expect_default_values(env)
@@ -33,7 +51,7 @@ module SecureHeaders
33
51
  SecureHeaders.use_secure_headers_override(request, "my_custom_config")
34
52
  expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config"))
35
53
  _, env = middleware.call request.env
36
- expect(env[CSP::HEADER_NAME]).to match("example.org")
54
+ expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
37
55
  end
38
56
 
39
57
  context "secure_cookies" do
@@ -87,6 +105,18 @@ module SecureHeaders
87
105
  _, env = cookie_middleware.call request.env
88
106
  expect(env['Set-Cookie']).to eq("foo=bar")
89
107
  end
108
+
109
+ it "sets the secure cookie flag correctly on interleaved http/https requests" do
110
+ Configuration.default { |config| config.cookies = { secure: true } }
111
+
112
+ request = Rack::Request.new("HTTPS" => "off")
113
+ _, env = cookie_middleware.call request.env
114
+ expect(env['Set-Cookie']).to eq("foo=bar")
115
+
116
+ request = Rack::Request.new("HTTPS" => "on")
117
+ _, env = cookie_middleware.call request.env
118
+ expect(env['Set-Cookie']).to eq("foo=bar; secure")
119
+ end
90
120
  end
91
121
  end
92
122
  end
@@ -27,7 +27,16 @@ class Message < ERB
27
27
  background-color: black;
28
28
  }
29
29
  <% end %>
30
- <%= @name %>
30
+
31
+ <script nonce="<%= content_security_policy_script_nonce %>">
32
+ alert(1)
33
+ </script>
34
+
35
+ <style nonce="<%= content_security_policy_style_nonce %>">
36
+ body {
37
+ background-color: black;
38
+ }
39
+ </style>
31
40
 
32
41
  TEMPLATE
33
42
  end
@@ -72,8 +81,11 @@ module SecureHeaders
72
81
 
73
82
  before(:all) do
74
83
  Configuration.default do |config|
75
- config.csp[:script_src] = %w('self')
76
- config.csp[:style_src] = %w('self')
84
+ config.csp = {
85
+ :default_src => %w('self'),
86
+ :script_src => %w('self'),
87
+ :style_src => %w('self')
88
+ }
77
89
  end
78
90
  end
79
91
 
@@ -115,10 +127,10 @@ module SecureHeaders
115
127
  Message.new(request).result
116
128
  _, env = middleware.call request.env
117
129
 
118
- expect(env[CSP::HEADER_NAME]).to match(/script-src[^;]*'#{Regexp.escape(expected_hash)}'/)
119
- expect(env[CSP::HEADER_NAME]).to match(/script-src[^;]*'nonce-abc123'/)
120
- expect(env[CSP::HEADER_NAME]).to match(/style-src[^;]*'nonce-abc123'/)
121
- expect(env[CSP::HEADER_NAME]).to match(/style-src[^;]*'#{Regexp.escape(expected_style_hash)}'/)
130
+ expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'#{Regexp.escape(expected_hash)}'/)
131
+ expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/script-src[^;]*'nonce-abc123'/)
132
+ expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'nonce-abc123'/)
133
+ expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match(/style-src[^;]*'#{Regexp.escape(expected_style_hash)}'/)
122
134
  end
123
135
  end
124
136
  end