secure_headers 6.0.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of secure_headers might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6d7392744d486b32bda2b91432d39ddfeee87dfd
4
- data.tar.gz: 38328982bf71376b9412ed8c7d0652163741bf16
2
+ SHA256:
3
+ metadata.gz: 26a6b47b87ec772f94c264ef318bf4c20997b3a541898f80110a1bd26a471753
4
+ data.tar.gz: 73e40f805bafe030a82cbe37cdf499acea6c2613cce3c25964db89a9fde2f172
5
5
  SHA512:
6
- metadata.gz: e08d9df89db6908d0c8419d0086bf8fb6c7e374c53e0bf774bd74d534094c66ef3cdcbac23e97c03b05d4045ba6878c3f6d8a17877146bdb6f2f0c15b57d1784
7
- data.tar.gz: 304824374c236d11edc52049732a2ea133bbbdf611f35b2c5309a950125e879fb4016f0f15d7ad6c09310ab4b6080e966fac912147f43ed91989aa15898595a7
6
+ metadata.gz: ed3eb3ba011292b668fd786a22a2c9d93a20067e4391881eafc555f978a7ae3c5da33741f82c908b7cfe4c66cb837f9fc1b2f7360be788d320dab0694fdaeb66
7
+ data.tar.gz: df5800647d9dc5462d51529461d85374831bdc1fe0755949d4ec9764f69810c7e5456be1095f25d4b34bb2ec2c3c0773243d2ce4ebaf4c11bace6ea035201947
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.4.2
1
+ 2.6.1
data/.travis.yml CHANGED
@@ -2,10 +2,9 @@ language: ruby
2
2
 
3
3
  rvm:
4
4
  - ruby-head
5
+ - 2.6.1
5
6
  - 2.5.0
6
7
  - 2.4.3
7
- - 2.3.6
8
- - 2.2.9
9
8
  - jruby-head
10
9
 
11
10
  env:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 6.1
2
+
3
+ Adds support for navigate-to, prefetch-src, and require-sri-for #395
4
+
1
5
  ## 6.0
2
6
 
3
7
  - See the [upgrading to 6.0](docs/upgrading-to-6-0.md) guide for the breaking changes.
data/README.md CHANGED
@@ -2,10 +2,6 @@
2
2
 
3
3
  **master represents 6.x line**. See the [upgrading to 4.x doc](docs/upgrading-to-4-0.md), [upgrading to 5.x doc](docs/upgrading-to-5-0.md), or [upgrading to 6.x doc](docs/upgrading-to-6-0.md) for instructions on how to upgrade. Bug fixes should go in the 5.x branch for now.
4
4
 
5
- **The [3.x](https://github.com/twitter/secureheaders/tree/2.x) branch is moving into maintenance mode**. See the [upgrading to 3.x doc](docs/upgrading-to-3-0.md) for instructions on how to upgrade including the differences and benefits of using the 3.x branch.
6
-
7
- **The [2.x branch](https://github.com/twitter/secureheaders/tree/2.x) will be not be maintained once 4.x is released**. The documentation below only applies to the 3.x branch. See the 2.x [README](https://github.com/twitter/secureheaders/blob/2.x/README.md) for the old way of doing things.
8
-
9
5
  The gem will automatically apply several headers that are related to security. This includes:
10
6
  - Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](http://www.w3.org/TR/CSP2/)
11
7
  - https://csp.withgoogle.com
@@ -33,20 +29,6 @@ It can also mark all http cookies with the Secure, HttpOnly and SameSite attribu
33
29
  - [Hashes](docs/hashes.md)
34
30
  - [Sinatra Config](docs/sinatra.md)
35
31
 
36
- ## Getting Started
37
-
38
- ### Rails 3+
39
-
40
- For Rails 3+ applications, `secure_headers` has a `railtie` that should automatically include the middleware. If for some reason the middleware is not being included follow the instructions for Rails 2.
41
-
42
- ### Rails 2
43
-
44
- For Rails 2 or non-rails applications, an explicit statement is required to use the middleware component.
45
-
46
- ```ruby
47
- use SecureHeaders::Middleware
48
- ```
49
-
50
32
  ## Configuration
51
33
 
52
34
  If you do not supply a `default` configuration, exceptions will be raised. If you would like to use a default configuration (which is fairly locked down), just call `SecureHeaders::Configuration.default` without any arguments or block.
@@ -119,6 +101,23 @@ X-Permitted-Cross-Domain-Policies: none
119
101
  X-Xss-Protection: 1; mode=block
120
102
  ```
121
103
 
104
+ ## API configurations
105
+
106
+ Which headers you decide to use for API responses is entirely a personal choice. Things like X-Frame-Options seem to have no place in an API response and would be wasting bytes. While this is true, browsers can do funky things with non-html responses. At the minimum, we suggest CSP:
107
+
108
+ ```ruby
109
+ SecureHeaders::Configuration.override(:api) do |config|
110
+ config.csp = { default_src: 'none' }
111
+ config.hsts = SecureHeaders::OPT_OUT
112
+ config.x_frame_options = SecureHeaders::OPT_OUT
113
+ config.x_content_type_options = SecureHeaders::OPT_OUT
114
+ config.x_xss_protection = SecureHeaders::OPT_OUT
115
+ config.x_permitted_cross_domain_policies = SecureHeaders::OPT_OUT
116
+ end
117
+ ```
118
+
119
+ However, I would consider these headers anyways depending on your load and bandwidth requirements.
120
+
122
121
  ## Similar libraries
123
122
 
124
123
  * Rack [rack-secure_headers](https://github.com/frodsan/rack-secure_headers)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "secure_headers/hash_helper"
3
3
  require "secure_headers/headers/cookie"
4
- require "secure_headers/headers/public_key_pins"
5
4
  require "secure_headers/headers/content_security_policy"
6
5
  require "secure_headers/headers/x_frame_options"
7
6
  require "secure_headers/headers/strict_transport_security"
@@ -18,8 +17,7 @@ require "secure_headers/view_helper"
18
17
  require "singleton"
19
18
  require "secure_headers/configuration"
20
19
 
21
- # All headers (except for hpkp) have a default value. Provide SecureHeaders::OPT_OUT
22
- # or ":optout_of_protection" as a config value to disable a given header
20
+ # Provide SecureHeaders::OPT_OUT as a config value to disable a given header
23
21
  module SecureHeaders
24
22
  class NoOpHeaderConfig
25
23
  include Singleton
@@ -51,10 +49,6 @@ module SecureHeaders
51
49
  HTTPS = "https".freeze
52
50
  CSP = ContentSecurityPolicy
53
51
 
54
- # Headers set on http requests (excludes STS and HPKP)
55
- HTTPS_HEADER_CLASSES =
56
- [StrictTransportSecurity, PublicKeyPins].freeze
57
-
58
52
  class << self
59
53
  # Public: override a given set of directives for the current request. If a
60
54
  # value already exists for a given directive, it will be overridden.
@@ -138,7 +132,7 @@ module SecureHeaders
138
132
  # Public: Builds the hash of headers that should be applied base on the
139
133
  # request.
140
134
  #
141
- # StrictTransportSecurity and PublicKeyPins are not applied to http requests.
135
+ # StrictTransportSecurity is not applied to http requests.
142
136
  # See #config_for to determine which config is used for a given request.
143
137
  #
144
138
  # Returns a hash of header names => header values. The value
@@ -151,9 +145,7 @@ module SecureHeaders
151
145
  headers = config.generate_headers
152
146
 
153
147
  if request.scheme != HTTPS
154
- HTTPS_HEADER_CLASSES.each do |klass|
155
- headers.delete(klass::HEADER_NAME)
156
- end
148
+ headers.delete(StrictTransportSecurity::HEADER_NAME)
157
149
  end
158
150
  headers
159
151
  end
@@ -115,7 +115,6 @@ module SecureHeaders
115
115
  expect_certificate_transparency: ExpectCertificateTransparency,
116
116
  csp: ContentSecurityPolicy,
117
117
  csp_report_only: ContentSecurityPolicy,
118
- hpkp: PublicKeyPins,
119
118
  cookies: Cookie,
120
119
  }.freeze
121
120
 
@@ -144,7 +143,6 @@ module SecureHeaders
144
143
  @clear_site_data = nil
145
144
  @csp = nil
146
145
  @csp_report_only = nil
147
- @hpkp = nil
148
146
  @hsts = nil
149
147
  @x_content_type_options = nil
150
148
  @x_download_options = nil
@@ -153,7 +151,6 @@ module SecureHeaders
153
151
  @x_xss_protection = nil
154
152
  @expect_certificate_transparency = nil
155
153
 
156
- self.hpkp = OPT_OUT
157
154
  self.referrer_policy = OPT_OUT
158
155
  self.csp = ContentSecurityPolicyConfig.new(ContentSecurityPolicyConfig::DEFAULT)
159
156
  self.csp_report_only = OPT_OUT
@@ -178,7 +175,6 @@ module SecureHeaders
178
175
  copy.clear_site_data = @clear_site_data
179
176
  copy.expect_certificate_transparency = @expect_certificate_transparency
180
177
  copy.referrer_policy = @referrer_policy
181
- copy.hpkp = @hpkp
182
178
  copy
183
179
  end
184
180
 
@@ -263,10 +259,5 @@ module SecureHeaders
263
259
  raise ArgumentError, "Must provide either an existing CSP config or a CSP config hash"
264
260
  end
265
261
  end
266
-
267
- def hpkp_report_host
268
- return nil unless @hpkp && hpkp != OPT_OUT && @hpkp[:report_uri]
269
- URI.parse(@hpkp[:report_uri]).host
270
- end
271
262
  end
272
263
  end
@@ -51,14 +51,14 @@ module SecureHeaders
51
51
  def build_value
52
52
  directives.map do |directive_name|
53
53
  case DIRECTIVE_VALUE_TYPES[directive_name]
54
+ when :source_list, :require_sri_for_list # require_sri is a simple set of strings that don't need to deal with symbol casing
55
+ build_source_list_directive(directive_name)
54
56
  when :boolean
55
57
  symbol_to_hyphen_case(directive_name) if @config.directive_value(directive_name)
56
58
  when :sandbox_list
57
59
  build_sandbox_list_directive(directive_name)
58
60
  when :media_type_list
59
61
  build_media_type_list_directive(directive_name)
60
- when :source_list
61
- build_source_list_directive(directive_name)
62
62
  end
63
63
  end.compact.join("; ")
64
64
  end
@@ -27,11 +27,14 @@ module SecureHeaders
27
27
  @img_src = nil
28
28
  @manifest_src = nil
29
29
  @media_src = nil
30
+ @navigate_to = nil
30
31
  @object_src = nil
31
32
  @plugin_types = nil
33
+ @prefetch_src = nil
32
34
  @preserve_schemes = nil
33
35
  @report_only = nil
34
36
  @report_uri = nil
37
+ @require_sri_for = nil
35
38
  @sandbox = nil
36
39
  @script_nonce = nil
37
40
  @script_src = nil
@@ -1,4 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require "set"
4
+
2
5
  module SecureHeaders
3
6
  module PolicyManagement
4
7
  def self.included(base)
@@ -70,6 +73,9 @@ module SecureHeaders
70
73
  # https://w3c.github.io/webappsec/specs/CSP2/
71
74
  BLOCK_ALL_MIXED_CONTENT = :block_all_mixed_content
72
75
  MANIFEST_SRC = :manifest_src
76
+ NAVIGATE_TO = :navigate_to
77
+ PREFETCH_SRC = :prefetch_src
78
+ REQUIRE_SRI_FOR = :require_sri_for
73
79
  UPGRADE_INSECURE_REQUESTS = :upgrade_insecure_requests
74
80
  WORKER_SRC = :worker_src
75
81
 
@@ -77,6 +83,9 @@ module SecureHeaders
77
83
  DIRECTIVES_2_0,
78
84
  BLOCK_ALL_MIXED_CONTENT,
79
85
  MANIFEST_SRC,
86
+ NAVIGATE_TO,
87
+ PREFETCH_SRC,
88
+ REQUIRE_SRI_FOR,
80
89
  WORKER_SRC,
81
90
  UPGRADE_INSECURE_REQUESTS
82
91
  ].flatten.freeze
@@ -100,14 +109,17 @@ module SecureHeaders
100
109
  IMG_SRC => :source_list,
101
110
  MANIFEST_SRC => :source_list,
102
111
  MEDIA_SRC => :source_list,
112
+ NAVIGATE_TO => :source_list,
103
113
  OBJECT_SRC => :source_list,
104
114
  PLUGIN_TYPES => :media_type_list,
115
+ REQUIRE_SRI_FOR => :require_sri_for_list,
105
116
  REPORT_URI => :source_list,
117
+ PREFETCH_SRC => :source_list,
106
118
  SANDBOX => :sandbox_list,
107
119
  SCRIPT_SRC => :source_list,
108
120
  STYLE_SRC => :source_list,
109
121
  WORKER_SRC => :source_list,
110
- UPGRADE_INSECURE_REQUESTS => :boolean
122
+ UPGRADE_INSECURE_REQUESTS => :boolean,
111
123
  }.freeze
112
124
 
113
125
  # These are directives that don't have use a source list, and hence do not
@@ -122,7 +134,8 @@ module SecureHeaders
122
134
  BASE_URI,
123
135
  FORM_ACTION,
124
136
  FRAME_ANCESTORS,
125
- REPORT_URI
137
+ NAVIGATE_TO,
138
+ REPORT_URI,
126
139
  ]
127
140
 
128
141
  FETCH_SOURCES = ALL_DIRECTIVES - NON_FETCH_SOURCES - NON_SOURCE_LIST_SOURCES
@@ -148,6 +161,8 @@ module SecureHeaders
148
161
  :style_nonce
149
162
  ].freeze
150
163
 
164
+ REQUIRE_SRI_FOR_VALUES = Set.new(%w(script style))
165
+
151
166
  module ClassMethods
152
167
  # Public: generate a header name, value array that is user-agent-aware.
153
168
  #
@@ -241,7 +256,8 @@ module SecureHeaders
241
256
  def list_directive?(directive)
242
257
  source_list?(directive) ||
243
258
  sandbox_list?(directive) ||
244
- media_type_list?(directive)
259
+ media_type_list?(directive) ||
260
+ require_sri_for_list?(directive)
245
261
  end
246
262
 
247
263
  # For each directive in additions that does not exist in the original config,
@@ -274,11 +290,17 @@ module SecureHeaders
274
290
  DIRECTIVE_VALUE_TYPES[directive] == :media_type_list
275
291
  end
276
292
 
293
+ def require_sri_for_list?(directive)
294
+ DIRECTIVE_VALUE_TYPES[directive] == :require_sri_for_list
295
+ end
296
+
277
297
  # Private: Validates that the configuration has a valid type, or that it is a valid
278
298
  # source expression.
279
299
  def validate_directive!(directive, value)
280
300
  ensure_valid_directive!(directive)
281
301
  case ContentSecurityPolicy::DIRECTIVE_VALUE_TYPES[directive]
302
+ when :source_list
303
+ validate_source_expression!(directive, value)
282
304
  when :boolean
283
305
  unless boolean?(value)
284
306
  raise ContentSecurityPolicyConfigError.new("#{directive} must be a boolean. Found #{value.class} value")
@@ -287,8 +309,8 @@ module SecureHeaders
287
309
  validate_sandbox_expression!(directive, value)
288
310
  when :media_type_list
289
311
  validate_media_type_expression!(directive, value)
290
- when :source_list
291
- validate_source_expression!(directive, value)
312
+ when :require_sri_for_list
313
+ validate_require_sri_source_expression!(directive, value)
292
314
  else
293
315
  raise ContentSecurityPolicyConfigError.new("Unknown directive #{directive}")
294
316
  end
@@ -323,6 +345,16 @@ module SecureHeaders
323
345
  end
324
346
  end
325
347
 
348
+ # Private: validates that a require sri for expression:
349
+ # 1. is an array of strings
350
+ # 2. is a subset of ["string", "style"]
351
+ def validate_require_sri_source_expression!(directive, require_sri_for_expression)
352
+ ensure_array_of_strings!(directive, require_sri_for_expression)
353
+ unless require_sri_for_expression.to_set.subset?(REQUIRE_SRI_FOR_VALUES)
354
+ raise ContentSecurityPolicyConfigError.new(%(require-sri for must be a subset of #{REQUIRE_SRI_FOR_VALUES.to_a} but was #{require_sri_for_expression}))
355
+ end
356
+ end
357
+
326
358
  # Private: validates that a source expression:
327
359
  # 1. is an array of strings
328
360
  # 2. does not contain any deprecated, now invalid values (inline, eval, self, none)
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module SecureHeaders
3
3
  class Middleware
4
- HPKP_SAME_HOST_WARNING = "[WARNING] HPKP report host should not be the same as the request host. See https://github.com/twitter/secureheaders/issues/166"
5
-
6
4
  def initialize(app)
7
5
  @app = app
8
6
  end
@@ -13,10 +11,6 @@ module SecureHeaders
13
11
  status, headers, response = @app.call(env)
14
12
 
15
13
  config = SecureHeaders.config_for(req)
16
- if config.hpkp_report_host == req.host
17
- Kernel.warn(HPKP_SAME_HOST_WARNING)
18
- end
19
-
20
14
  flag_cookies!(headers, override_secure(env, config.cookies)) unless config.cookies == OPT_OUT
21
15
  headers.merge!(SecureHeaders.header_hash_for(req))
22
16
  [status, headers, response]
@@ -65,7 +65,7 @@ module SecureHeaders
65
65
 
66
66
  def validate_hash_or_true_or_opt_out!(attribute)
67
67
  if !(is_hash?(config[attribute]) || is_true_or_opt_out?(config[attribute]))
68
- raise CookiesConfigError.new("#{attribute} cookie config must be a hash or boolean")
68
+ raise CookiesConfigError.new("#{attribute} cookie config must be a hash, true, or SecureHeaders::OPT_OUT")
69
69
  end
70
70
  end
71
71
 
@@ -0,0 +1,3 @@
1
+ module SecureHeaders
2
+ VERSION = "6.1.0"
3
+ end
@@ -1,8 +1,12 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "secure_headers/version"
4
+
1
5
  # -*- encoding: utf-8 -*-
2
6
  # frozen_string_literal: true
3
7
  Gem::Specification.new do |gem|
4
8
  gem.name = "secure_headers"
5
- gem.version = "6.0.0"
9
+ gem.version = SecureHeaders::VERSION
6
10
  gem.authors = ["Neil Matatall"]
7
11
  gem.email = ["neil.matatall@gmail.com"]
8
12
  gem.description = "Manages application of security headers with many safe defaults."
@@ -116,6 +116,31 @@ module SecureHeaders
116
116
  ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
117
117
  end
118
118
 
119
+ it "allows script as a require-sri-src" do
120
+ csp = ContentSecurityPolicy.new(default_src: %w('self'), require_sri_for: %w(script))
121
+ expect(csp.value).to eq("default-src 'self'; require-sri-for script")
122
+ end
123
+
124
+ it "allows style as a require-sri-src" do
125
+ csp = ContentSecurityPolicy.new(default_src: %w('self'), require_sri_for: %w(style))
126
+ expect(csp.value).to eq("default-src 'self'; require-sri-for style")
127
+ end
128
+
129
+ it "allows script and style as a require-sri-src" do
130
+ csp = ContentSecurityPolicy.new(default_src: %w('self'), require_sri_for: %w(script style))
131
+ expect(csp.value).to eq("default-src 'self'; require-sri-for script style")
132
+ end
133
+
134
+ it "includes prefetch-src" do
135
+ csp = ContentSecurityPolicy.new(default_src: %w('self'), prefetch_src: %w(foo.com))
136
+ expect(csp.value).to eq("default-src 'self'; prefetch-src foo.com")
137
+ end
138
+
139
+ it "includes navigate-to" do
140
+ csp = ContentSecurityPolicy.new(default_src: %w('self'), navigate_to: %w(foo.com))
141
+ expect(csp.value).to eq("default-src 'self'; navigate-to foo.com")
142
+ end
143
+
119
144
  it "supports strict-dynamic" do
120
145
  csp = ContentSecurityPolicy.new({default_src: %w('self'), script_src: [ContentSecurityPolicy::STRICT_DYNAMIC], script_nonce: 123456})
121
146
  expect(csp.value).to eq("default-src 'self'; script-src 'strict-dynamic' 'nonce-123456' 'unsafe-inline'")
@@ -28,24 +28,29 @@ module SecureHeaders
28
28
 
29
29
  # directive values: these values will directly translate into source directives
30
30
  default_src: %w(https: 'self'),
31
- frame_src: %w('self' *.twimg.com itunes.apple.com),
32
- child_src: %w('self' *.twimg.com itunes.apple.com),
31
+
32
+ base_uri: %w('self'),
33
+ block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
33
34
  connect_src: %w(wss:),
35
+ child_src: %w('self' *.twimg.com itunes.apple.com),
34
36
  font_src: %w('self' data:),
37
+ form_action: %w('self' github.com),
38
+ frame_ancestors: %w('none'),
39
+ frame_src: %w('self' *.twimg.com itunes.apple.com),
35
40
  img_src: %w(mycdn.com data:),
36
41
  manifest_src: %w(manifest.com),
37
42
  media_src: %w(utoob.com),
43
+ navigate_to: %w(netscape.com),
38
44
  object_src: %w('self'),
45
+ plugin_types: %w(application/x-shockwave-flash),
46
+ prefetch_src: %w(fetch.com),
47
+ require_sri_for: %w(script style),
39
48
  script_src: %w('self'),
40
49
  style_src: %w('unsafe-inline'),
41
- worker_src: %w(worker.com),
42
- base_uri: %w('self'),
43
- form_action: %w('self' github.com),
44
- frame_ancestors: %w('none'),
45
- plugin_types: %w(application/x-shockwave-flash),
46
- block_all_mixed_content: true, # see [http://www.w3.org/TR/mixed-content/](http://www.w3.org/TR/mixed-content/)
47
50
  upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
48
- report_uri: %w(https://example.com/uri-directive)
51
+ worker_src: %w(worker.com),
52
+
53
+ report_uri: %w(https://example.com/uri-directive),
49
54
  }
50
55
 
51
56
  ContentSecurityPolicy.validate_config!(ContentSecurityPolicyConfig.new(config))
@@ -14,25 +14,6 @@ module SecureHeaders
14
14
  Configuration.default
15
15
  end
16
16
 
17
- it "warns if the hpkp report-uri host is the same as the current host" do
18
- report_host = "report-uri.io"
19
- reset_config
20
- Configuration.default do |config|
21
- config.hpkp = {
22
- max_age: 10000000,
23
- pins: [
24
- {sha256: "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
25
- {sha256: "73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f"}
26
- ],
27
- report_uri: "https://#{report_host}/example-hpkp"
28
- }
29
- end
30
-
31
- expect(Kernel).to receive(:warn).with(Middleware::HPKP_SAME_HOST_WARNING)
32
-
33
- middleware.call(Rack::MockRequest.env_for("https://#{report_host}", {}))
34
- end
35
-
36
17
  it "sets the headers" do
37
18
  _, env = middleware.call(Rack::MockRequest.env_for("https://looocalhost", {}))
38
19
  expect_default_values(env)
@@ -90,16 +90,6 @@ module SecureHeaders
90
90
  Configuration.default do |config|
91
91
  config.csp = { default_src: ["example.com"], script_src: %w('self') }
92
92
  config.csp_report_only = config.csp
93
- config.hpkp = {
94
- report_only: false,
95
- max_age: 10000000,
96
- include_subdomains: true,
97
- report_uri: "https://report-uri.io/example-hpkp",
98
- pins: [
99
- {sha256: "abc"},
100
- {sha256: "123"}
101
- ]
102
- }
103
93
  end
104
94
  SecureHeaders.opt_out_of_all_protection(request)
105
95
  hash = SecureHeaders.header_hash_for(request)
@@ -141,23 +131,6 @@ module SecureHeaders
141
131
  expect(SecureHeaders.header_hash_for(plaintext_request)[StrictTransportSecurity::HEADER_NAME]).to be_nil
142
132
  end
143
133
 
144
- it "does not set the HPKP header if request is over HTTP" do
145
- plaintext_request = Rack::Request.new({})
146
- Configuration.default do |config|
147
- config.hpkp = {
148
- max_age: 1_000_000,
149
- include_subdomains: true,
150
- report_uri: "//example.com/uri-directive",
151
- pins: [
152
- { sha256: "abc" },
153
- { sha256: "123" }
154
- ]
155
- }
156
- end
157
-
158
- expect(SecureHeaders.header_hash_for(plaintext_request)[PublicKeyPins::HEADER_NAME]).to be_nil
159
- end
160
-
161
134
  context "content security policy" do
162
135
  let(:chrome_request) {
163
136
  Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
@@ -531,14 +504,6 @@ module SecureHeaders
531
504
  end.to raise_error(ReferrerPolicyConfigError)
532
505
  end
533
506
 
534
- it "validates your hpkp config upon configuration" do
535
- expect do
536
- Configuration.default do |config|
537
- config.hpkp = "lol"
538
- end
539
- end.to raise_error(PublicKeyPinsConfigError)
540
- end
541
-
542
507
  it "validates your cookies config upon configuration" do
543
508
  expect do
544
509
  Configuration.default do |config|
data/spec/spec_helper.rb CHANGED
@@ -37,7 +37,6 @@ def expect_default_values(hash)
37
37
  expect(hash[SecureHeaders::ExpectCertificateTransparency::HEADER_NAME]).to be_nil
38
38
  expect(hash[SecureHeaders::ClearSiteData::HEADER_NAME]).to be_nil
39
39
  expect(hash[SecureHeaders::ExpectCertificateTransparency::HEADER_NAME]).to be_nil
40
- expect(hash[SecureHeaders::PublicKeyPins::HEADER_NAME]).to be_nil
41
40
  end
42
41
 
43
42
  module SecureHeaders
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: 6.0.0
4
+ version: 6.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: 2018-05-08 00:00:00.000000000 Z
11
+ date: 2019-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -65,7 +65,6 @@ files:
65
65
  - lib/secure_headers/headers/cookie.rb
66
66
  - lib/secure_headers/headers/expect_certificate_transparency.rb
67
67
  - lib/secure_headers/headers/policy_management.rb
68
- - lib/secure_headers/headers/public_key_pins.rb
69
68
  - lib/secure_headers/headers/referrer_policy.rb
70
69
  - lib/secure_headers/headers/strict_transport_security.rb
71
70
  - lib/secure_headers/headers/x_content_type_options.rb
@@ -76,6 +75,7 @@ files:
76
75
  - lib/secure_headers/middleware.rb
77
76
  - lib/secure_headers/railtie.rb
78
77
  - lib/secure_headers/utils/cookies_config.rb
78
+ - lib/secure_headers/version.rb
79
79
  - lib/secure_headers/view_helper.rb
80
80
  - lib/tasks/tasks.rake
81
81
  - secure_headers.gemspec
@@ -85,7 +85,6 @@ files:
85
85
  - spec/lib/secure_headers/headers/cookie_spec.rb
86
86
  - spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb
87
87
  - spec/lib/secure_headers/headers/policy_management_spec.rb
88
- - spec/lib/secure_headers/headers/public_key_pins_spec.rb
89
88
  - spec/lib/secure_headers/headers/referrer_policy_spec.rb
90
89
  - spec/lib/secure_headers/headers/strict_transport_security_spec.rb
91
90
  - spec/lib/secure_headers/headers/x_content_type_options_spec.rb
@@ -116,8 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
115
  - !ruby/object:Gem::Version
117
116
  version: '0'
118
117
  requirements: []
119
- rubyforge_project:
120
- rubygems_version: 2.6.13
118
+ rubygems_version: 3.0.1
121
119
  signing_key:
122
120
  specification_version: 4
123
121
  summary: Add easily configured security headers to responses including content-security-policy,
@@ -129,7 +127,6 @@ test_files:
129
127
  - spec/lib/secure_headers/headers/cookie_spec.rb
130
128
  - spec/lib/secure_headers/headers/expect_certificate_transparency_spec.rb
131
129
  - spec/lib/secure_headers/headers/policy_management_spec.rb
132
- - spec/lib/secure_headers/headers/public_key_pins_spec.rb
133
130
  - spec/lib/secure_headers/headers/referrer_policy_spec.rb
134
131
  - spec/lib/secure_headers/headers/strict_transport_security_spec.rb
135
132
  - spec/lib/secure_headers/headers/x_content_type_options_spec.rb
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
- module SecureHeaders
3
- class PublicKeyPinsConfigError < StandardError; end
4
- class PublicKeyPins
5
- HEADER_NAME = "Public-Key-Pins".freeze
6
- REPORT_ONLY = "Public-Key-Pins-Report-Only".freeze
7
- HASH_ALGORITHMS = [:sha256].freeze
8
-
9
-
10
- class << self
11
- # Public: make an hpkp header name, value pair
12
- #
13
- # Returns nil if not configured, returns header name and value if configured.
14
- def make_header(config, user_agent = nil)
15
- return if config.nil? || config == OPT_OUT
16
- header = new(config)
17
- [header.name, header.value]
18
- end
19
-
20
- def validate_config!(config)
21
- return if config.nil? || config == OPT_OUT
22
- raise PublicKeyPinsConfigError.new("config must be a hash.") unless config.is_a? Hash
23
-
24
- if !config[:max_age]
25
- raise PublicKeyPinsConfigError.new("max-age is a required directive.")
26
- elsif config[:max_age].to_s !~ /\A\d+\z/
27
- raise PublicKeyPinsConfigError.new("max-age must be a number.
28
- #{config[:max_age]} was supplied.")
29
- elsif config[:pins] && config[:pins].length < 2
30
- raise PublicKeyPinsConfigError.new("A minimum of 2 pins are required.")
31
- end
32
- end
33
- end
34
-
35
- def initialize(config)
36
- @max_age = config.fetch(:max_age, nil)
37
- @pins = config.fetch(:pins, nil)
38
- @report_uri = config.fetch(:report_uri, nil)
39
- @report_only = !!config.fetch(:report_only, nil)
40
- @include_subdomains = !!config.fetch(:include_subdomains, nil)
41
- end
42
-
43
- def name
44
- if @report_only
45
- REPORT_ONLY
46
- else
47
- HEADER_NAME
48
- end
49
- end
50
-
51
- def value
52
- [
53
- max_age_directive,
54
- pin_directives,
55
- report_uri_directive,
56
- subdomain_directive
57
- ].compact.join("; ").strip
58
- end
59
-
60
- def pin_directives
61
- return nil if @pins.nil?
62
- @pins.collect do |pin|
63
- pin.map do |token, hash|
64
- "pin-#{token}=\"#{hash}\"" if HASH_ALGORITHMS.include?(token)
65
- end
66
- end.join("; ")
67
- end
68
-
69
- def max_age_directive
70
- "max-age=#{@max_age}" if @max_age
71
- end
72
-
73
- def report_uri_directive
74
- "report-uri=\"#{@report_uri}\"" if @report_uri
75
- end
76
-
77
- def subdomain_directive
78
- @include_subdomains ? "includeSubDomains" : nil
79
- end
80
- end
81
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
- require "spec_helper"
3
-
4
- module SecureHeaders
5
- describe PublicKeyPins do
6
- specify { expect(PublicKeyPins.new(max_age: 1234, report_only: true).name).to eq("Public-Key-Pins-Report-Only") }
7
- specify { expect(PublicKeyPins.new(max_age: 1234).name).to eq("Public-Key-Pins") }
8
-
9
- specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
10
- specify { expect(PublicKeyPins.new(max_age: 1234).value).to eq("max-age=1234") }
11
- specify do
12
- config = { max_age: 1234, pins: [{ sha256: "base64encodedpin1" }, { sha256: "base64encodedpin2" }] }
13
- header_value = "max-age=1234; pin-sha256=\"base64encodedpin1\"; pin-sha256=\"base64encodedpin2\""
14
- expect(PublicKeyPins.new(config).value).to eq(header_value)
15
- end
16
-
17
- context "with an invalid configuration" do
18
- it "raises an exception when max-age is not provided" do
19
- expect do
20
- PublicKeyPins.validate_config!(foo: "bar")
21
- end.to raise_error(PublicKeyPinsConfigError)
22
- end
23
-
24
- it "raises an exception with an invalid max-age" do
25
- expect do
26
- PublicKeyPins.validate_config!(max_age: "abc123")
27
- end.to raise_error(PublicKeyPinsConfigError)
28
- end
29
-
30
- it "raises an exception with less than 2 pins" do
31
- expect do
32
- config = { max_age: 1234, pins: [{ sha256: "base64encodedpin" }] }
33
- PublicKeyPins.validate_config!(config)
34
- end.to raise_error(PublicKeyPinsConfigError)
35
- end
36
- end
37
- end
38
- end