secure_headers 2.4.1 → 2.5.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: 333c51c1bbbed7415696fa0e8fd7e3147ddcf542
4
- data.tar.gz: 798eae0a9dc303a0c72c946e74aa76a2f36e229c
3
+ metadata.gz: 36b98616c83ec1049b8f1bcc25671738da8d8bed
4
+ data.tar.gz: 730108ec10da36aeeb59049e88122a99987d1dcc
5
5
  SHA512:
6
- metadata.gz: 80224aff2b4a8230de68b0338dd5ca4ef19c56b929fe59693d3f01395f7b4c3cab57170b389044dacd0745e43d07cf98951f13de48d646cc17cf8a40befe4d90
7
- data.tar.gz: 7f663e76802e8c2282908eb2eefce7a040cf9f5c04da49b825bd147854879a900577f22f9a8e918a973d3019e840e95d08aec6eaeeb3836c6233fb0db0dd8982
6
+ metadata.gz: 854d9e8c9de09d5515dad3449726b4b2308b8a25b5dc702ed80da47fd3f94f983119531a4089877b215ee07dccfa8fec2766107891b018bcca376ec16ed95e8f
7
+ data.tar.gz: f0ec419ac593cdeb556dc852d6e2a3b8f5fcfe83c07b34ac8392fcb912004bbe1117ea9e473c068230a38c44c96a5e036dfde630ae5e7397203c9aed8babd741
data/.travis.yml CHANGED
@@ -9,3 +9,4 @@ rvm:
9
9
 
10
10
  sudo: false
11
11
  cache: bundler
12
+ before_install: gem update bundler
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :test do
6
+ gem "guard-rspec", platforms: [:ruby_19, :ruby_20, :ruby_21, :ruby_22]
6
7
  gem 'test-unit', '~> 3.0'
7
8
  gem 'rails', '3.2.22'
8
9
  gem 'sqlite3', :platforms => [:ruby, :mswin, :mingw]
data/Guardfile ADDED
@@ -0,0 +1,12 @@
1
+ guard :rspec, cmd: "bundle exec rspec", all_on_start: true, all_after_pass: true do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ # RSpec files
6
+ rspec = dsl.rspec
7
+ watch(rspec.spec_helper) { rspec.spec_dir }
8
+ watch(rspec.spec_support) { rspec.spec_dir }
9
+ watch(rspec.spec_files)
10
+
11
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
12
+ end
data/README.md CHANGED
@@ -8,7 +8,7 @@ The gem will automatically apply several headers that are related to security.
8
8
  - X-Content-Type-Options - [Prevent content type sniffing](http://msdn.microsoft.com/en-us/library/ie/gg622941\(v=vs.85\).aspx)
9
9
  - X-Download-Options - [Prevent file downloads opening](http://msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx)
10
10
  - 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)
11
- - Public Key Pinning - Pin certificate fingerprints in the browser to prevent man-in-the-middle attacks due to compromised Certificate Authorities. [Public Key Pinnning Specification](https://tools.ietf.org/html/rfc7469)
11
+ - 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)
12
12
 
13
13
  ## Usage
14
14
 
@@ -11,6 +11,7 @@ module SecureHeaders
11
11
  DEFAULT_CSP_HEADER = "default-src https: data: 'unsafe-inline' 'unsafe-eval'; frame-src https: about: javascript:; img-src data:"
12
12
  HEADER_NAME = "Content-Security-Policy"
13
13
  ENV_KEY = 'secure_headers.content_security_policy'
14
+ USER_AGENT_PARSER = UserAgentParser::Parser.new
14
15
 
15
16
  DIRECTIVES_1_0 = [
16
17
  :default_src,
@@ -133,34 +134,42 @@ module SecureHeaders
133
134
  @ua = options[:ua]
134
135
  @ssl_request = !!options.delete(:ssl)
135
136
  @request_uri = options.delete(:request_uri)
136
- @http_additions = config.delete(:http_additions)
137
- @disable_img_src_data_uri = !!config.delete(:disable_img_src_data_uri)
138
- @tag_report_uri = !!config.delete(:tag_report_uri)
139
- @script_hashes = config.delete(:script_hashes) || []
140
- @app_name = config.delete(:app_name)
141
- @app_name = @app_name.call(@controller) if @app_name.respond_to?(:call)
142
- @enforce = config.delete(:enforce)
143
- @enforce = @enforce.call(@controller) if @enforce.respond_to?(:call)
144
- @enforce = !!@enforce
145
137
 
146
138
  # Config values can be string, array, or lamdba values
147
139
  @config = config.inject({}) do |hash, (key, value)|
148
- config_val = value.respond_to?(:call) ? value.call(@controller) : value
140
+ config_val = if value.respond_to?(:call)
141
+ warn "[DEPRECATION] secure_headers 3.x will not support procs as config values."
142
+ value.call(@controller)
143
+ else
144
+ value
145
+ end
146
+
149
147
  if ALL_DIRECTIVES.include?(key.to_sym) # directives need to be normalized to arrays of strings
150
- config_val = config_val.split if config_val.is_a? String
148
+ if config_val.is_a? String
149
+ warn "[DEPRECATION] A String was supplied for directive #{key}. secure_headers 3.x will require all directives to be arrays of strings."
150
+ config_val = config_val.split
151
+ end
151
152
  if config_val.is_a?(Array)
152
153
  config_val = config_val.map do |val|
153
154
  translate_dir_value(val)
154
155
  end.flatten.uniq
155
156
  end
156
- elsif key != :script_hash_middleware
157
- raise ArgumentError.new("Unknown directive supplied: #{key}")
158
157
  end
159
158
 
160
159
  hash[key] = config_val
161
160
  hash
162
161
  end
163
162
 
163
+ @http_additions = @config.delete(:http_additions)
164
+ @disable_img_src_data_uri = !!@config.delete(:disable_img_src_data_uri)
165
+ @tag_report_uri = !!@config.delete(:tag_report_uri)
166
+ @script_hashes = @config.delete(:script_hashes) || []
167
+ @app_name = @config.delete(:app_name)
168
+ @app_name = @app_name.call(@controller) if @app_name.respond_to?(:call)
169
+ @enforce = @config.delete(:enforce)
170
+ @enforce = @enforce.call(@controller) if @enforce.respond_to?(:call)
171
+ @enforce = !!@enforce
172
+
164
173
  # normalize and tag the report-uri
165
174
  if @config[:report_uri]
166
175
  @config[:report_uri] = @config[:report_uri].map do |report_uri|
@@ -258,10 +267,10 @@ module SecureHeaders
258
267
 
259
268
  def translate_dir_value val
260
269
  if %w{inline eval}.include?(val)
261
- warn "[DEPRECATION] using inline/eval may not be supported in the future. Instead use 'unsafe-inline'/'unsafe-eval' instead."
270
+ warn "[DEPRECATION] using inline/eval is not suppored in secure_headers 3.x. Instead use 'unsafe-inline'/'unsafe-eval' instead."
262
271
  val == 'inline' ? "'unsafe-inline'" : "'unsafe-eval'"
263
272
  elsif %{self none}.include?(val)
264
- warn "[DEPRECATION] using self/none may not be supported in the future. Instead use 'self'/'none' instead."
273
+ warn "[DEPRECATION] using self/none is not suppored in secure_headers 3.x. Instead use 'self'/'none' instead."
265
274
  "'#{val}'"
266
275
  elsif val == 'nonce'
267
276
  if supports_nonces?
@@ -305,7 +314,7 @@ module SecureHeaders
305
314
  end
306
315
 
307
316
  def supported_directives
308
- @supported_directives ||= case UserAgentParser.parse(@ua).family
317
+ @supported_directives ||= case USER_AGENT_PARSER.parse(@ua).family
309
318
  when "Chrome"
310
319
  CHROME_DIRECTIVES
311
320
  when "Safari"
@@ -318,7 +327,7 @@ module SecureHeaders
318
327
  end
319
328
 
320
329
  def supports_nonces?
321
- parsed_ua = UserAgentParser.parse(@ua)
330
+ parsed_ua = USER_AGENT_PARSER.parse(@ua)
322
331
  ["Chrome", "Opera", "Firefox"].include?(parsed_ua.family)
323
332
  end
324
333
  end
@@ -41,6 +41,7 @@ module SecureHeaders
41
41
 
42
42
  def validate_config
43
43
  if @config.is_a? Hash
44
+ warn "[DEPRECATION] secure_headers 3.0 will only accept string values for StrictTransportSecurity config"
44
45
  if !@config[:max_age]
45
46
  raise STSBuildError.new("No max-age was supplied.")
46
47
  elsif @config[:max_age].to_s !~ /\A\d+\z/
@@ -25,6 +25,7 @@ module SecureHeaders
25
25
  when String
26
26
  @config
27
27
  else
28
+ warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XContentTypeOptions config"
28
29
  @config[:value]
29
30
  end
30
31
  end
@@ -24,6 +24,7 @@ module SecureHeaders
24
24
  when String
25
25
  @config
26
26
  else
27
+ warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XDownloadOptions config"
27
28
  @config[:value]
28
29
  end
29
30
  end
@@ -25,6 +25,7 @@ module SecureHeaders
25
25
  when String
26
26
  @config
27
27
  else
28
+ warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XFrameOptions config"
28
29
  @config[:value]
29
30
  end
30
31
  end
@@ -25,6 +25,7 @@ module SecureHeaders
25
25
  when String
26
26
  @config
27
27
  else
28
+ warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XPermittedCrossDomainPolicies config"
28
29
  @config[:value]
29
30
  end
30
31
  end
@@ -25,6 +25,7 @@ module SecureHeaders
25
25
  when String
26
26
  @config
27
27
  else
28
+ warn "[DEPRECATION] secure_headers 3.0 will only accept string values for XXssProtection config"
28
29
  value = @config[:value].to_s
29
30
  value += "; mode=#{@config[:mode]}" if @config[:mode]
30
31
  value += "; report=#{@config[:report_uri]}" if @config[:report_uri]
@@ -1,3 +1,3 @@
1
1
  module SecureHeaders
2
- VERSION = "2.4.1"
2
+ VERSION = "2.5.0"
3
3
  end
@@ -33,12 +33,18 @@ module SecureHeaders
33
33
  :x_xss_protection, :csp, :x_download_options, :script_hashes,
34
34
  :x_permitted_cross_domain_policies, :hpkp
35
35
 
36
- def configure &block
36
+ # For preparation for the secure_headers 3.x change.
37
+ def default &block
37
38
  instance_eval &block
38
39
  if File.exists?(SCRIPT_HASH_CONFIG_FILE)
39
40
  ::SecureHeaders::Configuration.script_hashes = YAML.load(File.open(SCRIPT_HASH_CONFIG_FILE))
40
41
  end
41
42
  end
43
+
44
+ def configure &block
45
+ warn "[DEPRECATION] `configure` is removed in secure_headers 3.x. Instead use `default`."
46
+ default &block
47
+ end
42
48
  end
43
49
  end
44
50
 
@@ -52,21 +58,23 @@ module SecureHeaders
52
58
 
53
59
  def header_hash(options = nil)
54
60
  ALL_HEADER_CLASSES.inject({}) do |memo, klass|
55
- config = if options.is_a?(Hash) && options[klass::Constants::CONFIG_KEY]
61
+ # must use !options[key].nil? because 'false' represents opting out, nil
62
+ # represents use global default.
63
+ config = if options.is_a?(Hash) && !options[klass::Constants::CONFIG_KEY].nil?
56
64
  options[klass::Constants::CONFIG_KEY]
57
65
  else
58
66
  ::SecureHeaders::Configuration.send(klass::Constants::CONFIG_KEY)
59
67
  end
60
68
 
61
69
  unless klass == SecureHeaders::PublicKeyPins && !config.is_a?(Hash)
62
- header = get_a_header(klass::Constants::CONFIG_KEY, klass, config)
63
- memo[header.name] = header.value
70
+ header = get_a_header(klass, config)
71
+ memo[header.name] = header.value if header
64
72
  end
65
73
  memo
66
74
  end
67
75
  end
68
76
 
69
- def get_a_header(name, klass, options)
77
+ def get_a_header(klass, options)
70
78
  return if options == false
71
79
  klass.new(options)
72
80
  end
@@ -204,13 +212,14 @@ module SecureHeaders
204
212
  # we can't use ||= because I'm overloading false => disable, nil => default
205
213
  # both of which trigger the conditional assignment
206
214
  def secure_header_options_for(type, options)
215
+ warn "[DEPRECATION] secure_header_options_for will not be supported in secure_headers 3.x."
207
216
  options.nil? ? ::SecureHeaders::Configuration.send(type) : options
208
217
  end
209
218
 
210
219
  def set_a_header(name, klass, options=nil)
211
220
  options = secure_header_options_for(name, options)
212
221
  return if options == false
213
- set_header(SecureHeaders::get_a_header(name, klass, options))
222
+ set_header(SecureHeaders::get_a_header(klass, options))
214
223
  end
215
224
 
216
225
  def set_header(name_or_header, value=nil)
@@ -20,4 +20,5 @@ Gem::Specification.new do |gem|
20
20
  gem.require_paths = ["lib"]
21
21
  gem.add_development_dependency "rake"
22
22
  gem.add_dependency "user_agent_parser"
23
+ gem.post_install_message = "[DEPRECATION] Development has stopped on the 2.x line of secure_headers. It will be maintained, but new features will be added to the 3.x branch. A lot has changed in secure_headers 3.x. A migration guide can be found in the documentation: https://github.com/twitter/secureheaders/blob/master/upgrading-to-3-0.md."
23
24
  end
@@ -142,6 +142,14 @@ module SecureHeaders
142
142
  end
143
143
 
144
144
  describe "#value" do
145
+ it "does not mutate shared state" do
146
+ opts = default_opts.merge(enforce: true)
147
+ policy = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
148
+ expect(policy.name).to eq("Content-Security-Policy")
149
+ policy = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
150
+ expect(policy.name).to eq("Content-Security-Policy")
151
+ end
152
+
145
153
  context "browser sniffing" do
146
154
  let(:complex_opts) do
147
155
  ALL_DIRECTIVES.inject({}) { |memo, directive| memo[directive] = "'self'"; memo }.merge(:block_all_mixed_content => '')
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  module SecureHeaders
2
4
  describe XContentTypeOptions do
3
5
  specify{ expect(XContentTypeOptions.new.name).to eq("X-Content-Type-Options") }
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  module SecureHeaders
2
4
  describe XXssProtection do
3
5
  specify { expect(XXssProtection.new.name).to eq(X_XSS_PROTECTION_HEADER_NAME)}
@@ -8,6 +8,7 @@ describe SecureHeaders do
8
8
  let(:request) {double(:ssl? => true, :url => 'https://example.com')}
9
9
 
10
10
  before(:each) do
11
+ reset_config
11
12
  stub_user_agent(nil)
12
13
  allow(headers).to receive(:[])
13
14
  allow(subject).to receive(:response).and_return(response)
@@ -19,7 +20,7 @@ describe SecureHeaders do
19
20
  end
20
21
 
21
22
  def reset_config
22
- ::SecureHeaders::Configuration.configure do |config|
23
+ ::SecureHeaders::Configuration.default do |config|
23
24
  config.hpkp = nil
24
25
  config.hsts = nil
25
26
  config.x_frame_options = nil
@@ -138,7 +139,7 @@ describe SecureHeaders do
138
139
 
139
140
  context "when disabled by configuration settings" do
140
141
  it "does not set any headers when disabled" do
141
- ::SecureHeaders::Configuration.configure do |config|
142
+ ::SecureHeaders::Configuration.default do |config|
142
143
  config.hsts = false
143
144
  config.hpkp = false
144
145
  config.x_frame_options = false
@@ -171,8 +172,25 @@ describe SecureHeaders do
171
172
  expect_default_values(hash)
172
173
  end
173
174
 
175
+ it "allows opting out" do
176
+ hash = SecureHeaders::header_hash(:csp => false, :hpkp => false)
177
+ expect(hash['Content-Security-Policy-Report-Only']).to be_nil
178
+ expect(hash['Content-Security-Policy']).to be_nil
179
+ end
180
+
181
+ it "allows opting out with config" do
182
+ ::SecureHeaders::Configuration.default do |config|
183
+ config.hsts = false
184
+ config.csp = false
185
+ end
186
+
187
+ hash = SecureHeaders::header_hash
188
+ expect(hash['Content-Security-Policy-Report-Only']).to be_nil
189
+ expect(hash['Content-Security-Policy']).to be_nil
190
+ end
191
+
174
192
  it "produces a hash with a mix of config values, override values, and default values" do
175
- ::SecureHeaders::Configuration.configure do |config|
193
+ ::SecureHeaders::Configuration.default do |config|
176
194
  config.hsts = { :max_age => '123456'}
177
195
  config.hpkp = {
178
196
  :enforce => true,
@@ -187,7 +205,7 @@ describe SecureHeaders do
187
205
  end
188
206
 
189
207
  hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:"})
190
- ::SecureHeaders::Configuration.configure do |config|
208
+ ::SecureHeaders::Configuration.default do |config|
191
209
  config.hsts = nil
192
210
  config.hpkp = nil
193
211
  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: 2.4.1
4
+ version: 2.5.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: 2015-10-14 00:00:00.000000000 Z
11
+ date: 2016-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -50,6 +50,7 @@ files:
50
50
  - ".ruby-version"
51
51
  - ".travis.yml"
52
52
  - Gemfile
53
+ - Guardfile
53
54
  - LICENSE
54
55
  - README.md
55
56
  - Rakefile
@@ -170,7 +171,10 @@ homepage: https://github.com/twitter/secureheaders
170
171
  licenses:
171
172
  - Apache Public License 2.0
172
173
  metadata: {}
173
- post_install_message:
174
+ post_install_message: "[DEPRECATION] Development has stopped on the 2.x line of secure_headers.
175
+ It will be maintained, but new features will be added to the 3.x branch. A lot has
176
+ changed in secure_headers 3.x. A migration guide can be found in the documentation:
177
+ https://github.com/twitter/secureheaders/blob/master/upgrading-to-3-0.md."
174
178
  rdoc_options: []
175
179
  require_paths:
176
180
  - lib