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
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ module SecureHeaders
5
+ describe Cookie do
6
+ let(:raw_cookie) { "_session=thisisatest" }
7
+
8
+ it "does not tamper with cookies when using OPT_OUT is used" do
9
+ cookie = Cookie.new(raw_cookie, OPT_OUT)
10
+ expect(cookie.to_s).to eq(raw_cookie)
11
+ end
12
+
13
+ it "applies httponly, secure, and samesite by default" do
14
+ cookie = Cookie.new(raw_cookie, nil)
15
+ expect(cookie.to_s).to eq("_session=thisisatest; secure; HttpOnly; SameSite=Lax")
16
+ end
17
+
18
+ it "preserves existing attributes" do
19
+ cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
20
+ expect(cookie.to_s).to eq("_session=thisisatest; secure")
21
+ end
22
+
23
+ it "prevents duplicate flagging of attributes" do
24
+ cookie = Cookie.new("_session=thisisatest; secure", secure: true, httponly: OPT_OUT)
25
+ expect(cookie.to_s.scan(/secure/i).count).to eq(1)
26
+ end
27
+
28
+ context "Secure cookies" do
29
+ context "when configured with a boolean" do
30
+ it "flags cookies as Secure" do
31
+ cookie = Cookie.new(raw_cookie, secure: true, httponly: OPT_OUT, samesite: OPT_OUT)
32
+ expect(cookie.to_s).to eq("_session=thisisatest; secure")
33
+ end
34
+ end
35
+
36
+ context "when configured with a Hash" do
37
+ it "flags cookies as Secure when whitelisted" do
38
+ cookie = Cookie.new(raw_cookie, secure: { only: ["_session"]}, httponly: OPT_OUT, samesite: OPT_OUT)
39
+ expect(cookie.to_s).to eq("_session=thisisatest; secure")
40
+ end
41
+
42
+ it "does not flag cookies as Secure when excluded" do
43
+ cookie = Cookie.new(raw_cookie, secure: { except: ["_session"] }, httponly: OPT_OUT, samesite: OPT_OUT)
44
+ expect(cookie.to_s).to eq("_session=thisisatest")
45
+ end
46
+ end
47
+ end
48
+
49
+ context "HttpOnly cookies" do
50
+ context "when configured with a boolean" do
51
+ it "flags cookies as HttpOnly" do
52
+ cookie = Cookie.new(raw_cookie, httponly: true, secure: OPT_OUT, samesite: OPT_OUT)
53
+ expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
54
+ end
55
+ end
56
+
57
+ context "when configured with a Hash" do
58
+ it "flags cookies as HttpOnly when whitelisted" do
59
+ cookie = Cookie.new(raw_cookie, httponly: { only: ["_session"]}, secure: OPT_OUT, samesite: OPT_OUT)
60
+ expect(cookie.to_s).to eq("_session=thisisatest; HttpOnly")
61
+ end
62
+
63
+ it "does not flag cookies as HttpOnly when excluded" do
64
+ cookie = Cookie.new(raw_cookie, httponly: { except: ["_session"] }, secure: OPT_OUT, samesite: OPT_OUT)
65
+ expect(cookie.to_s).to eq("_session=thisisatest")
66
+ end
67
+ end
68
+ end
69
+
70
+ context "SameSite cookies" do
71
+ it "flags SameSite=Lax" do
72
+ cookie = Cookie.new(raw_cookie, samesite: { lax: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
73
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
74
+ end
75
+
76
+ it "flags SameSite=Lax when configured with a boolean" do
77
+ cookie = Cookie.new(raw_cookie, samesite: { lax: true}, secure: OPT_OUT, httponly: OPT_OUT)
78
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
79
+ end
80
+
81
+ it "does not flag cookies as SameSite=Lax when excluded" do
82
+ cookie = Cookie.new(raw_cookie, samesite: { lax: { except: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
83
+ expect(cookie.to_s).to eq("_session=thisisatest")
84
+ end
85
+
86
+ it "flags SameSite=Strict" do
87
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
88
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
89
+ end
90
+
91
+ it "does not flag cookies as SameSite=Strict when excluded" do
92
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { except: ["_session"] }}, secure: OPT_OUT, httponly: OPT_OUT)
93
+ expect(cookie.to_s).to eq("_session=thisisatest")
94
+ end
95
+
96
+ it "flags SameSite=Strict when configured with a boolean" do
97
+ cookie = Cookie.new(raw_cookie, {samesite: { strict: true}, secure: OPT_OUT, httponly: OPT_OUT})
98
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
99
+ end
100
+
101
+ it "flags properly when both lax and strict are configured" do
102
+ raw_cookie = "_session=thisisatest"
103
+ cookie = Cookie.new(raw_cookie, samesite: { strict: { only: ["_session"] }, lax: { only: ["_additional_session"] } }, secure: OPT_OUT, httponly: OPT_OUT)
104
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Strict")
105
+ end
106
+
107
+ it "ignores configuration if the cookie is already flagged" do
108
+ raw_cookie = "_session=thisisatest; SameSite=Strict"
109
+ cookie = Cookie.new(raw_cookie, samesite: { lax: true }, secure: OPT_OUT, httponly: OPT_OUT)
110
+ expect(cookie.to_s).to eq(raw_cookie)
111
+ end
112
+
113
+ it "samesite: true sets all cookies to samesite=lax" do
114
+ raw_cookie = "_session=thisisatest"
115
+ cookie = Cookie.new(raw_cookie, samesite: true, secure: OPT_OUT, httponly: OPT_OUT)
116
+ expect(cookie.to_s).to eq("_session=thisisatest; SameSite=Lax")
117
+ end
118
+ end
119
+ end
120
+
121
+ context "with an invalid configuration" do
122
+ it "raises an exception when not configured with a Hash" do
123
+ expect do
124
+ Cookie.validate_config!("configuration")
125
+ end.to raise_error(CookiesConfigError)
126
+ end
127
+
128
+ it "raises an exception when configured without a boolean(true or OPT_OUT)/Hash" do
129
+ expect do
130
+ Cookie.validate_config!(secure: "true")
131
+ end.to raise_error(CookiesConfigError)
132
+ end
133
+
134
+ it "raises an exception when configured with false" do
135
+ expect do
136
+ Cookie.validate_config!(secure: false)
137
+ end.to raise_error(CookiesConfigError)
138
+ end
139
+
140
+ it "raises an exception when both only and except filters are provided" do
141
+ expect do
142
+ Cookie.validate_config!(secure: { only: [], except: [] })
143
+ end.to raise_error(CookiesConfigError)
144
+ end
145
+
146
+ it "raises an exception when SameSite is not configured with a Hash" do
147
+ expect do
148
+ Cookie.validate_config!(samesite: true)
149
+ end.to raise_error(CookiesConfigError)
150
+ end
151
+
152
+ it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
153
+ expect do
154
+ Cookie.validate_config!(samesite: { lax: true, strict: true})
155
+ end.to raise_error(CookiesConfigError)
156
+ end
157
+
158
+ it "raises an exception when SameSite lax and strict enforcement modes are configured with booleans" do
159
+ expect do
160
+ Cookie.validate_config!(samesite: { lax: true, strict: { only: ["_anything"] } })
161
+ end.to raise_error(CookiesConfigError)
162
+ end
163
+
164
+ it "raises an exception when both only and except filters are provided to SameSite configurations" do
165
+ expect do
166
+ Cookie.validate_config!(samesite: { lax: { only: ["_anything"], except: ["_anythingelse"] } })
167
+ end.to raise_error(CookiesConfigError)
168
+ end
169
+
170
+ it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
171
+ expect do
172
+ Cookie.validate_config!(samesite: { lax: { only: ["_anything"] }, strict: { only: ["_anything"] } })
173
+ end.to raise_error(CookiesConfigError)
174
+ end
175
+
176
+ it "raises an exception when both lax and strict only filters are provided to SameSite configurations" do
177
+ expect do
178
+ Cookie.validate_config!(samesite: { lax: { except: ["_anything"] }, strict: { except: ["_anything"] } })
179
+ end.to raise_error(CookiesConfigError)
180
+ end
181
+ end
182
+ 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
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ module SecureHeaders
5
+ describe PolicyManagement do
6
+ let (:default_opts) do
7
+ {
8
+ default_src: %w(https:),
9
+ img_src: %w(https: data:),
10
+ script_src: %w('unsafe-inline' 'unsafe-eval' https: data:),
11
+ style_src: %w('unsafe-inline' https: about:),
12
+ report_uri: %w(/csp_report)
13
+ }
14
+ end
15
+
16
+ describe "#validate_config!" do
17
+ it "accepts all keys" do
18
+ # (pulled from README)
19
+ config = {
20
+ # "meta" values. these will shape the header, but the values are not included in the header.
21
+ report_only: true, # default: false
22
+ preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
23
+
24
+ # directive values: these values will directly translate into source directives
25
+ default_src: %w(https: 'self'),
26
+ frame_src: %w('self' *.twimg.com itunes.apple.com),
27
+ child_src: %w('self' *.twimg.com itunes.apple.com),
28
+ connect_src: %w(wss:),
29
+ font_src: %w('self' data:),
30
+ img_src: %w(mycdn.com data:),
31
+ manifest_src: %w(manifest.com),
32
+ media_src: %w(utoob.com),
33
+ object_src: %w('self'),
34
+ script_src: %w('self'),
35
+ style_src: %w('unsafe-inline'),
36
+ base_uri: %w('self'),
37
+ form_action: %w('self' github.com),
38
+ frame_ancestors: %w('none'),
39
+ plugin_types: %w(application/x-shockwave-flash),
40
+ block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
41
+ upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
42
+ report_uri: %w(https://example.com/uri-directive)
43
+ }
44
+
45
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
46
+ end
47
+
48
+ it "requires a :default_src value" do
49
+ expect do
50
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(script_src: %w('self')))
51
+ end.to raise_error(ContentSecurityPolicyConfigError)
52
+ end
53
+
54
+ it "requires a :script_src value" do
55
+ expect do
56
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self')))
57
+ end.to raise_error(ContentSecurityPolicyConfigError)
58
+ end
59
+
60
+ it "accepts OPT_OUT as a script-src value" do
61
+ expect do
62
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: OPT_OUT))
63
+ end.to_not raise_error
64
+ end
65
+
66
+ it "requires :report_only to be a truthy value" do
67
+ expect do
68
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(report_only: "steve")))
69
+ end.to raise_error(ContentSecurityPolicyConfigError)
70
+ end
71
+
72
+ it "requires :preserve_schemes to be a truthy value" do
73
+ expect do
74
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(preserve_schemes: "steve")))
75
+ end.to raise_error(ContentSecurityPolicyConfigError)
76
+ end
77
+
78
+ it "requires :block_all_mixed_content to be a boolean value" do
79
+ expect do
80
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(block_all_mixed_content: "steve")))
81
+ end.to raise_error(ContentSecurityPolicyConfigError)
82
+ end
83
+
84
+ it "requires :upgrade_insecure_requests to be a boolean value" do
85
+ expect do
86
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(upgrade_insecure_requests: "steve")))
87
+ end.to raise_error(ContentSecurityPolicyConfigError)
88
+ end
89
+
90
+ it "requires all source lists to be an array of strings" do
91
+ expect do
92
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: "steve"))
93
+ end.to raise_error(ContentSecurityPolicyConfigError)
94
+ end
95
+
96
+ it "allows nil values" do
97
+ expect do
98
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), script_src: ["https:", nil]))
99
+ end.to_not raise_error
100
+ end
101
+
102
+ it "rejects unknown directives / config" do
103
+ expect do
104
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w('self'), default_src_totally_mispelled: "steve"))
105
+ end.to raise_error(ContentSecurityPolicyConfigError)
106
+ end
107
+
108
+ # this is mostly to ensure people don't use the antiquated shorthands common in other configs
109
+ it "performs light validation on source lists" do
110
+ expect do
111
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_src: %w(self none inline eval), script_src: %w('self')))
112
+ end.to raise_error(ContentSecurityPolicyConfigError)
113
+ end
114
+
115
+ it "rejects anything not of the form allow-* as a sandbox value" do
116
+ expect do
117
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["steve"])))
118
+ end.to raise_error(ContentSecurityPolicyConfigError)
119
+ end
120
+
121
+ it "accepts anything of the form allow-* as a sandbox value " do
122
+ expect do
123
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: ["allow-foo"])))
124
+ end.to_not raise_error
125
+ end
126
+
127
+ it "accepts true as a sandbox policy" do
128
+ expect do
129
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(sandbox: true)))
130
+ end.to_not raise_error
131
+ end
132
+
133
+ it "rejects anything not of the form type/subtype as a plugin-type value" do
134
+ expect do
135
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["steve"])))
136
+ end.to raise_error(ContentSecurityPolicyConfigError)
137
+ end
138
+
139
+ it "accepts anything of the form type/subtype as a plugin-type value " do
140
+ expect do
141
+ ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(default_opts.merge(plugin_types: ["application/pdf"])))
142
+ end.to_not raise_error
143
+ end
144
+ end
145
+
146
+ describe "#combine_policies" do
147
+ it "combines the default-src value with the override if the directive was unconfigured" do
148
+ Configuration.default do |config|
149
+ config.csp = {
150
+ default_src: %w(https:),
151
+ script_src: %w('self'),
152
+ }
153
+ end
154
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, style_src: %w(anothercdn.com))
155
+ csp = ContentSecurityPolicy.new(combined_config)
156
+ expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
157
+ expect(csp.value).to eq("default-src https:; script-src 'self'; style-src https: anothercdn.com")
158
+ end
159
+
160
+ it "combines directives where the original value is nil and the hash is frozen" do
161
+ Configuration.default do |config|
162
+ config.csp = {
163
+ default_src: %w('self'),
164
+ script_src: %w('self'),
165
+ report_only: false
166
+ }.freeze
167
+ end
168
+ report_uri = "https://report-uri.io/asdf"
169
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_uri: [report_uri])
170
+ csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
171
+ expect(csp.value).to include("report-uri #{report_uri}")
172
+ end
173
+
174
+ it "does not combine the default-src value for directives that don't fall back to default sources" do
175
+ Configuration.default do |config|
176
+ config.csp = {
177
+ default_src: %w('self'),
178
+ script_src: %w('self'),
179
+ report_only: false
180
+ }.freeze
181
+ end
182
+ non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
183
+ hash[directive] = %w("http://example.org)
184
+ end
185
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
186
+
187
+ ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
188
+ expect(combined_config[directive]).to eq(%w("http://example.org))
189
+ end
190
+ end
191
+
192
+ it "overrides the report_only flag" do
193
+ Configuration.default do |config|
194
+ config.csp = {
195
+ default_src: %w('self'),
196
+ script_src: %w('self'),
197
+ report_only: false
198
+ }
199
+ end
200
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_only: true)
201
+ csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
202
+ expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
203
+ end
204
+
205
+ it "overrides the :block_all_mixed_content flag" do
206
+ Configuration.default do |config|
207
+ config.csp = {
208
+ default_src: %w(https:),
209
+ script_src: %w('self'),
210
+ block_all_mixed_content: false
211
+ }
212
+ end
213
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
214
+ csp = ContentSecurityPolicy.new(combined_config)
215
+ expect(csp.value).to eq("default-src https:; block-all-mixed-content; script-src 'self'")
216
+ end
217
+
218
+ it "raises an error if appending to a OPT_OUT policy" do
219
+ Configuration.default do |config|
220
+ config.csp = OPT_OUT
221
+ end
222
+ expect do
223
+ ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
224
+ end.to raise_error(ContentSecurityPolicyConfigError)
225
+ end
226
+ end
227
+ end
228
+ 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 PublicKeyPins do
@@ -8,7 +9,7 @@ module SecureHeaders
8
9
  specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
9
10
  specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
10
11
  specify do
11
- config = { max_age: 1234, pins: [{ sha256: 'base64encodedpin1' }, { sha256: 'base64encodedpin2' }] }
12
+ config = { max_age: 1234, pins: [{ sha256: "base64encodedpin1" }, { sha256: "base64encodedpin2" }] }
12
13
  header_value = "max-age=1234; pin-sha256=\"base64encodedpin1\"; pin-sha256=\"base64encodedpin2\""
13
14
  expect(PublicKeyPins.new(config).value).to eq(header_value)
14
15
  end
@@ -16,19 +17,19 @@ module SecureHeaders
16
17
  context "with an invalid configuration" do
17
18
  it "raises an exception when max-age is not provided" do
18
19
  expect do
19
- PublicKeyPins.validate_config!(foo: 'bar')
20
+ PublicKeyPins.validate_config!(foo: "bar")
20
21
  end.to raise_error(PublicKeyPinsConfigError)
21
22
  end
22
23
 
23
24
  it "raises an exception with an invalid max-age" do
24
25
  expect do
25
- PublicKeyPins.validate_config!(max_age: 'abc123')
26
+ PublicKeyPins.validate_config!(max_age: "abc123")
26
27
  end.to raise_error(PublicKeyPinsConfigError)
27
28
  end
28
29
 
29
- it 'raises an exception with less than 2 pins' do
30
+ it "raises an exception with less than 2 pins" do
30
31
  expect do
31
- config = { max_age: 1234, pins: [{ sha256: 'base64encodedpin' }] }
32
+ config = { max_age: 1234, pins: [{ sha256: "base64encodedpin" }] }
32
33
  PublicKeyPins.validate_config!(config)
33
34
  end.to raise_error(PublicKeyPinsConfigError)
34
35
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ module SecureHeaders
5
+ describe ReferrerPolicy do
6
+ specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
7
+ specify { expect(ReferrerPolicy.make_header("no-referrer")).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
8
+
9
+ context "valid configuration values" do
10
+ it "accepts 'no-referrer'" do
11
+ expect do
12
+ ReferrerPolicy.validate_config!("no-referrer")
13
+ end.not_to raise_error
14
+ end
15
+
16
+ it "accepts 'no-referrer-when-downgrade'" do
17
+ expect do
18
+ ReferrerPolicy.validate_config!("no-referrer-when-downgrade")
19
+ end.not_to raise_error
20
+ end
21
+
22
+ it "accepts 'same-origin'" do
23
+ expect do
24
+ ReferrerPolicy.validate_config!("same-origin")
25
+ end.not_to raise_error
26
+ end
27
+
28
+ it "accepts 'strict-origin'" do
29
+ expect do
30
+ ReferrerPolicy.validate_config!("strict-origin")
31
+ end.not_to raise_error
32
+ end
33
+
34
+ it "accepts 'strict-origin-when-cross-origin'" do
35
+ expect do
36
+ ReferrerPolicy.validate_config!("strict-origin-when-cross-origin")
37
+ end.not_to raise_error
38
+ end
39
+
40
+ it "accepts 'origin'" do
41
+ expect do
42
+ ReferrerPolicy.validate_config!("origin")
43
+ end.not_to raise_error
44
+ end
45
+
46
+ it "accepts 'origin-when-cross-origin'" do
47
+ expect do
48
+ ReferrerPolicy.validate_config!("origin-when-cross-origin")
49
+ end.not_to raise_error
50
+ end
51
+
52
+ it "accepts 'unsafe-url'" do
53
+ expect do
54
+ ReferrerPolicy.validate_config!("unsafe-url")
55
+ end.not_to raise_error
56
+ end
57
+
58
+ it "accepts nil" do
59
+ expect do
60
+ ReferrerPolicy.validate_config!(nil)
61
+ end.not_to raise_error
62
+ end
63
+ end
64
+
65
+ context "invlaid configuration values" do
66
+ it "doesn't accept invalid values" do
67
+ expect do
68
+ ReferrerPolicy.validate_config!("open")
69
+ end.to raise_error(ReferrerPolicyConfigError)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,28 +1,29 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe StrictTransportSecurity do
5
6
  describe "#value" do
6
7
  specify { expect(StrictTransportSecurity.make_header).to eq([StrictTransportSecurity::HEADER_NAME, StrictTransportSecurity::DEFAULT_VALUE]) }
7
- specify { expect(StrictTransportSecurity.make_header("max-age=1234")).to eq([StrictTransportSecurity::HEADER_NAME, "max-age=1234"]) }
8
+ specify { expect(StrictTransportSecurity.make_header("max-age=1234; includeSubdomains; preload")).to eq([StrictTransportSecurity::HEADER_NAME, "max-age=1234; includeSubdomains; preload"]) }
8
9
 
9
10
  context "with an invalid configuration" do
10
11
  context "with a string argument" do
11
12
  it "raises an exception with an invalid max-age" do
12
13
  expect do
13
- StrictTransportSecurity.validate_config!('max-age=abc123')
14
+ StrictTransportSecurity.validate_config!("max-age=abc123")
14
15
  end.to raise_error(STSConfigError)
15
16
  end
16
17
 
17
18
  it "raises an exception if max-age is not supplied" do
18
19
  expect do
19
- StrictTransportSecurity.validate_config!('includeSubdomains')
20
+ StrictTransportSecurity.validate_config!("includeSubdomains")
20
21
  end.to raise_error(STSConfigError)
21
22
  end
22
23
 
23
24
  it "raises an exception with an invalid format" do
24
25
  expect do
25
- StrictTransportSecurity.validate_config!('max-age=123includeSubdomains')
26
+ StrictTransportSecurity.validate_config!("max-age=123includeSubdomains")
26
27
  end.to raise_error(STSConfigError)
27
28
  end
28
29
  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 XContentTypeOptions do
@@ -1,9 +1,10 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe XDownloadOptions do
5
6
  specify { expect(XDownloadOptions.make_header).to eq([XDownloadOptions::HEADER_NAME, XDownloadOptions::DEFAULT_VALUE]) }
6
- specify { expect(XDownloadOptions.make_header('noopen')).to eq([XDownloadOptions::HEADER_NAME, 'noopen']) }
7
+ specify { expect(XDownloadOptions.make_header("noopen")).to eq([XDownloadOptions::HEADER_NAME, "noopen"]) }
7
8
 
8
9
  context "invalid configuration values" do
9
10
  it "accepts noopen" do
@@ -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 XFrameOptions do
@@ -1,9 +1,10 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
2
3
 
3
4
  module SecureHeaders
4
5
  describe XPermittedCrossDomainPolicies do
5
6
  specify { expect(XPermittedCrossDomainPolicies.make_header).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, "none"]) }
6
- specify { expect(XPermittedCrossDomainPolicies.make_header('master-only')).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, 'master-only']) }
7
+ specify { expect(XPermittedCrossDomainPolicies.make_header("master-only")).to eq([XPermittedCrossDomainPolicies::HEADER_NAME, "master-only"]) }
7
8
 
8
9
  context "valid configuration values" do
9
10
  it "accepts 'all'" do
@@ -36,7 +37,7 @@ module SecureHeaders
36
37
  end
37
38
  end
38
39
 
39
- context 'invlaid configuration values' do
40
+ context "invlaid configuration values" do
40
41
  it "doesn't accept invalid values" do
41
42
  expect do
42
43
  XPermittedCrossDomainPolicies.validate_config!("open")