secure_headers 5.0.4 → 5.2.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: d30a295775010a197058029cc447bdf51ad1f93e
4
- data.tar.gz: 79a4928de6fe2508ebdfed92cfe36ba1d70cdce8
3
+ metadata.gz: 1b901bf6e9f4127c81ec207e46599054feb18f32
4
+ data.tar.gz: 642585cfc04901c83ff5d90ea1600f5ef141fec4
5
5
  SHA512:
6
- metadata.gz: 206573552433a49370adf984f538091ac3bd4c2e6ee0ba521f949762e145259725a3938ab2ec5124b5556fdd1502388ac8dd7e8d5fcc947c9f96e3ed78947cfa
7
- data.tar.gz: c53aab77376ad2d8fac21c2d684cc02dcf14322cd51591d7048f0865315a60b6266ce7f066651040b8d626adf311101e31b2b1690ce1e3b18190ea42f90f24ac
6
+ metadata.gz: 8e304a7d52e6dbf0a4fd2bb114201ba781e8565fd9ecbd4977e6169f922101f186c7024194c81ecd780376f2e4c18f7af3bd88f98cd2b86301b15de5e4148c44
7
+ data.tar.gz: 9d5e1e8f35954baea9e4fcec70af22e7f3c2d8b16deaa98d6c882c9373c9a76556a711696b081920d9279df96375dd6e2a473e265c9bfbf2e12a6f9a62da486f
data/.travis.yml CHANGED
@@ -2,9 +2,9 @@ language: ruby
2
2
 
3
3
  rvm:
4
4
  - ruby-head
5
- - 2.4.2
6
- - 2.3.5
7
- - 2.2.8
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,15 @@
1
+ ## 5.2.0
2
+
3
+ Fixes newline injection issue
4
+
5
+ ## 5.1.0
6
+
7
+ Fixes semicolon injection issue reported by @mvgijssel see https://github.com/twitter/secure_headers/issues/418
8
+
9
+ ## 5.0.5
10
+
11
+ A release to deprecate `SecureHeaders::Configuration#get` in prep for 6.x
12
+
1
13
  ## 5.0.4
2
14
 
3
15
  - Adds support for `nonced_stylesheet_pack_tag` #373 (@paulfri)
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ group :test do
9
9
  gem "pry-nav"
10
10
  gem "rack"
11
11
  gem "rspec"
12
- gem "rubocop"
12
+ gem "rubocop", "< 0.68"
13
13
  gem "rubocop-github"
14
14
  gem "term-ansicolor"
15
15
  gem "tins"
@@ -19,4 +19,5 @@ 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
@@ -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
@@ -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
@@ -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 =~ /(\n|;)/
144
+ Kernel.warn("#{directive} contains a #{$1} in #{minified_source_list.inspect} 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(/[\n;]/, " ")
148
+ [symbol_to_hyphen_case(directive), escaped_source_list].join(" ").strip
143
149
  end
144
150
  end
145
151
 
@@ -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.4"
5
+ gem.version = "5.2.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-5-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,16 @@ 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
+ it "deprecates and escapes semicolons in directive source lists" do
37
+ expect(Kernel).to receive(:warn).with(%(frame_ancestors contains a \n in "\\nfoo.com\\nhacked" which will raise an error in future versions. It has been replaced with a blank space.))
38
+ expect(ContentSecurityPolicy.new(frame_ancestors: ["\nfoo.com\nhacked"]).value).to eq("frame-ancestors foo.com hacked")
39
+ end
40
+
31
41
  it "discards 'none' values if any other source expressions are present" do
32
42
  csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
33
43
  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
@@ -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
@@ -67,7 +67,7 @@ TEMPLATE
67
67
  end
68
68
 
69
69
  if options.is_a?(Hash)
70
- options = options.map {|k, v| " #{k}=#{v}"}
70
+ options = options.map { |k, v| " #{k}=#{v}" }
71
71
  end
72
72
  "<#{type}#{options}>#{content}</#{type}>"
73
73
  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.4
4
+ version: 5.2.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-12-05 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
@@ -114,12 +114,7 @@ homepage: https://github.com/twitter/secureheaders
114
114
  licenses:
115
115
  - Apache Public License 2.0
116
116
  metadata: {}
117
- post_install_message: |2+
118
-
119
- **********
120
- :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-5-0.md
121
- **********
122
-
117
+ post_install_message:
123
118
  rdoc_options: []
124
119
  require_paths:
125
120
  - lib