secure_headers 3.4.0 → 3.4.1

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: 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