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 +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 [](http://travis-ci.org/twitter/secureheaders) [](https://codeclimate.com/github/twitter/secureheaders) [](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
|