secure_headers 3.3.1 → 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/.github/ISSUE_TEMPLATE.md +41 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +20 -0
- data/CHANGELOG.md +79 -0
- data/Gemfile +2 -1
- data/README.md +66 -11
- data/lib/secure_headers/configuration.rb +13 -2
- data/lib/secure_headers/headers/content_security_policy.rb +33 -3
- data/lib/secure_headers/headers/policy_management.rb +43 -12
- data/lib/secure_headers/view_helper.rb +3 -5
- data/lib/secure_headers.rb +9 -0
- data/lib/tasks/tasks.rake +4 -4
- data/secure_headers.gemspec +1 -1
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +37 -11
- data/spec/lib/secure_headers/headers/policy_management_spec.rb +2 -2
- data/spec/lib/secure_headers_spec.rb +53 -3
- data/spec/spec_helper.rb +2 -1
- data/upgrading-to-3-0.md +13 -11
- metadata +4 -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
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Feature Requests
|
|
2
|
+
|
|
3
|
+
## Adding a new header
|
|
4
|
+
|
|
5
|
+
Generally, adding a new header is always OK.
|
|
6
|
+
|
|
7
|
+
* Is the header supported by any user agent? If so, which?
|
|
8
|
+
* What does it do?
|
|
9
|
+
* What are the valid values for the header?
|
|
10
|
+
* Where does the specification live?
|
|
11
|
+
|
|
12
|
+
## Adding a new CSP directive
|
|
13
|
+
|
|
14
|
+
* Is the directive supported by any user agent? If so, which?
|
|
15
|
+
* What does it do?
|
|
16
|
+
* What are the valid values for the directive?
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Bugs
|
|
21
|
+
|
|
22
|
+
Console errors and deprecation warnings are considered bugs that should be addressed with more precise UA sniffing. Bugs caused by incorrect or invalid UA sniffing are also bugs.
|
|
23
|
+
|
|
24
|
+
### Expected outcome
|
|
25
|
+
|
|
26
|
+
Describe what you expected to happen
|
|
27
|
+
|
|
28
|
+
1. I configure CSP to do X
|
|
29
|
+
1. When I inspect the response headers, the CSP should have included X
|
|
30
|
+
|
|
31
|
+
### Actual outcome
|
|
32
|
+
|
|
33
|
+
1. The generated policy did not include X
|
|
34
|
+
|
|
35
|
+
### Config
|
|
36
|
+
|
|
37
|
+
Please provide the configuration (`SecureHeaders::Configuration.default`) you are using including any overrides (`SecureHeaders::Configuration.override`).
|
|
38
|
+
|
|
39
|
+
### Generated headers
|
|
40
|
+
|
|
41
|
+
Provide a sample response containing the headers
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## All PRs:
|
|
2
|
+
|
|
3
|
+
* [ ] Has tests
|
|
4
|
+
* [ ] Documentation updated
|
|
5
|
+
|
|
6
|
+
## Adding a new header
|
|
7
|
+
|
|
8
|
+
Generally, adding a new header is always OK.
|
|
9
|
+
|
|
10
|
+
* Is the header supported by any user agent? If so, which?
|
|
11
|
+
* What does it do?
|
|
12
|
+
* What are the valid values for the header?
|
|
13
|
+
* Where does the specification live?
|
|
14
|
+
|
|
15
|
+
## Adding a new CSP directive
|
|
16
|
+
|
|
17
|
+
* Is the directive supported by any user agent? If so, which?
|
|
18
|
+
* What does it do?
|
|
19
|
+
* What are the valid values for the directive?
|
|
20
|
+
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
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
|
+
|
|
62
|
+
## 3.4.0 the frame-src/child-src transition for Firefox.
|
|
63
|
+
|
|
64
|
+
Handle the `child-src`/`frame-src` transition semi-intelligently across versions. I think the code best descibes the behavior here:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
if supported_directives.include?(:child_src)
|
|
68
|
+
@config[:child_src] = @config[:child_src] || @config[:frame_src]
|
|
69
|
+
else
|
|
70
|
+
@config[:frame_src] = @config[:frame_src] || @config[:child_src]
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Also, @koenpunt noticed that we were [loading view helpers](https://github.com/twitter/secureheaders/pull/272) in a way that Rails 5 did not like.
|
|
75
|
+
|
|
76
|
+
## 3.3.2 minor fix to silence warnings when using rake
|
|
77
|
+
|
|
78
|
+
[@dankohn](https://github.com/twitter/secureheaders/issues/257) was seeing "already initialized" errors in his output. This change conditionally defines the constants.
|
|
79
|
+
|
|
1
80
|
## 3.3.1 bugfix for boolean CSP directives
|
|
2
81
|
|
|
3
82
|
[@stefansundin](https://github.com/twitter/secureheaders/pull/253) noticed that supplying `false` to "boolean" CSP directives (e.g. `upgrade-insecure-requests` and `block-all-mixed-content`) would still include the value.
|
data/Gemfile
CHANGED
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.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
The gem will automatically apply several headers that are related to security. This includes:
|
|
9
9
|
- Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 2 Specification](http://www.w3.org/TR/CSP2/)
|
|
10
10
|
- HTTP Strict Transport Security (HSTS) - Ensures the browser never visits the http version of a website. Protects from SSLStrip/Firesheep attacks. [HSTS Specification](https://tools.ietf.org/html/rfc6797)
|
|
11
|
-
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options
|
|
11
|
+
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options Specification](https://tools.ietf.org/html/rfc7034)
|
|
12
12
|
- X-XSS-Protection - [Cross site scripting heuristic filter for IE/Chrome](https://msdn.microsoft.com/en-us/library/dd565647\(v=vs.85\).aspx)
|
|
13
13
|
- X-Content-Type-Options - [Prevent content type sniffing](https://msdn.microsoft.com/library/gg622941\(v=vs.85\).aspx)
|
|
14
14
|
- X-Download-Options - [Prevent file downloads opening](https://msdn.microsoft.com/library/jj542450(v=vs.85).aspx)
|
|
@@ -53,20 +53,19 @@ SecureHeaders::Configuration.default do |config|
|
|
|
53
53
|
|
|
54
54
|
# directive values: these values will directly translate into source directives
|
|
55
55
|
default_src: %w(https: 'self'),
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
base_uri: %w('self'),
|
|
57
|
+
block_all_mixed_content: true, # see http://www.w3.org/TR/mixed-content/
|
|
58
|
+
child_src: %w('self'), # if child-src isn't supported, the value for frame-src will be set.
|
|
59
|
+
connect_src: %w(wss:),
|
|
58
60
|
font_src: %w('self' data:),
|
|
61
|
+
form_action: %w('self' github.com),
|
|
62
|
+
frame_ancestors: %w('none'),
|
|
59
63
|
img_src: %w(mycdn.com data:),
|
|
60
64
|
media_src: %w(utoob.com),
|
|
61
65
|
object_src: %w('self'),
|
|
66
|
+
plugin_types: %w(application/x-shockwave-flash),
|
|
62
67
|
script_src: %w('self'),
|
|
63
68
|
style_src: %w('unsafe-inline'),
|
|
64
|
-
base_uri: %w('self'),
|
|
65
|
-
child_src: %w('self'),
|
|
66
|
-
form_action: %w('self' github.com),
|
|
67
|
-
frame_ancestors: %w('none'),
|
|
68
|
-
plugin_types: %w(application/x-shockwave-flash),
|
|
69
|
-
block_all_mixed_content: true, # see http://www.w3.org/TR/mixed-content/
|
|
70
69
|
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
|
|
71
70
|
report_uri: %w(https://report-uri.io/example-csp)
|
|
72
71
|
}
|
|
@@ -95,6 +94,62 @@ use SecureHeaders::Middleware
|
|
|
95
94
|
|
|
96
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).
|
|
97
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
|
+
|
|
98
153
|
## Named overrides
|
|
99
154
|
|
|
100
155
|
Named overrides serve two purposes:
|
|
@@ -126,7 +181,7 @@ end
|
|
|
126
181
|
|
|
127
182
|
class MyController < ApplicationController
|
|
128
183
|
def index
|
|
129
|
-
# Produces default-src 'self'; script-src example.org otherdomain.
|
|
184
|
+
# Produces default-src 'self'; script-src example.org otherdomain.com
|
|
130
185
|
use_secure_headers_override(:script_from_otherdomain_com)
|
|
131
186
|
end
|
|
132
187
|
|
|
@@ -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.
|
|
@@ -203,7 +214,7 @@ module SecureHeaders
|
|
|
203
214
|
raise IllegalPolicyModificationError, "You are attempting to modify CSP settings directly. Use dynamic_csp= instead."
|
|
204
215
|
end
|
|
205
216
|
|
|
206
|
-
@csp = new_csp
|
|
217
|
+
@csp = self.class.send(:deep_copy_if_hash, new_csp)
|
|
207
218
|
end
|
|
208
219
|
|
|
209
220
|
def cookies=(cookies)
|
|
@@ -215,7 +226,7 @@ module SecureHeaders
|
|
|
215
226
|
end
|
|
216
227
|
|
|
217
228
|
def hpkp=(hpkp)
|
|
218
|
-
@hpkp = hpkp
|
|
229
|
+
@hpkp = self.class.send(:deep_copy_if_hash, hpkp)
|
|
219
230
|
end
|
|
220
231
|
|
|
221
232
|
def hpkp_report_host=(hpkp_report_host)
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
require_relative 'policy_management'
|
|
2
|
+
require 'useragent'
|
|
2
3
|
|
|
3
4
|
module SecureHeaders
|
|
4
5
|
class ContentSecurityPolicyConfigError < StandardError; end
|
|
5
6
|
class ContentSecurityPolicy
|
|
6
7
|
include PolicyManagement
|
|
7
8
|
|
|
9
|
+
# constants to be used for version-specific UA sniffing
|
|
10
|
+
VERSION_46 = ::UserAgent::Version.new("46")
|
|
11
|
+
|
|
8
12
|
def initialize(config = nil, user_agent = OTHER)
|
|
9
|
-
config = Configuration.deep_copy
|
|
10
|
-
@config = config
|
|
13
|
+
@config = Configuration.send(:deep_copy, config || DEFAULT_CONFIG)
|
|
11
14
|
@parsed_ua = if user_agent.is_a?(UserAgent::Browsers::Base)
|
|
12
15
|
user_agent
|
|
13
16
|
else
|
|
14
17
|
UserAgent.parse(user_agent)
|
|
15
18
|
end
|
|
19
|
+
normalize_child_frame_src
|
|
16
20
|
@report_only = @config[:report_only]
|
|
17
21
|
@preserve_schemes = @config[:preserve_schemes]
|
|
18
22
|
@script_nonce = @config[:script_nonce]
|
|
@@ -42,6 +46,22 @@ module SecureHeaders
|
|
|
42
46
|
|
|
43
47
|
private
|
|
44
48
|
|
|
49
|
+
# frame-src is deprecated, child-src is being implemented. They are
|
|
50
|
+
# very similar and in most cases, the same value can be used for both.
|
|
51
|
+
def normalize_child_frame_src
|
|
52
|
+
if @config[:frame_src] && @config[:child_src] && @config[:frame_src] != @config[:child_src]
|
|
53
|
+
Kernel.warn("#{Kernel.caller.first}: [DEPRECATION] both :child_src and :frame_src supplied and do not match. This can lead to inconsistent behavior across browsers.")
|
|
54
|
+
elsif @config[:frame_src]
|
|
55
|
+
Kernel.warn("#{Kernel.caller.first}: [DEPRECATION] :frame_src is deprecated, use :child_src instead. Provided: #{@config[:frame_src]}.")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if supported_directives.include?(:child_src)
|
|
59
|
+
@config[:child_src] = @config[:child_src] || @config[:frame_src]
|
|
60
|
+
else
|
|
61
|
+
@config[:frame_src] = @config[:frame_src] || @config[:child_src]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
45
65
|
# Private: converts the config object into a string representing a policy.
|
|
46
66
|
# Places default-src at the first directive and report-uri as the last. All
|
|
47
67
|
# others are presented in alphabetical order.
|
|
@@ -162,9 +182,19 @@ module SecureHeaders
|
|
|
162
182
|
|
|
163
183
|
# Private: determine which directives are supported for the given user agent.
|
|
164
184
|
#
|
|
185
|
+
# Add UA-sniffing special casing here.
|
|
186
|
+
#
|
|
165
187
|
# Returns an array of symbols representing the directives.
|
|
166
188
|
def supported_directives
|
|
167
|
-
@supported_directives ||= VARIATIONS[@parsed_ua.browser]
|
|
189
|
+
@supported_directives ||= if VARIATIONS[@parsed_ua.browser]
|
|
190
|
+
if @parsed_ua.browser == "Firefox" && @parsed_ua.version >= VERSION_46
|
|
191
|
+
VARIATIONS["FirefoxTransitional"]
|
|
192
|
+
else
|
|
193
|
+
VARIATIONS[@parsed_ua.browser]
|
|
194
|
+
end
|
|
195
|
+
else
|
|
196
|
+
VARIATIONS[OTHER]
|
|
197
|
+
end
|
|
168
198
|
end
|
|
169
199
|
|
|
170
200
|
def nonces_supported?
|
|
@@ -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,
|
|
@@ -100,10 +90,23 @@ module SecureHeaders
|
|
|
100
90
|
PLUGIN_TYPES
|
|
101
91
|
].freeze
|
|
102
92
|
|
|
93
|
+
FIREFOX_46_DEPRECATED_DIRECTIVES = [
|
|
94
|
+
FRAME_SRC
|
|
95
|
+
].freeze
|
|
96
|
+
|
|
97
|
+
FIREFOX_46_UNSUPPORTED_DIRECTIVES = [
|
|
98
|
+
BLOCK_ALL_MIXED_CONTENT,
|
|
99
|
+
PLUGIN_TYPES
|
|
100
|
+
].freeze
|
|
101
|
+
|
|
103
102
|
FIREFOX_DIRECTIVES = (
|
|
104
103
|
DIRECTIVES_2_0 + DIRECTIVES_DRAFT - FIREFOX_UNSUPPORTED_DIRECTIVES
|
|
105
104
|
).freeze
|
|
106
105
|
|
|
106
|
+
FIREFOX_46_DIRECTIVES = (
|
|
107
|
+
DIRECTIVES_2_0 + DIRECTIVES_DRAFT - FIREFOX_46_UNSUPPORTED_DIRECTIVES - FIREFOX_46_DEPRECATED_DIRECTIVES
|
|
108
|
+
).freeze
|
|
109
|
+
|
|
107
110
|
CHROME_DIRECTIVES = (
|
|
108
111
|
DIRECTIVES_2_0 + DIRECTIVES_DRAFT
|
|
109
112
|
).freeze
|
|
@@ -114,10 +117,23 @@ module SecureHeaders
|
|
|
114
117
|
# everything else is in between.
|
|
115
118
|
BODY_DIRECTIVES = ALL_DIRECTIVES - [DEFAULT_SRC, REPORT_URI]
|
|
116
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
|
+
|
|
117
132
|
VARIATIONS = {
|
|
118
133
|
"Chrome" => CHROME_DIRECTIVES,
|
|
119
134
|
"Opera" => CHROME_DIRECTIVES,
|
|
120
135
|
"Firefox" => FIREFOX_DIRECTIVES,
|
|
136
|
+
"FirefoxTransitional" => FIREFOX_46_DIRECTIVES,
|
|
121
137
|
"Safari" => SAFARI_DIRECTIVES,
|
|
122
138
|
"Edge" => EDGE_DIRECTIVES,
|
|
123
139
|
"Other" => CHROME_DIRECTIVES
|
|
@@ -254,8 +270,23 @@ module SecureHeaders
|
|
|
254
270
|
def populate_fetch_source_with_default!(original, additions)
|
|
255
271
|
# in case we would be appending to an empty directive, fill it with the default-src value
|
|
256
272
|
additions.keys.each do |directive|
|
|
257
|
-
|
|
258
|
-
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
|
|
259
290
|
end
|
|
260
291
|
end
|
|
261
292
|
end
|
|
@@ -108,8 +108,6 @@ module SecureHeaders
|
|
|
108
108
|
end
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
end
|
|
115
|
-
end
|
|
111
|
+
ActiveSupport.on_load :action_view do
|
|
112
|
+
include SecureHeaders::ViewHelpers
|
|
113
|
+
end if defined?(ActiveSupport)
|
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
|
data/lib/tasks/tasks.rake
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx
|
|
2
|
-
INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx
|
|
3
|
-
INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx
|
|
4
|
-
INLINE_HASH_STYLE_HELPER_REGEX = /<%=\s?hashed_style_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx
|
|
1
|
+
INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx unless defined? INLINE_SCRIPT_REGEX
|
|
2
|
+
INLINE_STYLE_REGEX = /(<style[^>]*>)(.*?)<\/style>/mx unless defined? INLINE_STYLE_REGEX
|
|
3
|
+
INLINE_HASH_SCRIPT_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx unless defined? INLINE_HASH_SCRIPT_HELPER_REGEX
|
|
4
|
+
INLINE_HASH_STYLE_HELPER_REGEX = /<%=\s?hashed_style_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx unless defined? INLINE_HASH_STYLE_HELPER_REGEX
|
|
5
5
|
|
|
6
6
|
namespace :secure_headers do
|
|
7
7
|
include SecureHeaders::HashHelper
|
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
|
+
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.'
|
|
@@ -24,7 +24,7 @@ module SecureHeaders
|
|
|
24
24
|
|
|
25
25
|
describe "#value" do
|
|
26
26
|
it "discards 'none' values if any other source expressions are present" do
|
|
27
|
-
csp = ContentSecurityPolicy.new(default_opts.merge(
|
|
27
|
+
csp = ContentSecurityPolicy.new(default_opts.merge(child_src: %w('self' 'none')))
|
|
28
28
|
expect(csp.value).not_to include("'none'")
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -86,42 +86,68 @@ module SecureHeaders
|
|
|
86
86
|
expect(csp.value).to eq("default-src example.org")
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
it "emits a warning when using frame-src" do
|
|
90
|
+
expect(Kernel).to receive(:warn).with(/:frame_src is deprecated, use :child_src instead./)
|
|
91
|
+
ContentSecurityPolicy.new(default_src: %w('self'), frame_src: %w('self')).value
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "emits a warning when child-src and frame-src are supplied but are not equal" do
|
|
95
|
+
expect(Kernel).to receive(:warn).with(/both :child_src and :frame_src supplied and do not match./)
|
|
96
|
+
ContentSecurityPolicy.new(default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)).value
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "will still set inconsistent child/frame-src values to be less surprising" do
|
|
100
|
+
expect(Kernel).to receive(:warn).at_least(:once)
|
|
101
|
+
firefox = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox]).value
|
|
102
|
+
firefox_transitional = ContentSecurityPolicy.new({default_src: %w('self'), child_src: %w(child-src.com), frame_src: %w(frame-src,com)}, USER_AGENTS[:firefox46]).value
|
|
103
|
+
expect(firefox).not_to eq(firefox_transitional)
|
|
104
|
+
expect(firefox).to match(/frame-src/)
|
|
105
|
+
expect(firefox).not_to match(/child-src/)
|
|
106
|
+
expect(firefox_transitional).to match(/child-src/)
|
|
107
|
+
expect(firefox_transitional).not_to match(/frame-src/)
|
|
108
|
+
end
|
|
109
|
+
|
|
89
110
|
context "browser sniffing" do
|
|
90
111
|
let (:complex_opts) do
|
|
91
|
-
ContentSecurityPolicy::ALL_DIRECTIVES.each_with_object({}) do |directive, hash|
|
|
92
|
-
hash[directive] =
|
|
112
|
+
(ContentSecurityPolicy::ALL_DIRECTIVES - [:frame_src]).each_with_object({}) do |directive, hash|
|
|
113
|
+
hash[directive] = ["#{directive.to_s.gsub("_", "-")}.com"]
|
|
93
114
|
end.merge({
|
|
94
115
|
block_all_mixed_content: true,
|
|
95
116
|
upgrade_insecure_requests: true,
|
|
96
117
|
reflected_xss: "block",
|
|
97
|
-
script_src: %w(
|
|
118
|
+
script_src: %w(script-src.com),
|
|
98
119
|
script_nonce: 123456
|
|
99
120
|
})
|
|
100
121
|
end
|
|
101
122
|
|
|
102
123
|
it "does not filter any directives for Chrome" do
|
|
103
124
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:chrome])
|
|
104
|
-
expect(policy.value).to eq("default-src
|
|
125
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
105
126
|
end
|
|
106
127
|
|
|
107
128
|
it "does not filter any directives for Opera" do
|
|
108
129
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:opera])
|
|
109
|
-
expect(policy.value).to eq("default-src
|
|
130
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; block-all-mixed-content; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; plugin-types plugin-types.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
110
131
|
end
|
|
111
132
|
|
|
112
133
|
it "filters blocked-all-mixed-content, child-src, and plugin-types for firefox" do
|
|
113
134
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox])
|
|
114
|
-
expect(policy.value).to eq("default-src
|
|
135
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it "filters blocked-all-mixed-content, frame-src, and plugin-types for firefox 46 and higher" do
|
|
139
|
+
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:firefox46])
|
|
140
|
+
expect(policy.value).to eq("default-src default-src.com; base-uri base-uri.com; child-src child-src.com; connect-src connect-src.com; font-src font-src.com; form-action form-action.com; frame-ancestors frame-ancestors.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'nonce-123456'; style-src style-src.com; upgrade-insecure-requests; report-uri report-uri.com")
|
|
115
141
|
end
|
|
116
142
|
|
|
117
|
-
it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for Edge" do
|
|
143
|
+
it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for Edge" do
|
|
118
144
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:edge])
|
|
119
|
-
expect(policy.value).to eq("default-src
|
|
145
|
+
expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
|
|
120
146
|
end
|
|
121
147
|
|
|
122
|
-
it "adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
|
|
148
|
+
it "child-src value is copied to frame-src, adds 'unsafe-inline', filters base-uri, blocked-all-mixed-content, upgrade-insecure-requests, child-src, form-action, frame-ancestors, nonce sources, hash sources, and plugin-types for safari" do
|
|
123
149
|
policy = ContentSecurityPolicy.new(complex_opts, USER_AGENTS[:safari6])
|
|
124
|
-
expect(policy.value).to eq("default-src
|
|
150
|
+
expect(policy.value).to eq("default-src default-src.com; connect-src connect-src.com; font-src font-src.com; frame-src child-src.com; img-src img-src.com; media-src media-src.com; object-src object-src.com; sandbox sandbox.com; script-src script-src.com 'unsafe-inline'; style-src style-src.com; report-uri report-uri.com")
|
|
125
151
|
end
|
|
126
152
|
end
|
|
127
153
|
end
|
|
@@ -23,7 +23,8 @@ module SecureHeaders
|
|
|
23
23
|
# directive values: these values will directly translate into source directives
|
|
24
24
|
default_src: %w(https: 'self'),
|
|
25
25
|
frame_src: %w('self' *.twimg.com itunes.apple.com),
|
|
26
|
-
|
|
26
|
+
child_src: %w('self' *.twimg.com itunes.apple.com),
|
|
27
|
+
connect_src: %w(wss:),
|
|
27
28
|
font_src: %w('self' data:),
|
|
28
29
|
img_src: %w(mycdn.com data:),
|
|
29
30
|
media_src: %w(utoob.com),
|
|
@@ -31,7 +32,6 @@ module SecureHeaders
|
|
|
31
32
|
script_src: %w('self'),
|
|
32
33
|
style_src: %w('unsafe-inline'),
|
|
33
34
|
base_uri: %w('self'),
|
|
34
|
-
child_src: %w('self'),
|
|
35
35
|
form_action: %w('self' github.com),
|
|
36
36
|
frame_ancestors: %w('none'),
|
|
37
37
|
plugin_types: %w(application/x-shockwave-flash),
|
|
@@ -90,8 +90,7 @@ module SecureHeaders
|
|
|
90
90
|
config = Configuration.default do |config|
|
|
91
91
|
config.csp = {
|
|
92
92
|
default_src: %w('self'),
|
|
93
|
-
child_src: %w('self')
|
|
94
|
-
frame_src: %w('self')
|
|
93
|
+
child_src: %w('self')
|
|
95
94
|
}
|
|
96
95
|
end
|
|
97
96
|
firefox_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:firefox]))
|
|
@@ -102,6 +101,8 @@ module SecureHeaders
|
|
|
102
101
|
SecureHeaders.override_content_security_policy_directives(firefox_request, script_src: %w('self'))
|
|
103
102
|
|
|
104
103
|
hash = SecureHeaders.header_hash_for(firefox_request)
|
|
104
|
+
|
|
105
|
+
# child-src is translated to frame-src
|
|
105
106
|
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; frame-src 'self'; script-src 'self'")
|
|
106
107
|
end
|
|
107
108
|
|
|
@@ -137,6 +138,10 @@ module SecureHeaders
|
|
|
137
138
|
end
|
|
138
139
|
|
|
139
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
|
+
|
|
140
145
|
it "appends a value to csp directive" do
|
|
141
146
|
Configuration.default do |config|
|
|
142
147
|
config.csp = {
|
|
@@ -150,6 +155,52 @@ module SecureHeaders
|
|
|
150
155
|
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
|
|
151
156
|
end
|
|
152
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
|
+
|
|
153
204
|
it "dups global configuration just once when overriding n times and only calls idempotent_additions? once" do
|
|
154
205
|
Configuration.default do |config|
|
|
155
206
|
config.csp = {
|
|
@@ -233,7 +284,6 @@ module SecureHeaders
|
|
|
233
284
|
}
|
|
234
285
|
end
|
|
235
286
|
|
|
236
|
-
chrome_request = Rack::Request.new(request.env.merge("HTTP_USER_AGENT" => USER_AGENTS[:chrome]))
|
|
237
287
|
nonce = SecureHeaders.content_security_policy_script_nonce(chrome_request)
|
|
238
288
|
|
|
239
289
|
# simulate the nonce being used multiple times in a request:
|
data/spec/spec_helper.rb
CHANGED
|
@@ -12,7 +12,8 @@ require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
|
|
|
12
12
|
|
|
13
13
|
USER_AGENTS = {
|
|
14
14
|
edge: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246",
|
|
15
|
-
firefox:
|
|
15
|
+
firefox: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1",
|
|
16
|
+
firefox46: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:46.0) Gecko/20100101 Firefox/46.0",
|
|
16
17
|
chrome: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.56 Safari/536.5',
|
|
17
18
|
ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
|
|
18
19
|
opera: 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
|
data/upgrading-to-3-0.md
CHANGED
|
@@ -3,17 +3,19 @@
|
|
|
3
3
|
Changes
|
|
4
4
|
==
|
|
5
5
|
|
|
6
|
-
| What
|
|
7
|
-
|
|
8
|
-
| Global configuration
|
|
9
|
-
| All headers besides HPKP and CSP
|
|
10
|
-
| CSP directive values
|
|
11
|
-
| CSP Nonce values in views
|
|
12
|
-
|
|
|
13
|
-
| `
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
6
|
+
| What | < = 2.x | >= 3.0 |
|
|
7
|
+
| ---------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
+
| Global configuration | `SecureHeaders::Configuration.configure` block | `SecureHeaders::Configuration.default` block |
|
|
9
|
+
| All headers besides HPKP and CSP | Accept hashes as config values | Must be strings (validated during configuration) |
|
|
10
|
+
| CSP directive values | Accepted space delimited strings OR arrays of strings | Must be arrays of strings |
|
|
11
|
+
| CSP Nonce values in views | `@content_security_policy_nonce` | `content_security_policy_nonce(:script)` or `content_security_policy_nonce(:style)` |
|
|
12
|
+
| nonce is no longer a source expression | `config.csp = "'self' 'nonce'"` | Remove `'nonce'` from source expression and use [nonce helpers](https://github.com/twitter/secureheaders#nonce). |
|
|
13
|
+
| `self`/`none` source expressions | Could be `self` / `none` / `'self'` / `'none'` | Must be `'self'` or `'none'` |
|
|
14
|
+
| `inline` / `eval` source expressions | Could be `inline`, `eval`, `'unsafe-inline'`, or `'unsafe-eval'` | Must be `'unsafe-eval'` or `'unsafe-inline'` |
|
|
15
|
+
| Per-action configuration | Override [`def secure_header_options_for(header, options)`](https://github.com/twitter/secureheaders/commit/bb9ebc6c12a677aad29af8e0f08ffd1def56efec#diff-04c6e90faac2675aa89e2176d2eec7d8R111) | Use [named overrides](https://github.com/twitter/secureheaders#named-overrides) or [per-action helpers](https://github.com/twitter/secureheaders#per-action-configuration) |
|
|
16
|
+
| CSP/HPKP use `report_only` config that defaults to false | `enforce: false` | `report_only: false` |
|
|
17
|
+
| Schemes in source expressions | Schemes were not stripped | Schemes are stripped by default to discourage mixed content. Setting `preserve_schemes: true` will revert to previous behavior |
|
|
18
|
+
| Opting out of default configuration | `skip_before_filter :set_x_download_options_header` or `config.x_download_options = false` | Within default block: `config.x_download_options = SecureHeaders::OPT_OUT` |
|
|
17
19
|
|
|
18
20
|
Migrating to 3.x from <= 2.x
|
|
19
21
|
==
|
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
|
+
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
|
|
@@ -45,6 +45,8 @@ executables: []
|
|
|
45
45
|
extensions: []
|
|
46
46
|
extra_rdoc_files: []
|
|
47
47
|
files:
|
|
48
|
+
- ".github/ISSUE_TEMPLATE.md"
|
|
49
|
+
- ".github/PULL_REQUEST_TEMPLATE.md"
|
|
48
50
|
- ".gitignore"
|
|
49
51
|
- ".rspec"
|
|
50
52
|
- ".ruby-gemset"
|