secure_headers 2.2.0 → 2.3.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: 8e454769ab715d2e94e5451190e7ef8679810ec0
4
- data.tar.gz: 9f0fd48b03bd3e8f5a60b85894492a4a9aee08a0
3
+ metadata.gz: 2ea3335e72b8eaea53c9fc03a77c008b73fc674c
4
+ data.tar.gz: 4097b301e8e7f18174abda1c6412f9652cd4c39a
5
5
  SHA512:
6
- metadata.gz: 9ace533baba91512c2d8b15f12ed188d93feda9b20f7e682caaa740d413b1428e9c39deb2c2fbba4aec6462093f54126de9ca8cc5268d5b8d98851c82876fa6d
7
- data.tar.gz: 9b5e12226b456e4983eb31cd75f48ccadf34d80d125ff0fb67ee84afe56f3ca3a682890213e20e2c24e2c715073c0284b4ab8fc3fc86ad722c7ae6d08c0bb207
6
+ metadata.gz: 53db0ad5f6d9e7a85bde9aa167a927e3bda91351ce2f57af0f505d1b6c0856ca50b295ad87e26c1b25fa0142a95d8ef8b11f12f562377e60a0b398454abdd5b9
7
+ data.tar.gz: a6c3c2366fcf08ed3749f6c6e9146e2fbc33fb167a504404e195bd9813c709b0d497c2c2fc64f138a482a5fcc68eb4d212895761722ae0beed4b49ccea481e81
data/.travis.yml CHANGED
@@ -4,7 +4,6 @@ rvm:
4
4
  - "2.1"
5
5
  - "2.0.0"
6
6
  - "1.9.3"
7
- - "1.8.7"
8
7
  - "jruby-19mode"
9
8
 
10
9
  sudo: false
data/Gemfile CHANGED
@@ -10,6 +10,6 @@ group :test do
10
10
  gem 'rspec', '>= 3.1'
11
11
  gem 'growl'
12
12
  gem 'rb-fsevent'
13
- gem 'coveralls', :platforms => [:ruby_19]
13
+ gem 'coveralls', :platforms => [:ruby_19, :ruby_20, :ruby_21]
14
14
  gem 'i18n', '< 0.7.0', :platforms => [:ruby_18]
15
15
  end
data/README.md CHANGED
@@ -49,7 +49,7 @@ This gem makes a few assumptions about how you will use some features. For exam
49
49
  config.x_permitted_cross_domain_policies = 'none'
50
50
  config.csp = {
51
51
  :default_src => "https: self",
52
- :enforce => proc {|controller| contoller.current_user.enforce_csp? }
52
+ :enforce => proc {|controller| controller.current_user.enforce_csp? },
53
53
  :frame_src => "https: http:.twimg.com http://itunes.apple.com",
54
54
  :img_src => "https:",
55
55
  :report_uri => '//example.com/uri-directive'
@@ -415,12 +415,43 @@ def before_load
415
415
  end
416
416
  ```
417
417
 
418
+ ### Using in rack middleware
419
+
420
+ The `SecureHeaders::header_hash` generates a hash of all header values, which is useful for merging with rack middleware values.
421
+
422
+ ```ruby
423
+ class MySecureHeaders
424
+ include SecureHeaders
425
+ def initialize(app)
426
+ @app = app
427
+ end
428
+
429
+ def call(env)
430
+ status, headers, response = @app.call(env)
431
+ security_headers = if override?
432
+ SecureHeaders::header_hash(:csp => false) # uses global config, but overrides CSP config
433
+ else
434
+ SecureHeaders::header_hash # uses global config
435
+ end
436
+ [status, headers.merge(security_headers), [response.body]]
437
+ end
438
+ end
439
+
440
+ module Testapp
441
+ class Application < Rails::Application
442
+ config.middleware.use MySecureHeaders
443
+ end
444
+ end
445
+ ```
446
+
418
447
  ## Similar libraries
419
448
 
449
+ * Rack [rack-secure_headers](https://github.com/harmoni/rack-secure_headers)
420
450
  * Node.js (express) [helmet](https://github.com/evilpacket/helmet) and [hood](https://github.com/seanmonstar/hood)
421
- * J2EE Servlet >= 3.0 [highlines](https://github.com/sourceclear/headlines)
451
+ * Node.js (hapi) [blankie](https://github.com/nlf/blankie)
452
+ * J2EE Servlet >= 3.0 [headlines](https://github.com/sourceclear/headlines)
422
453
  * ASP.NET - [NWebsec](https://github.com/NWebsec/NWebsec/wiki)
423
- * Python - [django-csp](https://github.com/mozilla/django-csp/) + [commonware](https://github.com/jsocol/commonware/)
454
+ * Python - [django-csp](https://github.com/mozilla/django-csp) + [commonware](https://github.com/jsocol/commonware/); [django-security](https://github.com/sdelements/django-security)
424
455
  * Go - [secureheader](https://github.com/kr/secureheader)
425
456
 
426
457
  ## Authors
@@ -13,7 +13,7 @@ describe OtherThingsController, :type => :controller do
13
13
  options = opts.merge(
14
14
  {
15
15
  'HTTPS' => 'on',
16
- 'HTTP_USER_AGENT' => "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
16
+ 'HTTP_USER_AGENT' => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
17
17
  }
18
18
  )
19
19
 
@@ -3,4 +3,18 @@ class OtherThingsController < ApplicationController
3
3
  def index
4
4
 
5
5
  end
6
+
7
+ def other_action
8
+ render :text => 'yooooo'
9
+ end
10
+
11
+ def secure_header_options_for(header, options)
12
+ if params[:action] == "other_action"
13
+ if header == :csp
14
+ options.merge(:style_src => 'self')
15
+ end
16
+ else
17
+ options
18
+ end
19
+ end
6
20
  end
@@ -12,11 +12,17 @@ describe OtherThingsController, :type => :controller do
12
12
  expect(response.headers['X-Frame-Options']).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
13
13
  end
14
14
 
15
- it "sets the X-WebKit-CSP header" do
15
+ it "sets the CSP header" do
16
16
  get :index
17
17
  expect(response.headers['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; img-src 'self' data:;")
18
18
  end
19
19
 
20
+ it "sets per-action values based on secure_header_options_for" do
21
+ # munges :style_src => self into policy
22
+ get :other_action
23
+ expect(response.headers['Content-Security-Policy-Report-Only']).to eq("default-src 'self'; img-src 'self' data:; style-src 'self';")
24
+ end
25
+
20
26
  #mock ssl
21
27
  it "sets the Strict-Transport-Security header" do
22
28
  request.env['HTTPS'] = 'on'
@@ -2,4 +2,4 @@ class OtherThingsController < ApplicationController
2
2
  def index
3
3
 
4
4
  end
5
- end
5
+ end
@@ -13,7 +13,7 @@ describe OtherThingsController, :type => :controller do
13
13
  options = opts.merge(
14
14
  {
15
15
  'HTTPS' => 'on',
16
- 'HTTP_USER_AGENT' => "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
16
+ 'HTTP_USER_AGENT' => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
17
17
  }
18
18
  )
19
19
 
@@ -1,6 +1,8 @@
1
1
  require 'uri'
2
2
  require 'base64'
3
3
  require 'securerandom'
4
+ require 'user_agent_parser'
5
+ require 'json'
4
6
 
5
7
  module SecureHeaders
6
8
  class ContentSecurityPolicyBuildError < StandardError; end
@@ -38,7 +40,9 @@ module SecureHeaders
38
40
  SOURCE_DIRECTIVES = DIRECTIVES + NON_DEFAULT_SOURCES
39
41
 
40
42
  ALL_DIRECTIVES = DIRECTIVES + NON_DEFAULT_SOURCES + OTHER
43
+ CONFIG_KEY = :csp
41
44
  end
45
+
42
46
  include Constants
43
47
 
44
48
  attr_reader :disable_fill_missing, :ssl_request
@@ -127,6 +131,7 @@ module SecureHeaders
127
131
 
128
132
  @disable_fill_missing = !!@config.delete(:disable_fill_missing)
129
133
  @enforce = !!@config.delete(:enforce)
134
+ @disable_img_src_data_uri = !!@config.delete(:disable_img_src_data_uri)
130
135
  @tag_report_uri = !!@config.delete(:tag_report_uri)
131
136
  @script_hashes = @config.delete(:script_hashes) || []
132
137
 
@@ -164,6 +169,21 @@ module SecureHeaders
164
169
  end
165
170
  end
166
171
 
172
+ def to_json
173
+ build_value
174
+ @config.to_json.gsub(/(\w+)_src/, "\\1-src")
175
+ end
176
+
177
+ def self.from_json(*json_configs)
178
+ json_configs.inject({}) do |combined_config, one_config|
179
+ one_config = one_config.gsub(/(\w+)-src/, "\\1_src")
180
+ config = JSON.parse(one_config, :symbolize_names => true)
181
+ combined_config.merge(config) do |_, lhs, rhs|
182
+ lhs | rhs
183
+ end
184
+ end
185
+ end
186
+
167
187
  private
168
188
 
169
189
  def add_script_hashes
@@ -205,8 +225,12 @@ module SecureHeaders
205
225
  elsif %{self none}.include?(val)
206
226
  "'#{val}'"
207
227
  elsif val == 'nonce'
208
- self.class.set_nonce(@controller, nonce)
209
- ["'nonce-#{nonce}'", "'unsafe-inline'"]
228
+ if supports_nonces?(@ua)
229
+ self.class.set_nonce(@controller, nonce)
230
+ ["'nonce-#{nonce}'", "'unsafe-inline'"]
231
+ else
232
+ "'unsafe-inline'"
233
+ end
210
234
  else
211
235
  val
212
236
  end
@@ -233,10 +257,11 @@ module SecureHeaders
233
257
 
234
258
  def generic_directives
235
259
  header_value = ''
260
+ data_uri = @disable_img_src_data_uri ? [] : ["data:"]
236
261
  if @config[:img_src]
237
- @config[:img_src] = @config[:img_src] + ['data:'] unless @config[:img_src].include?('data:')
262
+ @config[:img_src] = @config[:img_src] + data_uri unless @config[:img_src].include?('data:')
238
263
  else
239
- @config[:img_src] = @config[:default_src] + ['data:']
264
+ @config[:img_src] = @config[:default_src] + data_uri
240
265
  end
241
266
 
242
267
  DIRECTIVES.each do |directive_name|
@@ -258,5 +283,10 @@ module SecureHeaders
258
283
  def build_directive(key)
259
284
  "#{self.class.symbol_to_hyphen_case(key)} #{@config[key].join(" ")}; "
260
285
  end
286
+
287
+ def supports_nonces?(user_agent)
288
+ parsed_ua = UserAgentParser.parse(user_agent)
289
+ ["Chrome", "Opera", "Firefox"].include?(parsed_ua.family)
290
+ end
261
291
  end
262
292
  end
@@ -6,6 +6,7 @@ module SecureHeaders
6
6
  ENV_KEY = 'secure_headers.public_key_pins'
7
7
  HASH_ALGORITHMS = [:sha256]
8
8
  DIRECTIVES = [:max_age]
9
+ CONFIG_KEY = :hpkp
9
10
  end
10
11
  class << self
11
12
  def symbol_to_hyphen_case sym
@@ -8,6 +8,7 @@ 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."
11
+ CONFIG_KEY = :hsts
11
12
  end
12
13
  include Constants
13
14
 
@@ -5,6 +5,7 @@ module SecureHeaders
5
5
  module Constants
6
6
  X_CONTENT_TYPE_OPTIONS_HEADER_NAME = "X-Content-Type-Options"
7
7
  DEFAULT_VALUE = "nosniff"
8
+ CONFIG_KEY = :x_content_type_options
8
9
  end
9
10
  include Constants
10
11
 
@@ -37,4 +38,4 @@ module SecureHeaders
37
38
  end
38
39
  end
39
40
  end
40
- end
41
+ end
@@ -4,6 +4,7 @@ module SecureHeaders
4
4
  module Constants
5
5
  XDO_HEADER_NAME = "X-Download-Options"
6
6
  DEFAULT_VALUE = 'noopen'
7
+ CONFIG_KEY = :x_download_options
7
8
  end
8
9
  include Constants
9
10
 
@@ -5,6 +5,7 @@ module SecureHeaders
5
5
  XFO_HEADER_NAME = "X-Frame-Options"
6
6
  DEFAULT_VALUE = 'SAMEORIGIN'
7
7
  VALID_XFO_HEADER = /\A(SAMEORIGIN\z|DENY\z|ALLOW-FROM[:\s])/i
8
+ CONFIG_KEY = :x_frame_options
8
9
  end
9
10
  include Constants
10
11
 
@@ -5,6 +5,7 @@ module SecureHeaders
5
5
  XPCDP_HEADER_NAME = "X-Permitted-Cross-Domain-Policies"
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
8
9
  end
9
10
  include Constants
10
11
 
@@ -5,6 +5,7 @@ module SecureHeaders
5
5
  X_XSS_PROTECTION_HEADER_NAME = 'X-XSS-Protection'
6
6
  DEFAULT_VALUE = "1"
7
7
  VALID_X_XSS_HEADER = /\A[01](; mode=block)?(; report=.*)?\z/i
8
+ CONFIG_KEY = :x_xss_protection
8
9
  end
9
10
  include Constants
10
11
 
@@ -1,3 +1,3 @@
1
1
  module SecureHeaders
2
- VERSION = "2.2.0"
2
+ VERSION = "2.3.0"
3
3
  end
@@ -1,7 +1,32 @@
1
+ require "secure_headers/version"
2
+ require "secure_headers/header"
3
+ require "secure_headers/headers/public_key_pins"
4
+ require "secure_headers/headers/content_security_policy"
5
+ require "secure_headers/headers/x_frame_options"
6
+ require "secure_headers/headers/strict_transport_security"
7
+ require "secure_headers/headers/x_xss_protection"
8
+ require "secure_headers/headers/x_content_type_options"
9
+ require "secure_headers/headers/x_download_options"
10
+ require "secure_headers/headers/x_permitted_cross_domain_policies"
11
+ require "secure_headers/railtie"
12
+ require "secure_headers/hash_helper"
13
+ require "secure_headers/view_helper"
14
+
1
15
  module SecureHeaders
2
16
  SCRIPT_HASH_CONFIG_FILE = 'config/script_hashes.yml'
3
17
  HASHES_ENV_KEY = 'secure_headers.script_hashes'
4
18
 
19
+ ALL_HEADER_CLASSES = [
20
+ SecureHeaders::ContentSecurityPolicy,
21
+ SecureHeaders::StrictTransportSecurity,
22
+ SecureHeaders::PublicKeyPins,
23
+ SecureHeaders::XContentTypeOptions,
24
+ SecureHeaders::XDownloadOptions,
25
+ SecureHeaders::XFrameOptions,
26
+ SecureHeaders::XPermittedCrossDomainPolicies,
27
+ SecureHeaders::XXssProtection
28
+ ]
29
+
5
30
  module Configuration
6
31
  class << self
7
32
  attr_accessor :hsts, :x_frame_options, :x_content_type_options,
@@ -24,6 +49,27 @@ module SecureHeaders
24
49
  include InstanceMethods
25
50
  end
26
51
  end
52
+
53
+ def header_hash(options = nil)
54
+ ALL_HEADER_CLASSES.inject({}) do |memo, klass|
55
+ config = if options.is_a?(Hash) && options[klass::Constants::CONFIG_KEY]
56
+ options[klass::Constants::CONFIG_KEY]
57
+ else
58
+ ::SecureHeaders::Configuration.send(klass::Constants::CONFIG_KEY)
59
+ end
60
+
61
+ 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
64
+ end
65
+ memo
66
+ end
67
+ end
68
+
69
+ def get_a_header(name, klass, options)
70
+ return if options == false
71
+ klass.new(options)
72
+ end
27
73
  end
28
74
 
29
75
  module ClassMethods
@@ -39,6 +85,9 @@ module SecureHeaders
39
85
  end
40
86
 
41
87
  def ensure_security_headers options = {}
88
+ if RUBY_VERSION == "1.8.7"
89
+ warn "[DEPRECATION] secure_headers ruby 1.8.7 support will dropped in the next release"
90
+ end
42
91
  self.secure_headers_options = options
43
92
  before_filter :prep_script_hash
44
93
  before_filter :set_hsts_header
@@ -50,12 +99,6 @@ module SecureHeaders
50
99
  before_filter :set_x_download_options_header
51
100
  before_filter :set_x_permitted_cross_domain_policies_header
52
101
  end
53
-
54
- # we can't use ||= because I'm overloading false => disable, nil => default
55
- # both of which trigger the conditional assignment
56
- def options_for(type, options)
57
- options.nil? ? ::SecureHeaders::Configuration.send(type) : options
58
- end
59
102
  end
60
103
 
61
104
  module InstanceMethods
@@ -80,7 +123,7 @@ module SecureHeaders
80
123
  end
81
124
 
82
125
  config = self.class.secure_headers_options[:csp] if config.nil?
83
- config = self.class.options_for :csp, config
126
+ config = secure_header_options_for :csp, config
84
127
 
85
128
  return if config == false
86
129
 
@@ -140,7 +183,7 @@ module SecureHeaders
140
183
 
141
184
  def set_hpkp_header(options=self.class.secure_headers_options[:hpkp])
142
185
  return unless request.ssl?
143
- config = self.class.options_for :hpkp, options
186
+ config = secure_header_options_for :hpkp, options
144
187
 
145
188
  return if config == false || config.nil?
146
189
 
@@ -158,12 +201,16 @@ module SecureHeaders
158
201
 
159
202
  private
160
203
 
204
+ # we can't use ||= because I'm overloading false => disable, nil => default
205
+ # both of which trigger the conditional assignment
206
+ def secure_header_options_for(type, options)
207
+ options.nil? ? ::SecureHeaders::Configuration.send(type) : options
208
+ end
209
+
161
210
  def set_a_header(name, klass, options=nil)
162
- options = self.class.options_for name, options
211
+ options = secure_header_options_for(name, options)
163
212
  return if options == false
164
-
165
- header = klass.new(options)
166
- set_header(header)
213
+ set_header(SecureHeaders::get_a_header(name, klass, options))
167
214
  end
168
215
 
169
216
  def set_header(name_or_header, value=nil)
@@ -176,18 +223,3 @@ module SecureHeaders
176
223
  end
177
224
  end
178
225
  end
179
-
180
-
181
- require "secure_headers/version"
182
- require "secure_headers/header"
183
- require "secure_headers/headers/public_key_pins"
184
- require "secure_headers/headers/content_security_policy"
185
- require "secure_headers/headers/x_frame_options"
186
- require "secure_headers/headers/strict_transport_security"
187
- require "secure_headers/headers/x_xss_protection"
188
- require "secure_headers/headers/x_content_type_options"
189
- require "secure_headers/headers/x_download_options"
190
- require "secure_headers/headers/x_permitted_cross_domain_policies"
191
- require "secure_headers/railtie"
192
- require "secure_headers/hash_helper"
193
- require "secure_headers/view_helper"
@@ -19,5 +19,5 @@ Gem::Specification.new do |gem|
19
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
20
  gem.require_paths = ["lib"]
21
21
  gem.add_development_dependency "rake"
22
- gem.post_install_message = "Warning: lambda config values will be broken until you add |controller|. e.g. :enforce => lambda { |controller| some_expression }"
22
+ gem.add_dependency "user_agent_parser"
23
23
  end
@@ -17,7 +17,8 @@ module SecureHeaders
17
17
  FIREFOX_23 = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0"
18
18
  CHROME = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4"
19
19
  CHROME_25 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
20
-
20
+ SAFARI = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"
21
+ OPERA = "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16"
21
22
 
22
23
  def request_for user_agent, request_uri=nil, options={:ssl => false}
23
24
  double(:ssl? => options[:ssl], :env => {'HTTP_USER_AGENT' => user_agent}, :url => (request_uri || 'http://areallylongdomainexample.com') )
@@ -55,6 +56,23 @@ module SecureHeaders
55
56
  end
56
57
  end
57
58
 
59
+ it "exports a policy to JSON" do
60
+ policy = ContentSecurityPolicy.new(default_opts)
61
+ expected = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"],"style-src":["'unsafe-inline'","https:","about:"],"img-src":["https:","data:"]})
62
+ expect(policy.to_json).to eq(expected)
63
+ end
64
+
65
+ it "imports JSON to build a policy" do
66
+ json1 = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"]})
67
+ json2 = %({"style-src":["'unsafe-inline'"],"img-src":["https:","data:"]})
68
+ json3 = %({"style-src":["https:","about:"]})
69
+ config = ContentSecurityPolicy.from_json(json1, json2, json3)
70
+ policy = ContentSecurityPolicy.new(config.merge(:disable_fill_missing => true))
71
+
72
+ expected = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"],"style-src":["'unsafe-inline'","https:","about:"],"img-src":["https:","data:"]})
73
+ expect(policy.to_json).to eq(expected)
74
+ end
75
+
58
76
  context "when using hash sources" do
59
77
  it "adds hashes and unsafe-inline to the script-src" do
60
78
  policy = ContentSecurityPolicy.new(default_opts.merge(:script_hashes => ['sha256-abc123']))
@@ -155,6 +173,16 @@ module SecureHeaders
155
173
  csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_fill_missing => true}, :request => request_for(CHROME))
156
174
  expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
157
175
  end
176
+
177
+ it "doesn't add a duplicate data uri if img-src specifies it already" do
178
+ csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self data:', :disable_fill_missing => true}, :request => request_for(CHROME))
179
+ expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
180
+ end
181
+
182
+ it "allows the user to disable img-src data: uris auto-whitelisting" do
183
+ csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self', :disable_img_src_data_uri => true, :disable_fill_missing => true}, :request => request_for(CHROME))
184
+ expect(csp.value).to eq("default-src 'self'; img-src 'self';")
185
+ end
158
186
  end
159
187
 
160
188
  it "fills in directives without values with default-src value" do
@@ -184,11 +212,33 @@ module SecureHeaders
184
212
  end
185
213
 
186
214
  context "when using a nonce" do
187
- it "adds a nonce and unsafe-inline to the script-src value" do
215
+ it "adds a nonce and unsafe-inline to the script-src value when using chrome" do
188
216
  header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(CHROME))
189
217
  expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
190
218
  end
191
219
 
220
+ it "adds a nonce and unsafe-inline to the script-src value when using firefox" do
221
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(FIREFOX))
222
+ expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
223
+ end
224
+
225
+ it "adds a nonce and unsafe-inline to the script-src value when using opera" do
226
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(OPERA))
227
+ expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
228
+ end
229
+
230
+ it "does not add a nonce and unsafe-inline to the script-src value when using Safari" do
231
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(SAFARI))
232
+ expect(header.value).to include("script-src 'self' 'unsafe-inline'")
233
+ expect(header.value).not_to include("nonce")
234
+ end
235
+
236
+ it "does not add a nonce and unsafe-inline to the script-src value when using IE" do
237
+ header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(IE))
238
+ expect(header.value).to include("script-src 'self' 'unsafe-inline'")
239
+ expect(header.value).not_to include("nonce")
240
+ end
241
+
192
242
  it "adds a nonce and unsafe-inline to the style-src value" do
193
243
  header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce"), :request => request_for(CHROME))
194
244
  expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
@@ -159,6 +159,56 @@ describe SecureHeaders do
159
159
  end
160
160
  end
161
161
 
162
+ describe "SecureHeaders#header_hash" do
163
+ def expect_default_values(hash)
164
+ expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
165
+ expect(hash[XDO_HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
166
+ expect(hash[HSTS_HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
167
+ expect(hash[X_XSS_PROTECTION_HEADER_NAME]).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
168
+ expect(hash[X_CONTENT_TYPE_OPTIONS_HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
169
+ expect(hash[XPCDP_HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
170
+ end
171
+
172
+ it "produces a hash of headers given a hash as config" do
173
+ hash = SecureHeaders::header_hash(:csp => {:default_src => 'none', :img_src => "data:", :disable_fill_missing => true})
174
+ expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
175
+ expect_default_values(hash)
176
+ end
177
+
178
+ it "produces a hash with a mix of config values, override values, and default values" do
179
+ ::SecureHeaders::Configuration.configure do |config|
180
+ config.hsts = { :max_age => '123456'}
181
+ config.hpkp = {
182
+ :enforce => true,
183
+ :max_age => 1000000,
184
+ :include_subdomains => true,
185
+ :report_uri => '//example.com/uri-directive',
186
+ :pins => [
187
+ {:sha256 => 'abc'},
188
+ {:sha256 => '123'}
189
+ ]
190
+ }
191
+ end
192
+
193
+ hash = SecureHeaders::header_hash(:csp => {:default_src => 'none', :img_src => "data:", :disable_fill_missing => true})
194
+ ::SecureHeaders::Configuration.configure do |config|
195
+ config.hsts = nil
196
+ config.hpkp = nil
197
+ end
198
+
199
+ expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
200
+ expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
201
+ expect(hash[HSTS_HEADER_NAME]).to eq("max-age=123456")
202
+ expect(hash[HPKP_HEADER_NAME]).to eq(%{max-age=1000000; pin-sha256="abc"; pin-sha256="123"; report-uri="//example.com/uri-directive"; includeSubDomains})
203
+ end
204
+
205
+ it "produces a hash of headers with default config" do
206
+ hash = SecureHeaders::header_hash
207
+ expect(hash['Content-Security-Policy-Report-Only']).to eq(SecureHeaders::ContentSecurityPolicy::Constants::DEFAULT_CSP_HEADER)
208
+ expect_default_values(hash)
209
+ end
210
+ end
211
+
162
212
  describe "#set_x_frame_options_header" do
163
213
  it "sets the X-Frame-Options header" do
164
214
  should_assign_header(XFO_HEADER_NAME, SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
data/spec/spec_helper.rb CHANGED
@@ -3,8 +3,11 @@ require 'rspec'
3
3
 
4
4
  require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
5
5
 
6
- if defined?(Coveralls)
6
+ begin
7
+ require 'coveralls'
7
8
  Coveralls.wear!
9
+ rescue LoadError
10
+ # damn you 1.8.7
8
11
  end
9
12
 
10
13
  include ::SecureHeaders::PublicKeyPins::Constants
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.2.0
4
+ version: 2.3.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-06-18 00:00:00.000000000 Z
11
+ date: 2015-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: user_agent_parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description: Security related headers all in one gem.
28
42
  email:
29
43
  - neil.matatall@gmail.com
@@ -156,8 +170,7 @@ homepage: https://github.com/twitter/secureheaders
156
170
  licenses:
157
171
  - Apache Public License 2.0
158
172
  metadata: {}
159
- post_install_message: 'Warning: lambda config values will be broken until you add
160
- |controller|. e.g. :enforce => lambda { |controller| some_expression }'
173
+ post_install_message:
161
174
  rdoc_options: []
162
175
  require_paths:
163
176
  - lib
@@ -173,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
186
  version: '0'
174
187
  requirements: []
175
188
  rubyforge_project:
176
- rubygems_version: 2.2.3
189
+ rubygems_version: 2.4.8
177
190
  signing_key:
178
191
  specification_version: 4
179
192
  summary: Add easily configured security headers to responses including content-security-policy,