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 +4 -4
- data/CHANGELOG.md +61 -0
- data/README.md +58 -2
- data/lib/secure_headers.rb +9 -0
- data/lib/secure_headers/configuration.rb +11 -0
- data/lib/secure_headers/headers/policy_management.rb +29 -12
- data/secure_headers.gemspec +1 -1
- data/spec/lib/secure_headers_spec.rb +50 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45e392304147a02bee8cf0709fe52e16f9ad255c
|
4
|
+
data.tar.gz: e07030aa93c84a20e9b22d88af6489d8a71c95e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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
|
|
data/lib/secure_headers.rb
CHANGED
@@ -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
|
-
|
272
|
-
original
|
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
|
data/secure_headers.gemspec
CHANGED
@@ -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.
|
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.
|
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-
|
11
|
+
date: 2016-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|