secure_headers 5.1.0 → 6.0.0.alpha01

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -4
  3. data/CHANGELOG.md +3 -3
  4. data/Gemfile +1 -1
  5. data/docs/upgrading-to-6-0.md +50 -0
  6. data/lib/secure_headers/configuration.rb +114 -164
  7. data/lib/secure_headers/headers/clear_site_data.rb +1 -3
  8. data/lib/secure_headers/headers/content_security_policy.rb +4 -17
  9. data/lib/secure_headers/headers/content_security_policy_config.rb +3 -13
  10. data/lib/secure_headers/headers/expect_certificate_transparency.rb +2 -3
  11. data/lib/secure_headers/headers/policy_management.rb +12 -11
  12. data/lib/secure_headers/headers/public_key_pins.rb +2 -3
  13. data/lib/secure_headers/headers/referrer_policy.rb +2 -2
  14. data/lib/secure_headers/headers/strict_transport_security.rb +2 -2
  15. data/lib/secure_headers/headers/x_content_type_options.rb +2 -2
  16. data/lib/secure_headers/headers/x_download_options.rb +2 -2
  17. data/lib/secure_headers/headers/x_frame_options.rb +1 -2
  18. data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +2 -2
  19. data/lib/secure_headers/headers/x_xss_protection.rb +3 -3
  20. data/lib/secure_headers.rb +14 -76
  21. data/secure_headers.gemspec +1 -1
  22. data/spec/lib/secure_headers/configuration_spec.rb +15 -70
  23. data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +12 -17
  24. data/spec/lib/secure_headers/headers/policy_management_spec.rb +33 -7
  25. data/spec/lib/secure_headers/middleware_spec.rb +7 -1
  26. data/spec/lib/secure_headers/view_helpers_spec.rb +1 -0
  27. data/spec/lib/secure_headers_spec.rb +39 -40
  28. data/spec/spec_helper.rb +7 -3
  29. metadata +5 -4
@@ -5,7 +5,6 @@ module SecureHeaders
5
5
  base.extend(ClassMethods)
6
6
  end
7
7
 
8
- MODERN_BROWSERS = %w(Chrome Opera Firefox)
9
8
  DEFAULT_CONFIG = {
10
9
  default_src: %w(https:),
11
10
  img_src: %w(https: data: 'self'),
@@ -200,7 +199,8 @@ module SecureHeaders
200
199
  #
201
200
  # Returns a default policy if no configuration is provided, or a
202
201
  # header name and value based on the config.
203
- def make_header(config, user_agent)
202
+ def make_header(config, user_agent = nil)
203
+ return if config.nil? || config == OPT_OUT
204
204
  header = new(config, user_agent)
205
205
  [header.name, header.value]
206
206
  end
@@ -215,27 +215,28 @@ module SecureHeaders
215
215
  if config.directive_value(:script_src).nil?
216
216
  raise ContentSecurityPolicyConfigError.new(":script_src is required, falling back to default-src is too dangerous. Use `script_src: OPT_OUT` to override")
217
217
  end
218
+ if !config.report_only? && config.directive_value(:report_only)
219
+ raise ContentSecurityPolicyConfigError.new("Only the csp_report_only config should set :report_only to true")
220
+ end
221
+
222
+ if config.report_only? && config.directive_value(:report_only) == false
223
+ raise ContentSecurityPolicyConfigError.new("csp_report_only config must have :report_only set to true")
224
+ end
218
225
 
219
226
  ContentSecurityPolicyConfig.attrs.each do |key|
220
227
  value = config.directive_value(key)
221
228
  next unless value
229
+
222
230
  if META_CONFIGS.include?(key)
223
231
  raise ContentSecurityPolicyConfigError.new("#{key} must be a boolean value") unless boolean?(value) || value.nil?
232
+ elsif NONCES.include?(key)
233
+ raise ContentSecurityPolicyConfigError.new("#{key} must be a non-nil value") if value.nil?
224
234
  else
225
235
  validate_directive!(key, value)
226
236
  end
227
237
  end
228
238
  end
229
239
 
230
- # Public: check if a user agent supports CSP nonces
231
- #
232
- # user_agent - a String or a UserAgent object
233
- def nonces_supported?(user_agent)
234
- user_agent = UserAgent.parse(user_agent) if user_agent.is_a?(String)
235
- MODERN_BROWSERS.include?(user_agent.browser) ||
236
- user_agent.browser == "Safari" && (user_agent.version || CSP::FALLBACK_VERSION) >= CSP::VERSION_10
237
- end
238
-
239
240
  # Public: combine the values from two different configs.
240
241
  #
241
242
  # original - the main config
@@ -5,15 +5,14 @@ module SecureHeaders
5
5
  HEADER_NAME = "Public-Key-Pins".freeze
6
6
  REPORT_ONLY = "Public-Key-Pins-Report-Only".freeze
7
7
  HASH_ALGORITHMS = [:sha256].freeze
8
- CONFIG_KEY = :hpkp
9
8
 
10
9
 
11
10
  class << self
12
11
  # Public: make an hpkp header name, value pair
13
12
  #
14
13
  # Returns nil if not configured, returns header name and value if configured.
15
- def make_header(config)
16
- return if config.nil?
14
+ def make_header(config, user_agent = nil)
15
+ return if config.nil? || config == OPT_OUT
17
16
  header = new(config)
18
17
  [header.name, header.value]
19
18
  end
@@ -14,14 +14,14 @@ module SecureHeaders
14
14
  origin-when-cross-origin
15
15
  unsafe-url
16
16
  )
17
- CONFIG_KEY = :referrer_policy
18
17
 
19
18
  class << self
20
19
  # Public: generate an Referrer Policy header.
21
20
  #
22
21
  # Returns a default header if no configuration is provided, or a
23
22
  # header name and value based on the config.
24
- def make_header(config = nil)
23
+ def make_header(config = nil, user_agent = nil)
24
+ return if config == OPT_OUT
25
25
  config ||= DEFAULT_VALUE
26
26
  [HEADER_NAME, Array(config).join(", ")]
27
27
  end
@@ -8,14 +8,14 @@ module SecureHeaders
8
8
  DEFAULT_VALUE = "max-age=" + HSTS_MAX_AGE
9
9
  VALID_STS_HEADER = /\Amax-age=\d+(; includeSubdomains)?(; preload)?\z/i
10
10
  MESSAGE = "The config value supplied for the HSTS header was invalid. Must match #{VALID_STS_HEADER}"
11
- CONFIG_KEY = :hsts
12
11
 
13
12
  class << self
14
13
  # Public: generate an hsts header name, value pair.
15
14
  #
16
15
  # Returns a default header if no configuration is provided, or a
17
16
  # header name and value based on the config.
18
- def make_header(config = nil)
17
+ def make_header(config = nil, user_agent = nil)
18
+ return if config == OPT_OUT
19
19
  [HEADER_NAME, config || DEFAULT_VALUE]
20
20
  end
21
21
 
@@ -5,14 +5,14 @@ module SecureHeaders
5
5
  class XContentTypeOptions
6
6
  HEADER_NAME = "X-Content-Type-Options".freeze
7
7
  DEFAULT_VALUE = "nosniff"
8
- CONFIG_KEY = :x_content_type_options
9
8
 
10
9
  class << self
11
10
  # Public: generate an X-Content-Type-Options header.
12
11
  #
13
12
  # Returns a default header if no configuration is provided, or a
14
13
  # header name and value based on the config.
15
- def make_header(config = nil)
14
+ def make_header(config = nil, user_agent = nil)
15
+ return if config == OPT_OUT
16
16
  [HEADER_NAME, config || DEFAULT_VALUE]
17
17
  end
18
18
 
@@ -4,14 +4,14 @@ module SecureHeaders
4
4
  class XDownloadOptions
5
5
  HEADER_NAME = "X-Download-Options".freeze
6
6
  DEFAULT_VALUE = "noopen"
7
- CONFIG_KEY = :x_download_options
8
7
 
9
8
  class << self
10
9
  # Public: generate an X-Download-Options header.
11
10
  #
12
11
  # Returns a default header if no configuration is provided, or a
13
12
  # header name and value based on the config.
14
- def make_header(config = nil)
13
+ def make_header(config = nil, user_agent = nil)
14
+ return if config == OPT_OUT
15
15
  [HEADER_NAME, config || DEFAULT_VALUE]
16
16
  end
17
17
 
@@ -3,7 +3,6 @@ module SecureHeaders
3
3
  class XFOConfigError < StandardError; end
4
4
  class XFrameOptions
5
5
  HEADER_NAME = "X-Frame-Options".freeze
6
- CONFIG_KEY = :x_frame_options
7
6
  SAMEORIGIN = "sameorigin"
8
7
  DENY = "deny"
9
8
  ALLOW_FROM = "allow-from"
@@ -16,7 +15,7 @@ module SecureHeaders
16
15
  #
17
16
  # Returns a default header if no configuration is provided, or a
18
17
  # header name and value based on the config.
19
- def make_header(config = nil)
18
+ def make_header(config = nil, user_agent = nil)
20
19
  return if config == OPT_OUT
21
20
  [HEADER_NAME, config || DEFAULT_VALUE]
22
21
  end
@@ -5,14 +5,14 @@ module SecureHeaders
5
5
  HEADER_NAME = "X-Permitted-Cross-Domain-Policies".freeze
6
6
  DEFAULT_VALUE = "none"
7
7
  VALID_POLICIES = %w(all none master-only by-content-type by-ftp-filename)
8
- CONFIG_KEY = :x_permitted_cross_domain_policies
9
8
 
10
9
  class << self
11
10
  # Public: generate an X-Permitted-Cross-Domain-Policies header.
12
11
  #
13
12
  # Returns a default header if no configuration is provided, or a
14
13
  # header name and value based on the config.
15
- def make_header(config = nil)
14
+ def make_header(config = nil, user_agent = nil)
15
+ return if config == OPT_OUT
16
16
  [HEADER_NAME, config || DEFAULT_VALUE]
17
17
  end
18
18
 
@@ -4,15 +4,15 @@ module SecureHeaders
4
4
  class XXssProtection
5
5
  HEADER_NAME = "X-XSS-Protection".freeze
6
6
  DEFAULT_VALUE = "1; mode=block"
7
- VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
8
- CONFIG_KEY = :x_xss_protection
7
+ VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/
9
8
 
10
9
  class << self
11
10
  # Public: generate an X-Xss-Protection header.
12
11
  #
13
12
  # Returns a default header if no configuration is provided, or a
14
13
  # header name and value based on the config.
15
- def make_header(config = nil)
14
+ def make_header(config = nil, user_agent = nil)
15
+ return if config == OPT_OUT
16
16
  [HEADER_NAME, config || DEFAULT_VALUE]
17
17
  end
18
18
 
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require "secure_headers/configuration"
3
2
  require "secure_headers/hash_helper"
4
3
  require "secure_headers/headers/cookie"
5
4
  require "secure_headers/headers/public_key_pins"
@@ -18,6 +17,7 @@ require "secure_headers/railtie"
18
17
  require "secure_headers/view_helper"
19
18
  require "useragent"
20
19
  require "singleton"
20
+ require "secure_headers/configuration"
21
21
 
22
22
  # All headers (except for hpkp) have a default value. Provide SecureHeaders::OPT_OUT
23
23
  # or ":optout_of_protection" as a config value to disable a given header
@@ -52,29 +52,9 @@ module SecureHeaders
52
52
  HTTPS = "https".freeze
53
53
  CSP = ContentSecurityPolicy
54
54
 
55
- ALL_HEADER_CLASSES = [
56
- ExpectCertificateTransparency,
57
- ClearSiteData,
58
- ContentSecurityPolicyConfig,
59
- ContentSecurityPolicyReportOnlyConfig,
60
- StrictTransportSecurity,
61
- PublicKeyPins,
62
- ReferrerPolicy,
63
- XContentTypeOptions,
64
- XDownloadOptions,
65
- XFrameOptions,
66
- XPermittedCrossDomainPolicies,
67
- XXssProtection
68
- ].freeze
69
-
70
- ALL_HEADERS_BESIDES_CSP = (
71
- ALL_HEADER_CLASSES -
72
- [ContentSecurityPolicyConfig, ContentSecurityPolicyReportOnlyConfig]
73
- ).freeze
74
-
75
55
  # Headers set on http requests (excludes STS and HPKP)
76
- HTTP_HEADER_CLASSES =
77
- (ALL_HEADER_CLASSES - [StrictTransportSecurity, PublicKeyPins]).freeze
56
+ HTTPS_HEADER_CLASSES =
57
+ [StrictTransportSecurity, PublicKeyPins].freeze
78
58
 
79
59
  class << self
80
60
  # Public: override a given set of directives for the current request. If a
@@ -153,7 +133,7 @@ module SecureHeaders
153
133
  # Public: opts out of setting all headers by telling secure_headers to use
154
134
  # the NOOP configuration.
155
135
  def opt_out_of_all_protection(request)
156
- use_secure_headers_override(request, Configuration::NOOP_CONFIGURATION)
136
+ use_secure_headers_override(request, Configuration::NOOP_OVERRIDE)
157
137
  end
158
138
 
159
139
  # Public: Builds the hash of headers that should be applied base on the
@@ -168,27 +148,16 @@ module SecureHeaders
168
148
  def header_hash_for(request)
169
149
  prevent_dup = true
170
150
  config = config_for(request, prevent_dup)
171
- headers = config.cached_headers
151
+ config.validate_config!
172
152
  user_agent = UserAgent.parse(request.user_agent)
153
+ headers = config.generate_headers(user_agent)
173
154
 
174
- if !config.csp.opt_out? && config.csp.modified?
175
- headers = update_cached_csp(config.csp, headers, user_agent)
176
- end
177
-
178
- if !config.csp_report_only.opt_out? && config.csp_report_only.modified?
179
- headers = update_cached_csp(config.csp_report_only, headers, user_agent)
180
- end
181
-
182
- header_classes_for(request).each_with_object({}) do |klass, hash|
183
- if header = headers[klass::CONFIG_KEY]
184
- header_name, value = if klass == ContentSecurityPolicyConfig || klass == ContentSecurityPolicyReportOnlyConfig
185
- csp_header_for_ua(header, user_agent)
186
- else
187
- header
188
- end
189
- hash[header_name] = value
155
+ if request.scheme != HTTPS
156
+ HTTPS_HEADER_CLASSES.each do |klass|
157
+ headers.delete(klass::HEADER_NAME)
190
158
  end
191
159
  end
160
+ headers
192
161
  end
193
162
 
194
163
  # Public: specify which named override will be used for this request.
@@ -196,11 +165,9 @@ module SecureHeaders
196
165
  #
197
166
  # name - the name of the previously configured override.
198
167
  def use_secure_headers_override(request, name)
199
- if config = Configuration.get(name, internal: true)
200
- override_secure_headers_request_config(request, config)
201
- else
202
- raise ArgumentError.new("no override by the name of #{name} has been configured")
203
- end
168
+ config = config_for(request)
169
+ config.override(name)
170
+ override_secure_headers_request_config(request, config)
204
171
  end
205
172
 
206
173
  # Public: gets or creates a nonce for CSP.
@@ -228,7 +195,7 @@ module SecureHeaders
228
195
  # Falls back to the global config
229
196
  def config_for(request, prevent_dup = false)
230
197
  config = request.env[SECURE_HEADERS_CONFIG] ||
231
- Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
198
+ Configuration.send(:default_config)
232
199
 
233
200
 
234
201
  # Global configs are frozen, per-request configs are not. When we're not
@@ -285,35 +252,6 @@ module SecureHeaders
285
252
  def override_secure_headers_request_config(request, config)
286
253
  request.env[SECURE_HEADERS_CONFIG] = config
287
254
  end
288
-
289
- # Private: determines which headers are applicable to a given request.
290
- #
291
- # Returns a list of classes whose corresponding header values are valid for
292
- # this request.
293
- def header_classes_for(request)
294
- if request.scheme == HTTPS
295
- ALL_HEADER_CLASSES
296
- else
297
- HTTP_HEADER_CLASSES
298
- end
299
- end
300
-
301
- def update_cached_csp(config, headers, user_agent)
302
- headers = Configuration.send(:deep_copy, headers)
303
- headers[config.class::CONFIG_KEY] = {}
304
- variation = ContentSecurityPolicy.ua_to_variation(user_agent)
305
- headers[config.class::CONFIG_KEY][variation] = ContentSecurityPolicy.make_header(config, user_agent)
306
- headers
307
- end
308
-
309
- # Private: chooses the applicable CSP header for the provided user agent.
310
- #
311
- # headers - a hash of header_config_key => [header_name, header_value]
312
- #
313
- # Returns a CSP [header, value] array
314
- def csp_header_for_ua(headers, user_agent)
315
- headers[ContentSecurityPolicy.ua_to_variation(user_agent)]
316
- end
317
255
  end
318
256
 
319
257
  # These methods are mixed into controllers and delegate to the class method
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
  Gem::Specification.new do |gem|
4
4
  gem.name = "secure_headers"
5
- gem.version = "5.1.0"
5
+ gem.version = "6.0.0.alpha01"
6
6
  gem.authors = ["Neil Matatall"]
7
7
  gem.email = ["neil.matatall@gmail.com"]
8
8
  gem.description = "Manages application of security headers with many safe defaults."
@@ -5,88 +5,33 @@ module SecureHeaders
5
5
  describe Configuration do
6
6
  before(:each) do
7
7
  reset_config
8
- Configuration.default
9
8
  end
10
9
 
11
10
  it "has a default config" do
12
- expect(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)).to_not be_nil
13
- end
14
-
15
- it "warns when using deprecated internal-ish #get API" do
16
- expect(Kernel).to receive(:warn).once.with(/`#get` is deprecated/)
17
- Configuration.get(Configuration::DEFAULT_CONFIG)
18
- end
19
-
20
- it "has an 'noop' config" do
21
- expect(Configuration.get(Configuration::NOOP_CONFIGURATION, internal: true)).to_not be_nil
22
- end
23
-
24
- it "precomputes headers upon creation" do
25
- default_config = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
26
- header_hash = default_config.cached_headers.each_with_object({}) do |(key, value), hash|
27
- header_name, header_value = if key == :csp
28
- value["Chrome"]
29
- else
30
- value
31
- end
32
-
33
- hash[header_name] = header_value
34
- end
35
- expect_default_values(header_hash)
11
+ expect(Configuration.default).to_not be_nil
36
12
  end
37
13
 
38
- it "copies config values when duping" do
39
- Configuration.override(:test_override, Configuration::NOOP_CONFIGURATION) do
40
- # do nothing, just copy it
41
- end
42
-
43
- config = Configuration.get(:test_override, internal: true)
44
- noop = Configuration.get(Configuration::NOOP_CONFIGURATION, internal: true)
45
- [:csp, :csp_report_only, :cookies].each do |key|
46
- expect(config.send(key)).to eq(noop.send(key))
47
- end
14
+ it "has an 'noop' override" do
15
+ Configuration.default
16
+ expect(Configuration.overrides(Configuration::NOOP_OVERRIDE)).to_not be_nil
48
17
  end
49
18
 
50
- it "regenerates cached headers when building an override" do
51
- Configuration.override(:test_override) do |config|
52
- config.x_content_type_options = OPT_OUT
19
+ it "dup results in a copy of the default config" do
20
+ Configuration.default
21
+ original_configuration = Configuration.send(:default_config)
22
+ configuration = Configuration.dup
23
+ expect(original_configuration).not_to be(configuration)
24
+ Configuration::CONFIG_ATTRIBUTES.each do |attr|
25
+ expect(original_configuration.send(attr)).to eq(configuration.send(attr))
53
26
  end
54
-
55
- expect(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).cached_headers).to_not eq(Configuration.get(:test_override, internal: true).cached_headers)
56
27
  end
57
28
 
58
- it "stores an override of the global config" do
29
+ it "stores an override" do
59
30
  Configuration.override(:test_override) do |config|
60
31
  config.x_frame_options = "DENY"
61
32
  end
62
33
 
63
- expect(Configuration.get(:test_override, internal: true)).to_not be_nil
64
- end
65
-
66
- it "deep dup's config values when overriding so the original cannot be modified" do
67
- Configuration.override(:override) do |config|
68
- config.csp[:default_src] << "'self'"
69
- end
70
-
71
- default = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
72
- override = Configuration.get(:override, internal: true)
73
-
74
- expect(override.csp.directive_value(:default_src)).not_to be(default.csp.directive_value(:default_src))
75
- end
76
-
77
- it "allows you to override an override" do
78
- Configuration.override(:override) do |config|
79
- config.csp = { default_src: %w('self'), script_src: %w('self')}
80
- end
81
-
82
- Configuration.override(:second_override, :override) do |config|
83
- config.csp = config.csp.merge(script_src: %w(example.org))
84
- end
85
-
86
- original_override = Configuration.get(:override, internal: true)
87
- expect(original_override.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self'))
88
- override_config = Configuration.get(:second_override, internal: true)
89
- expect(override_config.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self' example.org))
34
+ expect(Configuration.overrides(:test_override)).to_not be_nil
90
35
  end
91
36
 
92
37
  it "deprecates the secure_cookies configuration" do
@@ -106,7 +51,7 @@ module SecureHeaders
106
51
  config.cookies = OPT_OUT
107
52
  end
108
53
 
109
- config = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
54
+ config = Configuration.dup
110
55
  expect(config.cookies).to eq(OPT_OUT)
111
56
  end
112
57
 
@@ -115,7 +60,7 @@ module SecureHeaders
115
60
  config.cookies = {httponly: true, secure: true, samesite: {lax: false}}
116
61
  end
117
62
 
118
- config = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
63
+ config = Configuration.dup
119
64
  expect(config.cookies).to eq({httponly: true, secure: true, samesite: {lax: false}})
120
65
  end
121
66
  end
@@ -28,11 +28,6 @@ module SecureHeaders
28
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:")
29
29
  end
30
30
 
31
- it "deprecates and escapes semicolons in directive source lists" do
32
- expect(Kernel).to receive(:warn).with("frame_ancestors contains a ; in 'google.com;script-src *;.;' which will raise an error in future versions. It has been replaced with a blank space.")
33
- expect(ContentSecurityPolicy.new(frame_ancestors: %w(https://google.com;script-src https://*;.;)).value).to eq("frame-ancestors google.com script-src * .")
34
- end
35
-
36
31
  it "discards 'none' values if any other source expressions are present" do
37
32
  csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
38
33
  expect(csp.value).not_to include("'none'")
@@ -129,7 +124,7 @@ module SecureHeaders
129
124
 
130
125
  it "supports strict-dynamic" do
131
126
  csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456}, USER_AGENTS[:chrome])
132
- expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456'")
127
+ expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456' 'unsafe-inline'")
133
128
  end
134
129
 
135
130
  context "browser sniffing" do
@@ -148,44 +143,44 @@ module SecureHeaders
148
143
 
149
144
  it "does not filter any directives for Chrome" do
150
145
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
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; worker-src worker-src.com; report-uri report-uri.com")
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' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; worker-src worker-src.com; report-uri report-uri.com")
152
147
  end
153
148
 
154
149
  it "does not filter any directives for Opera" do
155
150
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
156
- 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; worker-src worker-src.com; report-uri report-uri.com")
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' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; worker-src worker-src.com; report-uri report-uri.com")
157
152
  end
158
153
 
159
154
  it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
160
155
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
161
- 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")
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' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
162
157
  end
163
158
 
164
159
  it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
165
160
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
166
- 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")
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' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
167
162
  end
168
163
 
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 Edge" do
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, hash sources, and plugin-types for Edge" do
170
165
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
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")
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 'nonce-123456' 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
172
167
  end
173
168
 
174
- 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
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, hash sources, and plugin-types for safari" do
175
170
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
176
- 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")
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 'nonce-123456' 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
177
172
  end
178
173
 
179
- it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, nonce sources, and hash sources for safari 10 and higher" do
174
+ it "adds 'unsafe-inline', filters blocked-all-mixed-content, upgrade-insecure-requests, and hash sources for safari 10 and higher" do
180
175
  policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari10])
181
- 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")
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' 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
182
177
  end
183
178
 
184
179
  it "falls back to standard Firefox defaults when the useragent version is not present" do
185
180
  ua = USER_AGENTS[:firefox].dup
186
181
  allow(ua).to receive(:version).and_return(nil)
187
182
  policy = ContentSecurityPolicy.new(complex_opts, ua)
188
- 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")
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' 'unsafe-inline'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
189
184
  end
190
185
  end
191
186
  end