secure_headers 6.0.0 → 6.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.

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