secure_headers 5.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 36d743dc370dce07a032c6e5154eef9081e5d258
4
- data.tar.gz: 89c7965e04093df147c7543f9b3cd4cc3e347e41
3
+ metadata.gz: 32ea96aca183a8341bec40b998486beed14d59e8
4
+ data.tar.gz: 3081354f4bd486f5ea49d22f680d1ac2d36de4aa
5
5
  SHA512:
6
- metadata.gz: 11985973764bd80715c68e232e977074f084557deea8b2b96962d5c74f79af81601df30d6dda3150d5b278c75ee7f45c95e93bac26d62265fd5dabd99a39e024
7
- data.tar.gz: 9cf62ba5f6ebaad74cec79c2201faf1521aad2a65627d5a64c74c9ff668b535664c1c49b285578713ce2bc2472b704e6b972c51074d2574ff2ad574cf6b87870
6
+ metadata.gz: cc8c64abdabaab458be788ee09a18984bab17cb8a01ef6eb76c75099cfaa5b8fc5962f5094e39f7067ebece388794e7f53a2e44f2fecdbfe4146a59a981211df
7
+ data.tar.gz: f8c369a5a2ac0e7547e231100c2a946a9204684d4db33206cc3baf0dd786b99c5a6d05cdaaaca5fcb439530a6d385d029c265dbc84f9b6ff665df52ad1191537
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.4.1
1
+ 2.4.2
data/.travis.yml CHANGED
@@ -2,9 +2,9 @@ language: ruby
2
2
 
3
3
  rvm:
4
4
  - ruby-head
5
- - 2.4.1
6
- - 2.3.4
7
- - 2.2
5
+ - 2.6
6
+ - 2.5
7
+ - 2.4
8
8
  - jruby-head
9
9
 
10
10
  env:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## 5.1.0
2
+
3
+ Fixes semicolon injection issue reported by @mvgijssel see https://github.com/twitter/secure_headers/issues/418
4
+
5
+ ## 5.0.5
6
+
7
+ A release to deprecate `SecureHeaders::Configuration#get` in prep for 6.x
8
+
9
+ ## 5.0.4
10
+
11
+ - Adds support for `nonced_stylesheet_pack_tag` #373 (@paulfri)
12
+
13
+ ## 5.0.3
14
+
15
+ - Add nonced versions of Rails link/include tags #372 (@steveh)
16
+
17
+ ## 5.0.2
18
+
19
+ - Updates `Referrer-Policy` header to support multiple policy values
20
+
1
21
  ## 5.0.1
2
22
 
3
23
  - Updates `Expect-CT` header to use a comma separator between directives, as specified in the most current spec.
data/Gemfile CHANGED
@@ -5,18 +5,19 @@ gemspec
5
5
 
6
6
  group :test do
7
7
  gem "coveralls"
8
- gem "json", "~> 1"
8
+ gem "json"
9
9
  gem "pry-nav"
10
- gem "rack", "~> 1"
10
+ gem "rack"
11
11
  gem "rspec"
12
- gem "rubocop", "~> 0.47.0"
12
+ gem "rubocop", "< 0.68"
13
13
  gem "rubocop-github"
14
- gem "term-ansicolor", "< 1.4"
15
- gem "tins", "~> 1.6.0" # 1.7 requires ruby 2.0
14
+ gem "term-ansicolor"
15
+ gem "tins"
16
16
  end
17
17
 
18
18
  group :guard do
19
19
  gem "growl"
20
20
  gem "guard-rspec", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22, :ruby_23, :ruby_24]
21
21
  gem "rb-fsevent"
22
+ gem "terminal-notifier-guard"
22
23
  end
data/README.md CHANGED
@@ -18,7 +18,6 @@ The gem will automatically apply several headers that are related to security.
18
18
  - X-Download-Options - [Prevent file downloads opening](https://msdn.microsoft.com/library/jj542450(v=vs.85).aspx)
19
19
  - X-Permitted-Cross-Domain-Policies - [Restrict Adobe Flash Player's access to data](https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html)
20
20
  - Referrer-Policy - [Referrer Policy draft](https://w3c.github.io/webappsec-referrer-policy/)
21
- - Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorities. [Public Key Pinning Specification](https://tools.ietf.org/html/rfc7469)
22
21
  - Expect-CT - Only use certificates that are present in the certificate transparency logs. [Expect-CT draft specification](https://datatracker.ietf.org/doc/draft-stark-expect-ct/).
23
22
  - Clear-Site-Data - Clearing browser data for origin. [Clear-Site-Data specification](https://w3c.github.io/webappsec-clear-site-data/).
24
23
 
@@ -31,7 +30,6 @@ It can also mark all http cookies with the Secure, HttpOnly and SameSite attribu
31
30
  - [Named overrides and appends](docs/named_overrides_and_appends.md)
32
31
  - [Per action configuration](docs/per_action_configuration.md)
33
32
  - [Cookies](docs/cookies.md)
34
- - [HPKP](docs/HPKP.md)
35
33
  - [Hashes](docs/hashes.md)
36
34
  - [Sinatra Config](docs/sinatra.md)
37
35
 
@@ -67,13 +65,13 @@ SecureHeaders::Configuration.default do |config|
67
65
  }
68
66
  }
69
67
  # Add "; preload" and submit the site to hstspreload.org for best protection.
70
- config.hsts = "max-age=#{20.years.to_i}"
68
+ config.hsts = "max-age=#{1.week.to_i}"
71
69
  config.x_frame_options = "DENY"
72
70
  config.x_content_type_options = "nosniff"
73
71
  config.x_xss_protection = "1; mode=block"
74
72
  config.x_download_options = "noopen"
75
73
  config.x_permitted_cross_domain_policies = "none"
76
- config.referrer_policy = "origin-when-cross-origin"
74
+ config.referrer_policy = %w(origin-when-cross-origin strict-origin-when-cross-origin)
77
75
  config.csp = {
78
76
  # "meta" values. these will shape the header, but the values are not included in the header.
79
77
  preserve_schemes: true, # default: false. Schemes are removed from host sources to save bytes and discourage mixed content.
@@ -66,6 +66,14 @@ body {
66
66
  background-color: black;
67
67
  }
68
68
  <% end %>
69
+
70
+ <%= nonced_javascript_include_tag "include.js" %>
71
+
72
+ <%= nonced_javascript_pack_tag "pack.js" %>
73
+
74
+ <%= nonced_stylesheet_link_tag "link.css" %>
75
+
76
+ <%= nonced_stylesheet_pack_tag "pack.css" %>
69
77
  ```
70
78
 
71
79
  becomes:
@@ -130,4 +138,4 @@ end
130
138
  class SessionsController < ApplicationController
131
139
  after_action :clear_browser_cache, only: :destroy
132
140
  end
133
- ```
141
+ ```
@@ -28,7 +28,7 @@ module SecureHeaders
28
28
  #
29
29
  # Returns: the newly created config
30
30
  def override(name, base = DEFAULT_CONFIG, &block)
31
- unless get(base)
31
+ unless get(base, internal: true)
32
32
  raise NotYetConfiguredError, "#{base} policy not yet supplied"
33
33
  end
34
34
  override = @configurations[base].dup
@@ -40,7 +40,11 @@ module SecureHeaders
40
40
  #
41
41
  # Returns the configuration with a given name or raises a
42
42
  # NotYetConfiguredError if `default` has not been called.
43
- def get(name = DEFAULT_CONFIG)
43
+ def get(name = DEFAULT_CONFIG, internal: false)
44
+ unless internal
45
+ Kernel.warn "#{Kernel.caller.first}: [DEPRECATION] `#get` is deprecated. It will be removed in the next major release. Use SecureHeaders::Configuration.dup to retrieve the default config."
46
+ end
47
+
44
48
  if @configurations.nil?
45
49
  raise NotYetConfiguredError, "Default policy not yet supplied"
46
50
  end
@@ -308,7 +312,7 @@ module SecureHeaders
308
312
  def generate_csp_headers_for_config(headers, header_key, csp_config)
309
313
  unless csp_config.opt_out?
310
314
  headers[header_key] = {}
311
- ContentSecurityPolicy::VARIATIONS.each do |name, _|
315
+ ContentSecurityPolicy::VARIATIONS.each_key do |name|
312
316
  csp = ContentSecurityPolicy.make_header(csp_config, UserAgent.parse(name))
313
317
  headers[header_key][name] = csp.freeze
314
318
  end
@@ -48,7 +48,7 @@ module SecureHeaders
48
48
  #
49
49
  # Returns a String of quoted values that are comma separated.
50
50
  def make_header_value(types)
51
- types.map { |t| "\"#{t}\""}.join(", ")
51
+ types.map { |t| %("#{t}") }.join(", ")
52
52
  end
53
53
  end
54
54
  end
@@ -138,8 +138,14 @@ module SecureHeaders
138
138
  end
139
139
 
140
140
  if source_list != OPT_OUT && source_list && source_list.any?
141
- normalized_source_list = minify_source_list(directive, source_list)
142
- [symbol_to_hyphen_case(directive), normalized_source_list].join(" ")
141
+ minified_source_list = minify_source_list(directive, source_list).join(" ")
142
+
143
+ if minified_source_list.include?(";")
144
+ Kernel.warn("#{directive} contains a ; in '#{minified_source_list}' which will raise an error in future versions. It has been replaced with a blank space.")
145
+ end
146
+
147
+ escaped_source_list = minified_source_list.gsub(";", " ")
148
+ [symbol_to_hyphen_case(directive), escaped_source_list].join(" ").strip
143
149
  end
144
150
  end
145
151
 
@@ -22,14 +22,21 @@ module SecureHeaders
22
22
  # Returns a default header if no configuration is provided, or a
23
23
  # header name and value based on the config.
24
24
  def make_header(config = nil)
25
- [HEADER_NAME, config || DEFAULT_VALUE]
25
+ config ||= DEFAULT_VALUE
26
+ [HEADER_NAME, Array(config).join(", ")]
26
27
  end
27
28
 
28
29
  def validate_config!(config)
29
- return if config.nil? || config == OPT_OUT
30
- raise TypeError.new("Must be a string. Found #{config.class}: #{config}") unless config.is_a?(String)
31
- unless VALID_POLICIES.include?(config.downcase)
32
- raise ReferrerPolicyConfigError.new("Value can only be one of #{VALID_POLICIES.join(', ')}")
30
+ case config
31
+ when nil, OPT_OUT
32
+ # valid
33
+ when String, Array
34
+ config = Array(config)
35
+ unless config.all? { |t| t.is_a?(String) && VALID_POLICIES.include?(t.downcase) }
36
+ raise ReferrerPolicyConfigError.new("Value can only be one or more of #{VALID_POLICIES.join(", ")}")
37
+ end
38
+ else
39
+ raise TypeError.new("Must be a string or array of strings. Found #{config.class}: #{config}")
33
40
  end
34
41
  end
35
42
  end
@@ -7,21 +7,61 @@ module SecureHeaders
7
7
  class UnexpectedHashedScriptException < StandardError; end
8
8
 
9
9
  # Public: create a style tag using the content security policy nonce.
10
- # Instructs secure_headers to append a nonce to style/script-src directives.
10
+ # Instructs secure_headers to append a nonce to style-src directive.
11
11
  #
12
12
  # Returns an html-safe style tag with the nonce attribute.
13
13
  def nonced_style_tag(content_or_options = {}, &block)
14
14
  nonced_tag(:style, content_or_options, block)
15
15
  end
16
16
 
17
+ # Public: create a stylesheet link tag using the content security policy nonce.
18
+ # Instructs secure_headers to append a nonce to style-src directive.
19
+ #
20
+ # Returns an html-safe link tag with the nonce attribute.
21
+ def nonced_stylesheet_link_tag(*args, &block)
22
+ opts = extract_options(args).merge(nonce: content_security_policy_nonce(:style))
23
+
24
+ stylesheet_link_tag(*args, opts, &block)
25
+ end
26
+
17
27
  # Public: create a script tag using the content security policy nonce.
18
- # Instructs secure_headers to append a nonce to style/script-src directives.
28
+ # Instructs secure_headers to append a nonce to script-src directive.
19
29
  #
20
30
  # Returns an html-safe script tag with the nonce attribute.
21
31
  def nonced_javascript_tag(content_or_options = {}, &block)
22
32
  nonced_tag(:script, content_or_options, block)
23
33
  end
24
34
 
35
+ # Public: create a script src tag using the content security policy nonce.
36
+ # Instructs secure_headers to append a nonce to script-src directive.
37
+ #
38
+ # Returns an html-safe script tag with the nonce attribute.
39
+ def nonced_javascript_include_tag(*args, &block)
40
+ opts = extract_options(args).merge(nonce: content_security_policy_nonce(:script))
41
+
42
+ javascript_include_tag(*args, opts, &block)
43
+ end
44
+
45
+ # Public: create a script Webpacker pack tag using the content security policy nonce.
46
+ # Instructs secure_headers to append a nonce to script-src directive.
47
+ #
48
+ # Returns an html-safe script tag with the nonce attribute.
49
+ def nonced_javascript_pack_tag(*args, &block)
50
+ opts = extract_options(args).merge(nonce: content_security_policy_nonce(:script))
51
+
52
+ javascript_pack_tag(*args, opts, &block)
53
+ end
54
+
55
+ # Public: create a stylesheet Webpacker link tag using the content security policy nonce.
56
+ # Instructs secure_headers to append a nonce to style-src directive.
57
+ #
58
+ # Returns an html-safe link tag with the nonce attribute.
59
+ def nonced_stylesheet_pack_tag(*args, &block)
60
+ opts = extract_options(args).merge(nonce: content_security_policy_nonce(:style))
61
+
62
+ stylesheet_pack_tag(*args, opts, &block)
63
+ end
64
+
25
65
  # Public: use the content security policy nonce for this request directly.
26
66
  # Instructs secure_headers to append a nonce to style/script-src directives.
27
67
  #
@@ -114,6 +154,14 @@ module SecureHeaders
114
154
  end
115
155
  content_tag type, content, options.merge(nonce: content_security_policy_nonce(type))
116
156
  end
157
+
158
+ def extract_options(args)
159
+ if args.last.is_a? Hash
160
+ args.pop
161
+ else
162
+ {}
163
+ end
164
+ end
117
165
  end
118
166
  end
119
167
 
@@ -196,7 +196,7 @@ module SecureHeaders
196
196
  #
197
197
  # name - the name of the previously configured override.
198
198
  def use_secure_headers_override(request, name)
199
- if config = Configuration.get(name)
199
+ if config = Configuration.get(name, internal: true)
200
200
  override_secure_headers_request_config(request, config)
201
201
  else
202
202
  raise ArgumentError.new("no override by the name of #{name} has been configured")
@@ -228,7 +228,7 @@ module SecureHeaders
228
228
  # Falls back to the global config
229
229
  def config_for(request, prevent_dup = false)
230
230
  config = request.env[SECURE_HEADERS_CONFIG] ||
231
- Configuration.get(Configuration::DEFAULT_CONFIG)
231
+ Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
232
232
 
233
233
 
234
234
  # Global configs are frozen, per-request configs are not. When we're not
@@ -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.0.1"
5
+ gem.version = "5.1.0"
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."
@@ -17,13 +17,4 @@ Gem::Specification.new do |gem|
17
17
  gem.require_paths = ["lib"]
18
18
  gem.add_development_dependency "rake"
19
19
  gem.add_dependency "useragent", ">= 0.15.0"
20
-
21
- # TODO: delete this after 4.1 is cut or a number of 4.0.x releases have occurred
22
- gem.post_install_message = <<-POST_INSTALL
23
-
24
- **********
25
- :wave: secure_headers 5.0 introduces a lot of breaking changes (in the name of security!). It's highly likely you will need to update your secure_headers cookie configuration to avoid breaking things. See the upgrade guide for details: https://github.com/twitter/secureheaders/blob/master/docs/upgrading-to-4-0.md https://github.com/twitter/secureheaders/blob/master/docs/upgrading-to-4-0.md
26
- **********
27
-
28
- POST_INSTALL
29
20
  end
@@ -9,15 +9,20 @@ module SecureHeaders
9
9
  end
10
10
 
11
11
  it "has a default config" do
12
- expect(Configuration.get(Configuration::DEFAULT_CONFIG)).to_not be_nil
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)
13
18
  end
14
19
 
15
20
  it "has an 'noop' config" do
16
- expect(Configuration.get(Configuration::NOOP_CONFIGURATION)).to_not be_nil
21
+ expect(Configuration.get(Configuration::NOOP_CONFIGURATION, internal: true)).to_not be_nil
17
22
  end
18
23
 
19
24
  it "precomputes headers upon creation" do
20
- default_config = Configuration.get(Configuration::DEFAULT_CONFIG)
25
+ default_config = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
21
26
  header_hash = default_config.cached_headers.each_with_object({}) do |(key, value), hash|
22
27
  header_name, header_value = if key == :csp
23
28
  value["Chrome"]
@@ -35,8 +40,8 @@ module SecureHeaders
35
40
  # do nothing, just copy it
36
41
  end
37
42
 
38
- config = Configuration.get(:test_override)
39
- noop = Configuration.get(Configuration::NOOP_CONFIGURATION)
43
+ config = Configuration.get(:test_override, internal: true)
44
+ noop = Configuration.get(Configuration::NOOP_CONFIGURATION, internal: true)
40
45
  [:csp, :csp_report_only, :cookies].each do |key|
41
46
  expect(config.send(key)).to eq(noop.send(key))
42
47
  end
@@ -47,7 +52,7 @@ module SecureHeaders
47
52
  config.x_content_type_options = OPT_OUT
48
53
  end
49
54
 
50
- expect(Configuration.get.cached_headers).to_not eq(Configuration.get(:test_override).cached_headers)
55
+ expect(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).cached_headers).to_not eq(Configuration.get(:test_override, internal: true).cached_headers)
51
56
  end
52
57
 
53
58
  it "stores an override of the global config" do
@@ -55,7 +60,7 @@ module SecureHeaders
55
60
  config.x_frame_options = "DENY"
56
61
  end
57
62
 
58
- expect(Configuration.get(:test_override)).to_not be_nil
63
+ expect(Configuration.get(:test_override, internal: true)).to_not be_nil
59
64
  end
60
65
 
61
66
  it "deep dup's config values when overriding so the original cannot be modified" do
@@ -63,8 +68,8 @@ module SecureHeaders
63
68
  config.csp[:default_src] << "'self'"
64
69
  end
65
70
 
66
- default = Configuration.get
67
- override = Configuration.get(:override)
71
+ default = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
72
+ override = Configuration.get(:override, internal: true)
68
73
 
69
74
  expect(override.csp.directive_value(:default_src)).not_to be(default.csp.directive_value(:default_src))
70
75
  end
@@ -78,9 +83,9 @@ module SecureHeaders
78
83
  config.csp = config.csp.merge(script_src: %w(example.org))
79
84
  end
80
85
 
81
- original_override = Configuration.get(:override)
86
+ original_override = Configuration.get(:override, internal: true)
82
87
  expect(original_override.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self'))
83
- override_config = Configuration.get(:second_override)
88
+ override_config = Configuration.get(:second_override, internal: true)
84
89
  expect(override_config.csp.to_h).to eq(default_src: %w('self'), script_src: %w('self' example.org))
85
90
  end
86
91
 
@@ -101,7 +106,7 @@ module SecureHeaders
101
106
  config.cookies = OPT_OUT
102
107
  end
103
108
 
104
- config = Configuration.get
109
+ config = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
105
110
  expect(config.cookies).to eq(OPT_OUT)
106
111
  end
107
112
 
@@ -110,7 +115,7 @@ module SecureHeaders
110
115
  config.cookies = {httponly: true, secure: true, samesite: {lax: false}}
111
116
  end
112
117
 
113
- config = Configuration.get
118
+ config = Configuration.get(Configuration::DEFAULT_CONFIG, internal: true)
114
119
  expect(config.cookies).to eq({httponly: true, secure: true, samesite: {lax: false}})
115
120
  end
116
121
  end
@@ -28,6 +28,11 @@ 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
+
31
36
  it "discards 'none' values if any other source expressions are present" do
32
37
  csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
33
38
  expect(csp.value).not_to include("'none'")
@@ -152,7 +152,7 @@ module SecureHeaders
152
152
  script_src: %w('self'),
153
153
  }
154
154
  end
155
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, style_src: %w(anothercdn.com))
155
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, style_src: %w(anothercdn.com))
156
156
  csp = ContentSecurityPolicy.new(combined_config)
157
157
  expect(csp.name).to eq(ContentSecurityPolicyConfig::HEADER_NAME)
158
158
  expect(csp.value).to eq("default-src https:; script-src 'self'; style-src https: anothercdn.com")
@@ -167,7 +167,7 @@ module SecureHeaders
167
167
  }.freeze
168
168
  end
169
169
  report_uri = "https://report-uri.io/asdf"
170
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_uri: [report_uri])
170
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, report_uri: [report_uri])
171
171
  csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
172
172
  expect(csp.value).to include("report-uri #{report_uri}")
173
173
  end
@@ -183,7 +183,7 @@ module SecureHeaders
183
183
  non_default_source_additions = ContentSecurityPolicy::NON_FETCH_SOURCES.each_with_object({}) do |directive, hash|
184
184
  hash[directive] = %w("http://example.org)
185
185
  end
186
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, non_default_source_additions)
186
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, non_default_source_additions)
187
187
 
188
188
  ContentSecurityPolicy::NON_FETCH_SOURCES.each do |directive|
189
189
  expect(combined_config[directive]).to eq(%w("http://example.org))
@@ -198,7 +198,7 @@ module SecureHeaders
198
198
  report_only: false
199
199
  }
200
200
  end
201
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, report_only: true)
201
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, report_only: true)
202
202
  csp = ContentSecurityPolicy.new(combined_config, USER_AGENTS[:firefox])
203
203
  expect(csp.name).to eq(ContentSecurityPolicyReportOnlyConfig::HEADER_NAME)
204
204
  end
@@ -211,7 +211,7 @@ module SecureHeaders
211
211
  block_all_mixed_content: false
212
212
  }
213
213
  end
214
- combined_config = ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, block_all_mixed_content: true)
214
+ combined_config = ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, block_all_mixed_content: true)
215
215
  csp = ContentSecurityPolicy.new(combined_config)
216
216
  expect(csp.value).to eq("default-src https:; block-all-mixed-content; script-src 'self'")
217
217
  end
@@ -221,7 +221,7 @@ module SecureHeaders
221
221
  config.csp = OPT_OUT
222
222
  end
223
223
  expect do
224
- ContentSecurityPolicy.combine_policies(Configuration.get.csp.to_h, script_src: %w(anothercdn.com))
224
+ ContentSecurityPolicy.combine_policies(Configuration.get(Configuration::DEFAULT_CONFIG, internal: true).csp.to_h, script_src: %w(anothercdn.com))
225
225
  end.to raise_error(ContentSecurityPolicyConfigError)
226
226
  end
227
227
  end
@@ -5,6 +5,7 @@ module SecureHeaders
5
5
  describe ReferrerPolicy do
6
6
  specify { expect(ReferrerPolicy.make_header).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin"]) }
7
7
  specify { expect(ReferrerPolicy.make_header("no-referrer")).to eq([ReferrerPolicy::HEADER_NAME, "no-referrer"]) }
8
+ specify { expect(ReferrerPolicy.make_header(%w(origin-when-cross-origin strict-origin-when-cross-origin))).to eq([ReferrerPolicy::HEADER_NAME, "origin-when-cross-origin, strict-origin-when-cross-origin"]) }
8
9
 
9
10
  context "valid configuration values" do
10
11
  it "accepts 'no-referrer'" do
@@ -60,14 +61,31 @@ module SecureHeaders
60
61
  ReferrerPolicy.validate_config!(nil)
61
62
  end.not_to raise_error
62
63
  end
64
+
65
+ it "accepts array of policy values" do
66
+ expect do
67
+ ReferrerPolicy.validate_config!(
68
+ %w(
69
+ origin-when-cross-origin
70
+ strict-origin-when-cross-origin
71
+ )
72
+ )
73
+ end.not_to raise_error
74
+ end
63
75
  end
64
76
 
65
- context "invlaid configuration values" do
77
+ context "invalid configuration values" do
66
78
  it "doesn't accept invalid values" do
67
79
  expect do
68
80
  ReferrerPolicy.validate_config!("open")
69
81
  end.to raise_error(ReferrerPolicyConfigError)
70
82
  end
83
+
84
+ it "doesn't accept invalid types" do
85
+ expect do
86
+ ReferrerPolicy.validate_config!({})
87
+ end.to raise_error(TypeError)
88
+ end
71
89
  end
72
90
  end
73
91
  end
@@ -50,7 +50,7 @@ module SecureHeaders
50
50
  end
51
51
  request = Rack::Request.new({})
52
52
  SecureHeaders.use_secure_headers_override(request, "my_custom_config")
53
- expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config"))
53
+ expect(request.env[SECURE_HEADERS_CONFIG]).to be(Configuration.get("my_custom_config", internal: true))
54
54
  _, env = middleware.call request.env
55
55
  expect(env[ContentSecurityPolicyConfig::HEADER_NAME]).to match("example.org")
56
56
  end
@@ -66,7 +66,7 @@ module SecureHeaders
66
66
  end
67
67
 
68
68
  it "allows opting out of cookie protection with OPT_OUT alone" do
69
- Configuration.default { |config| config.cookies = OPT_OUT}
69
+ Configuration.default { |config| config.cookies = OPT_OUT }
70
70
 
71
71
  # do NOT make this request https. non-https requests modify a config,
72
72
  # causing an exception when operating on OPT_OUT. This ensures we don't
@@ -39,6 +39,14 @@ class Message < ERB
39
39
  }
40
40
  </style>
41
41
 
42
+ <%= nonced_javascript_include_tag "include.js", defer: true %>
43
+
44
+ <%= nonced_javascript_pack_tag "pack.js", "otherpack.js", defer: true %>
45
+
46
+ <%= nonced_stylesheet_link_tag "link.css", media: :all %>
47
+
48
+ <%= nonced_stylesheet_pack_tag "pack.css", "otherpack.css", media: :all %>
49
+
42
50
  TEMPLATE
43
51
  end
44
52
 
@@ -59,11 +67,27 @@ TEMPLATE
59
67
  end
60
68
 
61
69
  if options.is_a?(Hash)
62
- options = options.map {|k, v| " #{k}=#{v}"}
70
+ options = options.map { |k, v| " #{k}=#{v}" }
63
71
  end
64
72
  "<#{type}#{options}>#{content}</#{type}>"
65
73
  end
66
74
 
75
+ def javascript_include_tag(*sources, **options)
76
+ sources.map do |source|
77
+ content_tag(:script, nil, options.merge(src: source))
78
+ end
79
+ end
80
+
81
+ alias_method :javascript_pack_tag, :javascript_include_tag
82
+
83
+ def stylesheet_link_tag(*sources, **options)
84
+ sources.map do |source|
85
+ content_tag(:link, nil, options.merge(href: source, rel: "stylesheet", media: "screen"))
86
+ end
87
+ end
88
+
89
+ alias_method :stylesheet_pack_tag, :stylesheet_link_tag
90
+
67
91
  def result
68
92
  super(binding)
69
93
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secure_headers
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.1
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Matatall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-19 00:00:00.000000000 Z
11
+ date: 2020-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -61,7 +61,6 @@ files:
61
61
  - LICENSE
62
62
  - README.md
63
63
  - Rakefile
64
- - docs/HPKP.md
65
64
  - docs/cookies.md
66
65
  - docs/hashes.md
67
66
  - docs/named_overrides_and_appends.md
@@ -115,12 +114,7 @@ homepage: https://github.com/twitter/secureheaders
115
114
  licenses:
116
115
  - Apache Public License 2.0
117
116
  metadata: {}
118
- post_install_message: |2+
119
-
120
- **********
121
- :wave: secure_headers 5.0 introduces a lot of breaking changes (in the name of security!). It's highly likely you will need to update your secure_headers cookie configuration to avoid breaking things. See the upgrade guide for details: https://github.com/twitter/secureheaders/blob/master/docs/upgrading-to-4-0.md https://github.com/twitter/secureheaders/blob/master/docs/upgrading-to-4-0.md
122
- **********
123
-
117
+ post_install_message:
124
118
  rdoc_options: []
125
119
  require_paths:
126
120
  - lib
@@ -136,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
130
  version: '0'
137
131
  requirements: []
138
132
  rubyforge_project:
139
- rubygems_version: 2.6.11
133
+ rubygems_version: 2.6.13
140
134
  signing_key:
141
135
  specification_version: 4
142
136
  summary: Add easily configured security headers to responses including content-security-policy,
data/docs/HPKP.md DELETED
@@ -1,17 +0,0 @@
1
- ## HTTP Public Key Pins
2
-
3
- Be aware that pinning error reporting is governed by the same rules as everything else. If you have a pinning failure that tries to report back to the same origin, by definition this will not work.
4
-
5
- ```ruby
6
- config.hpkp = {
7
- max_age: 60.days.to_i, # max_age is a required parameter
8
- include_subdomains: true, # whether or not to apply pins to subdomains
9
- # Per the spec, SHA256 hashes are the only currently supported format.
10
- pins: [
11
- {sha256: 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c'},
12
- {sha256: '73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f'}
13
- ],
14
- report_only: true, # defaults to false (report-only mode)
15
- report_uri: 'https://report-uri.io/example-hpkp'
16
- }
17
- ```