secure_headers 3.4.0 → 3.4.1

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
2
  SHA1:
3
- metadata.gz: 88233251c7a26e1269f77957d56823619e3d9d11
4
- data.tar.gz: 8e9c0e69fcca0298ee52223d587134f5a8df062f
3
+ metadata.gz: 45e392304147a02bee8cf0709fe52e16f9ad255c
4
+ data.tar.gz: e07030aa93c84a20e9b22d88af6489d8a71c95e7
5
5
  SHA512:
6
- metadata.gz: f421d0d58eb9b5a7b22fd13932644c2c3868e0294886cbf5250127e8807fa20dec95717219e0a82ce59f49f376b32e17b97b77c7e011d2e2d1633162c1297bbc
7
- data.tar.gz: ae63cbdd588bf6c31e531bfec5480e022a5df96c8ee2cc4c824d1e869f64df2a44073d795cc6d5e7ffeca6d6369f0d2e33f202ce8ceda04d274849e95789ff43
6
+ metadata.gz: 8238f728eb74303b6aac54d40e3eaf1aacdaf7e0c910fe9a05f3dc005daa9eb96876391619eb2cb613a0d2a8ad358a48bc899626be46d201561f05064f47fdf8
7
+ data.tar.gz: d0d448274a7a4789f668ac59c49903bf50889a2675a82d62d91ca721c5160b50288b5fafcf8b041422f575cc42d31b4a614fbc8036759fb522c149131a472f33
data/CHANGELOG.md CHANGED
@@ -1,3 +1,64 @@
1
+ ## 3.4.1 Named Appends
2
+
3
+ ### Small bugfix
4
+
5
+ If your CSP did not define a script/style-src and you tried to use a script/style nonce, the nonce would be added to the page but it would not be added to the CSP. A workaround is to define a script/style src but now it should add the missing directive (and populate it with the default-src).
6
+
7
+ ### Named Appends
8
+
9
+ Named Appends are blocks of code that can be reused and composed during requests. e.g. If a certain partial is rendered conditionally, and the csp needs to be adjusted for that partial, you can create a named append for that situation. The value returned by the block will be passed into `append_content_security_policy_directives`. The current request object is passed as an argument to the block for even more flexibility.
10
+
11
+ ```ruby
12
+ def show
13
+ if include_widget?
14
+ @widget = widget.render
15
+ use_content_security_policy_named_append(:widget_partial)
16
+ end
17
+ end
18
+
19
+
20
+ SecureHeaders::Configuration.named_append(:widget_partial) do |request|
21
+ if request.controller_instance.current_user.in_test_bucket?
22
+ SecureHeaders.override_x_frame_options(request, "DENY")
23
+ { child_src: %w(beta.thirdpartyhost.com) }
24
+ else
25
+ { child_src: %w(thirdpartyhost.com) }
26
+ end
27
+ end
28
+ ```
29
+
30
+ You can use as many named appends as you would like per request, but be careful because order of inclusion matters. Consider the following:
31
+
32
+ ```ruby
33
+ SecureHeader::Configuration.default do |config|
34
+ config.csp = { default_src: %w('self')}
35
+ end
36
+
37
+ SecureHeaders::Configuration.named_append(:A) do |request|
38
+ { default_src: %w(myhost.com) }
39
+ end
40
+
41
+ SecureHeaders::Configuration.named_append(:B) do |request|
42
+ { script_src: %w('unsafe-eval') }
43
+ end
44
+ ```
45
+
46
+ The following code will produce different policies due to the way policies are normalized (e.g. providing a previously undefined directive that inherits from `default-src`, removing host source values when `*` is provided. Removing `'none'` when additional values are present, etc.):
47
+
48
+ ```ruby
49
+ def index
50
+ use_content_security_policy_named_append(:A)
51
+ use_content_security_policy_named_append(:B)
52
+ # produces default-src 'self' myhost.com; script-src 'self' myhost.com 'unsafe-eval';
53
+ end
54
+
55
+ def show
56
+ use_content_security_policy_named_append(:B)
57
+ use_content_security_policy_named_append(:A)
58
+ # produces default-src 'self' myhost.com; script-src 'self' 'unsafe-eval';
59
+ end
60
+ ```
61
+
1
62
  ## 3.4.0 the frame-src/child-src transition for Firefox.
2
63
 
3
64
  Handle the `child-src`/`frame-src` transition semi-intelligently across versions. I think the code best descibes the behavior here:
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Secure Headers [![Build Status](https://travis-ci.org/twitter/secureheaders.png?branch=master)](http://travis-ci.org/twitter/secureheaders) [![Code Climate](https://codeclimate.com/github/twitter/secureheaders.png)](https://codeclimate.com/github/twitter/secureheaders) [![Coverage Status](https://coveralls.io/repos/twitter/secureheaders/badge.png)](https://coveralls.io/r/twitter/secureheaders)
1
+ # Secure Headers [![Build Status](https://travis-ci.org/twitter/secureheaders.svg?branch=master)](http://travis-ci.org/twitter/secureheaders) [![Code Climate](https://codeclimate.com/github/twitter/secureheaders.svg)](https://codeclimate.com/github/twitter/secureheaders) [![Coverage Status](https://coveralls.io/repos/twitter/secureheaders/badge.svg)](https://coveralls.io/r/twitter/secureheaders)
2
2
 
3
3
 
4
4
  **The 3.x branch was recently merged**. See the [upgrading to 3.x doc](upgrading-to-3-0.md) for instructions on how to upgrade including the differences and benefits of using the 3.x branch.
@@ -94,6 +94,62 @@ use SecureHeaders::Middleware
94
94
 
95
95
  All headers except for PublicKeyPins have a default value. See the [corresponding classes for their defaults](https://github.com/twitter/secureheaders/tree/master/lib/secure_headers/headers).
96
96
 
97
+ ## Named Appends
98
+
99
+ Named Appends are blocks of code that can be reused and composed during requests. e.g. If a certain partial is rendered conditionally, and the csp needs to be adjusted for that partial, you can create a named append for that situation. The value returned by the block will be passed into `append_content_security_policy_directives`. The current request object is passed as an argument to the block for even more flexibility.
100
+
101
+ ```ruby
102
+ def show
103
+ if include_widget?
104
+ @widget = widget.render
105
+ use_content_security_policy_named_append(:widget_partial)
106
+ end
107
+ end
108
+
109
+
110
+ SecureHeaders::Configuration.named_append(:widget_partial) do |request|
111
+ SecureHeaders.override_x_frame_options(request, "DENY")
112
+ if request.controller_instance.current_user.in_test_bucket?
113
+ { child_src: %w(beta.thirdpartyhost.com) }
114
+ else
115
+ { child_src: %w(thirdpartyhost.com) }
116
+ end
117
+ end
118
+ ```
119
+
120
+ You can use as many named appends as you would like per request, but be careful because order of inclusion matters. Consider the following:
121
+
122
+ ```ruby
123
+ SecureHeader::Configuration.default do |config|
124
+ config.csp = { default_src: %w('self')}
125
+ end
126
+
127
+ SecureHeaders::Configuration.named_append(:A) do |request|
128
+ { default_src: %w(myhost.com) }
129
+ end
130
+
131
+ SecureHeaders::Configuration.named_append(:B) do |request|
132
+ { script_src: %w('unsafe-eval') }
133
+ end
134
+ ```
135
+
136
+ The following code will produce different policies due to the way policies are normalized (e.g. providing a previously undefined directive that inherits from `default-src`, removing host source values when `*` is provided. Removing `'none'` when additional values are present, etc.):
137
+
138
+ ```ruby
139
+ def index
140
+ use_content_security_policy_named_append(:A)
141
+ use_content_security_policy_named_append(:B)
142
+ # produces default-src 'self' myhost.com; script-src 'self' myhost.com 'unsafe-eval';
143
+ end
144
+
145
+ def show
146
+ use_content_security_policy_named_append(:B)
147
+ use_content_security_policy_named_append(:A)
148
+ # produces default-src 'self' myhost.com; script-src 'self' 'unsafe-eval';
149
+ end
150
+ ```
151
+
152
+
97
153
  ## Named overrides
98
154
 
99
155
  Named overrides serve two purposes:
@@ -125,7 +181,7 @@ end
125
181
 
126
182
  class MyController < ApplicationController
127
183
  def index
128
- # Produces default-src 'self'; script-src example.org otherdomain.org
184
+ # Produces default-src 'self'; script-src example.org otherdomain.com
129
185
  use_secure_headers_override(:script_from_otherdomain_com)
130
186
  end
131
187
 
@@ -72,6 +72,11 @@ module SecureHeaders
72
72
  override_secure_headers_request_config(request, config)
73
73
  end
74
74
 
75
+ def use_content_security_policy_named_append(request, name)
76
+ additions = SecureHeaders::Configuration.named_appends(name).call(request)
77
+ append_content_security_policy_directives(request, additions)
78
+ end
79
+
75
80
  # Public: override X-Frame-Options settings for this request.
76
81
  #
77
82
  # value - deny, sameorigin, or allowall
@@ -267,4 +272,8 @@ module SecureHeaders
267
272
  def override_x_frame_options(value)
268
273
  SecureHeaders.override_x_frame_options(request, value)
269
274
  end
275
+
276
+ def use_content_security_policy_named_append(name)
277
+ SecureHeaders.use_content_security_policy_named_append(request, name)
278
+ end
270
279
  end
@@ -46,6 +46,17 @@ module SecureHeaders
46
46
  @configurations[name]
47
47
  end
48
48
 
49
+ def named_appends(name)
50
+ @appends ||= {}
51
+ @appends[name]
52
+ end
53
+
54
+ def named_append(name, target = nil, &block)
55
+ @appends ||= {}
56
+ raise "Provide a configuration block" unless block_given?
57
+ @appends[name] = block
58
+ end
59
+
49
60
  private
50
61
 
51
62
  # Private: add a valid configuration to the global set of named configs.
@@ -53,16 +53,6 @@ module SecureHeaders
53
53
  FRAME_ANCESTORS = :frame_ancestors
54
54
  PLUGIN_TYPES = :plugin_types
55
55
 
56
- # These are directives that do not inherit the default-src value. This is
57
- # useful when calling #combine_policies.
58
- NON_FETCH_SOURCES = [
59
- BASE_URI,
60
- FORM_ACTION,
61
- FRAME_ANCESTORS,
62
- PLUGIN_TYPES,
63
- REPORT_URI
64
- ]
65
-
66
56
  DIRECTIVES_2_0 = [
67
57
  DIRECTIVES_1_0,
68
58
  BASE_URI,
@@ -127,6 +117,18 @@ module SecureHeaders
127
117
  # everything else is in between.
128
118
  BODY_DIRECTIVES = ALL_DIRECTIVES - [DEFAULT_SRC, REPORT_URI]
129
119
 
120
+ # These are directives that do not inherit the default-src value. This is
121
+ # useful when calling #combine_policies.
122
+ NON_FETCH_SOURCES = [
123
+ BASE_URI,
124
+ FORM_ACTION,
125
+ FRAME_ANCESTORS,
126
+ PLUGIN_TYPES,
127
+ REPORT_URI
128
+ ]
129
+
130
+ FETCH_SOURCES = ALL_DIRECTIVES - NON_FETCH_SOURCES
131
+
130
132
  VARIATIONS = {
131
133
  "Chrome" => CHROME_DIRECTIVES,
132
134
  "Opera" => CHROME_DIRECTIVES,
@@ -268,8 +270,23 @@ module SecureHeaders
268
270
  def populate_fetch_source_with_default!(original, additions)
269
271
  # in case we would be appending to an empty directive, fill it with the default-src value
270
272
  additions.keys.each do |directive|
271
- unless original[directive] || !source_list?(directive) || NON_FETCH_SOURCES.include?(directive)
272
- original[directive] = original[:default_src]
273
+ if !original[directive] && ((source_list?(directive) && FETCH_SOURCES.include?(directive)) || nonce_added?(original, additions))
274
+ if nonce_added?(original, additions)
275
+ inferred_directive = directive.to_s.gsub(/_nonce/, "_src").to_sym
276
+ unless original[inferred_directive] || NON_FETCH_SOURCES.include?(inferred_directive)
277
+ original[inferred_directive] = original[:default_src]
278
+ end
279
+ else
280
+ original[directive] = original[:default_src]
281
+ end
282
+ end
283
+ end
284
+ end
285
+
286
+ def nonce_added?(original, additions)
287
+ [:script_nonce, :style_nonce].each do |nonce|
288
+ if additions[nonce] && !original[nonce]
289
+ return true
273
290
  end
274
291
  end
275
292
  end
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |gem|
3
3
  gem.name = "secure_headers"
4
- gem.version = "3.4.0"
4
+ gem.version = "3.4.1"
5
5
  gem.authors = ["Neil Matatall"]
6
6
  gem.email = ["neil.matatall@gmail.com"]
7
7
  gem.description = 'Security related headers all in one gem.'
@@ -138,6 +138,10 @@ module SecureHeaders
138
138
  end
139
139
 
140
140
  context "content security policy" do
141
+ let(:chrome_request) {
142
+ Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
143
+ }
144
+
141
145
  it "appends a value to csp directive" do
142
146
  Configuration.default do |config|
143
147
  config.csp = {
@@ -151,6 +155,52 @@ module SecureHeaders
151
155
  expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
152
156
  end
153
157
 
158
+ it "supports named appends" do
159
+ Configuration.default do |config|
160
+ config.csp = {
161
+ default_src: %w('self')
162
+ }
163
+ end
164
+
165
+ Configuration.named_append(:moar_default_sources) do |request|
166
+ { default_src: %w(https:)}
167
+ end
168
+
169
+ Configuration.named_append(:how_about_a_script_src_too) do |request|
170
+ { script_src: %w('unsafe-inline')}
171
+ end
172
+
173
+ SecureHeaders.use_content_security_policy_named_append(request, :moar_default_sources)
174
+ SecureHeaders.use_content_security_policy_named_append(request, :how_about_a_script_src_too)
175
+ hash = SecureHeaders.header_hash_for(request)
176
+
177
+ expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self' https: 'unsafe-inline'")
178
+ end
179
+
180
+ it "appends a nonce to a missing script-src value" do
181
+ Configuration.default do |config|
182
+ config.csp = {
183
+ default_src: %w('self')
184
+ }
185
+ end
186
+
187
+ SecureHeaders.content_security_policy_script_nonce(request) # should add the value to the header
188
+ hash = SecureHeaders.header_hash_for(chrome_request)
189
+ expect(hash[CSP::HEADER_NAME]).to match /\Adefault-src 'self'; script-src 'self' 'nonce-.*'\z/
190
+ end
191
+
192
+ it "appends a hash to a missing script-src value" do
193
+ Configuration.default do |config|
194
+ config.csp = {
195
+ default_src: %w('self')
196
+ }
197
+ end
198
+
199
+ SecureHeaders.append_content_security_policy_directives(request, script_src: %w('sha256-abc123'))
200
+ hash = SecureHeaders.header_hash_for(chrome_request)
201
+ expect(hash[CSP::HEADER_NAME]).to match /\Adefault-src 'self'; script-src 'self' 'sha256-abc123'\z/
202
+ end
203
+
154
204
  it "dups global configuration just once when overriding n times and only calls idempotent_additions? once" do
155
205
  Configuration.default do |config|
156
206
  config.csp = {
@@ -234,7 +284,6 @@ module SecureHeaders
234
284
  }
235
285
  end
236
286
 
237
- chrome_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
238
287
  nonce = SecureHeaders.content_security_policy_script_nonce(chrome_request)
239
288
 
240
289
  # simulate the nonce being used multiple times in a request:
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: 3.4.0
4
+ version: 3.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Neil Matatall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-18 00:00:00.000000000 Z
11
+ date: 2016-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake