secure_headers 0.4.0 → 0.5.0
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.
- data/HISTORY.md +23 -0
- data/README.md +17 -7
- data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +2 -2
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +11 -6
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +12 -7
- data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +11 -6
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +11 -6
- data/lib/secure_headers/headers/content_security_policy.rb +16 -1
- data/lib/secure_headers/headers/strict_transport_security.rb +2 -2
- data/lib/secure_headers/headers/x_frame_options.rb +3 -3
- data/lib/secure_headers/headers/x_xss_protection.rb +1 -2
- data/lib/secure_headers/version.rb +1 -1
- data/lib/secure_headers.rb +4 -5
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +32 -0
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +17 -20
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +1 -1
- data/spec/lib/secure_headers_spec.rb +58 -39
- metadata +3 -3
data/HISTORY.md
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
+
0.5.0
|
|
2
|
+
======
|
|
3
|
+
|
|
4
|
+
X-Content-Type-Options also applied to Chrome requests
|
|
5
|
+
|
|
6
|
+
0.4.3
|
|
7
|
+
======
|
|
8
|
+
|
|
9
|
+
Safari 5 is just completely broken when CSP is used, both mobile and desktop versions
|
|
10
|
+
|
|
11
|
+
0.4.2
|
|
12
|
+
======
|
|
13
|
+
|
|
14
|
+
- Stupid bug where Fixnums couldn't be used for config values
|
|
15
|
+
- Doc updates
|
|
16
|
+
|
|
17
|
+
0.4.1
|
|
18
|
+
======
|
|
19
|
+
|
|
20
|
+
- Allow strings or ints in the HSTS max-age (@reedloden)
|
|
21
|
+
|
|
1
22
|
0.4.0
|
|
2
23
|
=======
|
|
24
|
+
|
|
3
25
|
- Treat each header as it's own before_filter. This allows you to `skip_before_filter :set_X_header, :only => :bad_idea
|
|
4
26
|
- Should be backwards compatible, but it is a change to the API.
|
|
5
27
|
|
|
6
28
|
0.3.0
|
|
7
29
|
=======
|
|
30
|
+
|
|
8
31
|
- Greatly reduce the need to use the forward_endpoint attribute. If you are posting from your site to a host that matches TLD+1 (e.g. translate.twitter.com matches twitter.com), use a protocol relative value for report-uri. This will alleviate the need to use forwarding. If your host doesn't match, you still need to use forwarding due to host mismatches for Firefox.
|
|
9
32
|
|
|
10
33
|
0.2.3
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The gem will automatically apply several headers that are related to security. This includes:
|
|
4
4
|
- Content Security Policy (CSP) - Helps detect/prevent XSS, mixed-content, and other classes of attack. [CSP 1.1 Specification](https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html)
|
|
5
5
|
- 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)
|
|
6
|
-
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options draft](https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-
|
|
6
|
+
- X-Frame-Options (XFO) - Prevents your content from being framed and potentially clickjacked. [X-Frame-Options draft](https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02)
|
|
7
7
|
- X-XSS-Protection - [Cross site scripting heuristic filter for IE/Chrome](http://msdn.microsoft.com/en-us/library/dd565647\(v=vs.85\).aspx)
|
|
8
8
|
- X-Content-Type-Options - [Prevent content type sniffing](http://msdn.microsoft.com/en-us/library/ie/gg622941\(v=vs.85\).aspx)
|
|
9
9
|
|
|
@@ -41,7 +41,7 @@ By default, it will set all of the headers listed in the options section below u
|
|
|
41
41
|
|
|
42
42
|
Use the standard `skip_before_filter :filter_name, options` mechanism. e.g. `skip_before_filter :set_csp_header, :only => :tinymce_page`
|
|
43
43
|
|
|
44
|
-
The following methods are going to be called,
|
|
44
|
+
The following methods are going to be called, unless they are provided in a `skip_before_filter` block.
|
|
45
45
|
|
|
46
46
|
* `:set_csp_header`
|
|
47
47
|
* `:set_hsts_header`
|
|
@@ -53,10 +53,10 @@ The following methods are going to be called, unles they are provided in a `skip
|
|
|
53
53
|
|
|
54
54
|
This gem makes a few assumptions about how you will use some features. For example:
|
|
55
55
|
|
|
56
|
-
* It adds 'chrome-extension:' to your CSP directives by default. This helps drastically reduce the amount of reports, but you can also disable this feature by supplying
|
|
57
|
-
* It fills any blank directives with the value in
|
|
56
|
+
* It adds 'chrome-extension:' to your CSP directives by default. This helps drastically reduce the amount of reports, but you can also disable this feature by supplying `:disable_chrome_extension => true`.
|
|
57
|
+
* It fills any blank directives with the value in `:default_src` Getting a default\-src report is pretty useless. This way, you will always know what type of violation occurred. You can disable this feature by supplying `:disable_fill_missing => true`.
|
|
58
58
|
* It copies the connect\-src value to xhr\-src for AJAX requests when using Firefox.
|
|
59
|
-
* Firefox does not support cross\-origin CSP reports. If we are using Firefox, AND the value for
|
|
59
|
+
* Firefox does not support cross\-origin CSP reports. If we are using Firefox, AND the value for `:report_uri` does not satisfy the same\-origin requirements, we will instead forward to an internal endpoint (`FF_CSP_ENDPOINT`). This is also the case if `:report_uri` only contains a path, which we assume will be cross host. This endpoint will in turn forward the request to the value in `:forward_endpoint` without restriction. More information can be found in the "Note on Firefox handling of CSP" section.
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
## Configuration
|
|
@@ -77,7 +77,7 @@ This gem makes a few assumptions about how you will use some features. For exam
|
|
|
77
77
|
}
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
# and then simply include
|
|
80
|
+
# and then simply include this in application_controller
|
|
81
81
|
ensure_security_headers
|
|
82
82
|
```
|
|
83
83
|
|
|
@@ -101,7 +101,7 @@ header will be constructed using the supplied options.
|
|
|
101
101
|
### Widely supported
|
|
102
102
|
|
|
103
103
|
```ruby
|
|
104
|
-
:hsts => {:max_age => 631138519, :
|
|
104
|
+
:hsts => {:max_age => 631138519, :include_subdomains => true}
|
|
105
105
|
:x_frame_options => {:value => 'SAMEORIGIN'}
|
|
106
106
|
:x_xss_protection => {:value => 1, :mode => false} # set the :mode option to 'block' to enforce the browser's xss filter
|
|
107
107
|
```
|
|
@@ -159,6 +159,16 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
|
|
|
159
159
|
:img_src => 'http://mycdn.example.com'
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
|
|
163
|
+
# script-nonce is an experimental feature of CSP 1.1 available in Chrome. It allows
|
|
164
|
+
# you to whitelist inline script blocks. For more information, see
|
|
165
|
+
# https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#script-nonce
|
|
166
|
+
:script_nonce => { 'abc123' }
|
|
167
|
+
|
|
168
|
+
# you can also use lambdas to use dynamically generated nonces
|
|
169
|
+
:script_nonce => lambda { @script_nonce] = 'something' }
|
|
170
|
+
# which can be used to whitelist a script block:
|
|
171
|
+
# script_tag :nonce = @script_nonce { inline_script_call() }
|
|
162
172
|
}
|
|
163
173
|
```
|
|
164
174
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
config.hsts = { :max_age => 10.years.to_i.to_s, :include_subdomains => false }
|
|
3
3
|
config.x_frame_options = 'SAMEORIGIN'
|
|
4
4
|
config.x_content_type_options = "nosniff"
|
|
5
|
-
config.x_xss_protection = {:value => 1, :mode => '
|
|
5
|
+
config.x_xss_protection = {:value => 1, :mode => 'block'}
|
|
6
6
|
csp = {
|
|
7
7
|
:default_src => "self",
|
|
8
8
|
:disable_chrome_extension => true,
|
|
@@ -12,4 +12,4 @@
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
config.csp = csp
|
|
15
|
-
end
|
|
15
|
+
end
|
|
@@ -7,14 +7,14 @@ describe OtherThingsController do
|
|
|
7
7
|
request.env['HTTP_USER_AGENT'] = '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'
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
it "sets the X-XSS-
|
|
10
|
+
it "sets the X-XSS-Protection header" do
|
|
11
11
|
get :index
|
|
12
|
-
response.headers['X-XSS-Protection'].should == '1; mode=
|
|
12
|
+
response.headers['X-XSS-Protection'].should == '1; mode=block'
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
it "sets the X-
|
|
15
|
+
it "sets the X-Frame-Options header" do
|
|
16
16
|
get :index
|
|
17
|
-
response.headers['X-
|
|
17
|
+
response.headers['X-Frame-Options'].should == 'SAMEORIGIN'
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
it "sets the X-WebKit-CSP header" do
|
|
@@ -23,14 +23,19 @@ describe OtherThingsController do
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
#mock ssl
|
|
26
|
-
it "sets the
|
|
26
|
+
it "sets the Strict-Transport-Security header" do
|
|
27
27
|
request.env['HTTPS'] = 'on'
|
|
28
28
|
get :index
|
|
29
29
|
response.headers['Strict-Transport-Security'].should == "max-age=315576000"
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
it "sets the X-Content-Type-Options header" do
|
|
33
|
+
get :index
|
|
34
|
+
response.headers['X-Content-Type-Options'].should == "nosniff"
|
|
35
|
+
end
|
|
36
|
+
|
|
32
37
|
context "using IE" do
|
|
33
|
-
it "sets the X-
|
|
38
|
+
it "sets the X-Content-Type-Options header" do
|
|
34
39
|
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
|
35
40
|
get :index
|
|
36
41
|
response.headers['X-Content-Type-Options'].should == "nosniff"
|
|
@@ -11,14 +11,14 @@ describe ThingsController do
|
|
|
11
11
|
request.env['HTTP_USER_AGENT'] = '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'
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
it "sets the X-XSS-
|
|
14
|
+
it "sets the X-XSS-Protection header" do
|
|
15
15
|
get :index
|
|
16
|
-
response.headers['X-XSS-Protection'].should == '1; mode=
|
|
16
|
+
response.headers['X-XSS-Protection'].should == '1; mode=block'
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
it "sets the X-
|
|
19
|
+
it "sets the X-Frame-Options header" do
|
|
20
20
|
get :index
|
|
21
|
-
response.headers['X-
|
|
21
|
+
response.headers['X-Frame-Options'].should == 'SAMEORIGIN'
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
it "sets the X-WebKit-CSP header" do
|
|
@@ -27,14 +27,19 @@ describe ThingsController do
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
#mock ssl
|
|
30
|
-
it "sets the
|
|
30
|
+
it "sets the Strict-Transport-Security header" do
|
|
31
31
|
request.env['HTTPS'] = 'on'
|
|
32
32
|
get :index
|
|
33
33
|
response.headers['Strict-Transport-Security'].should == "max-age=315576000"
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
it "sets the X-Content-Type-Options header" do
|
|
37
|
+
get :index
|
|
38
|
+
response.headers['X-Content-Type-Options'].should == "nosniff"
|
|
39
|
+
end
|
|
40
|
+
|
|
36
41
|
context "using IE" do
|
|
37
|
-
it "sets the X-
|
|
42
|
+
it "sets the X-Content-Type-Options header" do
|
|
38
43
|
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
|
39
44
|
get :index
|
|
40
45
|
response.headers['X-Content-Type-Options'].should == "nosniff"
|
|
@@ -44,4 +49,4 @@ describe ThingsController do
|
|
|
44
49
|
end
|
|
45
50
|
|
|
46
51
|
|
|
47
|
-
# response.headers['X-WebKit-CSP-Report-Only'].should == "default-src 'self'; report-uri somewhere"
|
|
52
|
+
# response.headers['X-WebKit-CSP-Report-Only'].should == "default-src 'self'; report-uri somewhere"
|
|
@@ -7,14 +7,14 @@ describe OtherThingsController do
|
|
|
7
7
|
request.env['HTTP_USER_AGENT'] = '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'
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
it "sets the X-XSS-
|
|
10
|
+
it "sets the X-XSS-Protection header" do
|
|
11
11
|
get :index
|
|
12
12
|
response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
it "sets the X-
|
|
15
|
+
it "sets the X-Frame-Options header" do
|
|
16
16
|
get :index
|
|
17
|
-
response.headers['X-
|
|
17
|
+
response.headers['X-Frame-Options'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
it "sets the X-WebKit-CSP header" do
|
|
@@ -23,17 +23,22 @@ describe OtherThingsController do
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
#mock ssl
|
|
26
|
-
it "sets the
|
|
26
|
+
it "sets the Strict-Transport-Security header" do
|
|
27
27
|
request.env['HTTPS'] = 'on'
|
|
28
28
|
get :index
|
|
29
29
|
response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
it "sets the X-Content-Type-Options header" do
|
|
33
|
+
get :index
|
|
34
|
+
response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
|
|
35
|
+
end
|
|
36
|
+
|
|
32
37
|
context "using IE" do
|
|
33
|
-
it "sets the X-
|
|
38
|
+
it "sets the X-Content-Type-Options header" do
|
|
34
39
|
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
|
35
40
|
get :index
|
|
36
|
-
response.headers['X-Content-Type-Options'].should ==
|
|
41
|
+
response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
|
|
37
42
|
end
|
|
38
43
|
end
|
|
39
44
|
end
|
|
@@ -11,14 +11,14 @@ describe ThingsController do
|
|
|
11
11
|
request.env['HTTP_USER_AGENT'] = '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'
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
it "sets the X-XSS-
|
|
14
|
+
it "sets the X-XSS-Protection header" do
|
|
15
15
|
get :index
|
|
16
16
|
response.headers['X-XSS-Protection'].should == SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
it "sets the X-
|
|
19
|
+
it "sets the X-Frame-Options header" do
|
|
20
20
|
get :index
|
|
21
|
-
response.headers['X-
|
|
21
|
+
response.headers['X-Frame-Options'].should == SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
it "sets the X-WebKit-CSP header" do
|
|
@@ -27,17 +27,22 @@ describe ThingsController do
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
#mock ssl
|
|
30
|
-
it "sets the
|
|
30
|
+
it "sets the Strict-Transport-Security header" do
|
|
31
31
|
request.env['HTTPS'] = 'on'
|
|
32
32
|
get :index
|
|
33
33
|
response.headers['Strict-Transport-Security'].should == SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
it "sets the X-Content-Type-Options header" do
|
|
37
|
+
get :index
|
|
38
|
+
response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
|
|
39
|
+
end
|
|
40
|
+
|
|
36
41
|
context "using IE" do
|
|
37
|
-
it "sets the X-
|
|
42
|
+
it "sets the X-Content-Type-Options header" do
|
|
38
43
|
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.0"
|
|
39
44
|
get :index
|
|
40
|
-
response.headers['X-Content-Type-Options'].should ==
|
|
45
|
+
response.headers['X-Content-Type-Options'].should == SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE
|
|
41
46
|
end
|
|
42
47
|
end
|
|
43
48
|
end
|
|
@@ -36,6 +36,7 @@ module SecureHeaders
|
|
|
36
36
|
# :report used to determine what :ssl_request, :ua, and :request_uri are set to
|
|
37
37
|
def initialize(config=nil, options={})
|
|
38
38
|
@experimental = !!options.delete(:experimental)
|
|
39
|
+
@controller = options.delete(:controller)
|
|
39
40
|
if options[:request]
|
|
40
41
|
parse_request(options[:request])
|
|
41
42
|
else
|
|
@@ -65,6 +66,7 @@ module SecureHeaders
|
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
@report_uri = @config.delete(:report_uri)
|
|
69
|
+
@script_nonce = @config.delete(:script_nonce)
|
|
68
70
|
|
|
69
71
|
normalize_csp_options
|
|
70
72
|
normalize_reporting_endpoint
|
|
@@ -103,7 +105,8 @@ module SecureHeaders
|
|
|
103
105
|
header_value = [
|
|
104
106
|
build_impl_specific_directives,
|
|
105
107
|
generic_directives(@config),
|
|
106
|
-
report_uri_directive
|
|
108
|
+
report_uri_directive,
|
|
109
|
+
script_nonce_directive,
|
|
107
110
|
].join
|
|
108
111
|
|
|
109
112
|
#store the value for next time
|
|
@@ -208,6 +211,18 @@ module SecureHeaders
|
|
|
208
211
|
"report-uri #{@report_uri};"
|
|
209
212
|
end
|
|
210
213
|
|
|
214
|
+
def script_nonce_directive
|
|
215
|
+
return '' if @script_nonce.nil?
|
|
216
|
+
nonce_value = if @script_nonce.is_a?(String)
|
|
217
|
+
@script_nonce
|
|
218
|
+
elsif @controller
|
|
219
|
+
@controller.instance_exec(&@script_nonce)
|
|
220
|
+
else
|
|
221
|
+
@script_nonce.call
|
|
222
|
+
end
|
|
223
|
+
"script-nonce #{nonce_value};"
|
|
224
|
+
end
|
|
225
|
+
|
|
211
226
|
def generic_directives(config)
|
|
212
227
|
header_value = ''
|
|
213
228
|
if config[:img_src]
|
|
@@ -29,7 +29,7 @@ module SecureHeaders
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
max_age = @config.fetch(:max_age, HSTS_MAX_AGE)
|
|
32
|
-
value = "max-age=" + max_age
|
|
32
|
+
value = "max-age=" + max_age.to_s
|
|
33
33
|
value += "; includeSubdomains" if @config[:include_subdomains]
|
|
34
34
|
|
|
35
35
|
value
|
|
@@ -41,7 +41,7 @@ module SecureHeaders
|
|
|
41
41
|
if @config.is_a? Hash
|
|
42
42
|
if !@config[:max_age]
|
|
43
43
|
raise STSBuildError.new("No max-age was supplied.")
|
|
44
|
-
elsif @config[:max_age] !~ /\A\d+\z/
|
|
44
|
+
elsif @config[:max_age].to_s !~ /\A\d+\z/
|
|
45
45
|
raise STSBuildError.new("max-age must be a number. #{@config[:max_age]} was supplied.")
|
|
46
46
|
end
|
|
47
47
|
else
|
|
@@ -2,9 +2,9 @@ module SecureHeaders
|
|
|
2
2
|
class XFOBuildError < StandardError; end
|
|
3
3
|
class XFrameOptions
|
|
4
4
|
module Constants
|
|
5
|
-
XFO_HEADER_NAME = "X-
|
|
5
|
+
XFO_HEADER_NAME = "X-Frame-Options"
|
|
6
6
|
DEFAULT_VALUE = 'SAMEORIGIN'
|
|
7
|
-
VALID_XFO_HEADER = /\A(SAMEORIGIN\z|DENY\z|ALLOW-FROM
|
|
7
|
+
VALID_XFO_HEADER = /\A(SAMEORIGIN\z|DENY\z|ALLOW-FROM[:\s])/i
|
|
8
8
|
end
|
|
9
9
|
include Constants
|
|
10
10
|
|
|
@@ -37,4 +37,4 @@ module SecureHeaders
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
|
-
end
|
|
40
|
+
end
|
data/lib/secure_headers.rb
CHANGED
|
@@ -72,10 +72,10 @@ module SecureHeaders
|
|
|
72
72
|
options = self.class.options_for :csp, options
|
|
73
73
|
return if options == false
|
|
74
74
|
|
|
75
|
-
header = ContentSecurityPolicy.new(options, :request => request)
|
|
75
|
+
header = ContentSecurityPolicy.new(options, :request => request, :controller => self)
|
|
76
76
|
set_header(header.name, header.value)
|
|
77
77
|
if options && options[:experimental] && options[:enforce]
|
|
78
|
-
header = ContentSecurityPolicy.new(options, :experimental => true, :request => request)
|
|
78
|
+
header = ContentSecurityPolicy.new(options, :experimental => true, :request => request, :controller => self)
|
|
79
79
|
set_header(header.name, header.value)
|
|
80
80
|
end
|
|
81
81
|
end
|
|
@@ -85,7 +85,7 @@ module SecureHeaders
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
def set_x_content_type_options_header(options=self.class.secure_headers_options[:x_content_type_options])
|
|
88
|
-
return unless brwsr.ie?
|
|
88
|
+
return unless brwsr.ie? || brwsr.chrome?
|
|
89
89
|
set_a_header(:x_content_type_options, XContentTypeOptions, options)
|
|
90
90
|
end
|
|
91
91
|
|
|
@@ -113,8 +113,7 @@ module SecureHeaders
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def broken_implementation?(browser)
|
|
116
|
-
|
|
117
|
-
return browser.ios5?
|
|
116
|
+
return browser.ios5? || (browser.safari? && browser.version == 5)
|
|
118
117
|
end
|
|
119
118
|
end
|
|
120
119
|
end
|
|
@@ -397,6 +397,38 @@ module SecureHeaders
|
|
|
397
397
|
end
|
|
398
398
|
end
|
|
399
399
|
end
|
|
400
|
+
|
|
401
|
+
context "when supplying a script nonce callback" do
|
|
402
|
+
let(:options) {
|
|
403
|
+
default_opts.merge({
|
|
404
|
+
:script_nonce => "random",
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
it "uses the value in the X-Webkit-CSP" do
|
|
409
|
+
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
410
|
+
csp.value.should match "script-nonce random;"
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
it "uses the value in the X-Content-Security-Policy" do
|
|
414
|
+
csp = ContentSecurityPolicy.new(options, :request => request_for(FIREFOX))
|
|
415
|
+
csp.value.should match "script-nonce random;"
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
it "runs a dynamic nonce generator" do
|
|
419
|
+
options[:script_nonce] = lambda { 'something' }
|
|
420
|
+
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
|
421
|
+
csp.value.should match "script-nonce something;"
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
it "runs against the given controller context" do
|
|
425
|
+
fake_params = {}
|
|
426
|
+
options[:script_nonce] = lambda { params[:script_nonce] = 'something' }
|
|
427
|
+
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME), :controller => double(:params => fake_params))
|
|
428
|
+
csp.value.should match "script-nonce something;"
|
|
429
|
+
fake_params.should == {:script_nonce => 'something'}
|
|
430
|
+
end
|
|
431
|
+
end
|
|
400
432
|
end
|
|
401
433
|
end
|
|
402
434
|
end
|
|
@@ -5,29 +5,26 @@ module SecureHeaders
|
|
|
5
5
|
specify{ StrictTransportSecurity.new.name.should == "Strict-Transport-Security" }
|
|
6
6
|
|
|
7
7
|
describe "#value" do
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
it "allows you to specify includeSubdomains" do
|
|
14
|
-
s = StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
|
|
15
|
-
s.value.should == "max-age=#{HSTS_MAX_AGE}; includeSubdomains"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it "accepts a string value and returns verbatim" do
|
|
19
|
-
s = StrictTransportSecurity.new('max-age=1234')
|
|
20
|
-
s.value.should == "max-age=1234"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
it "allows you to specify max-age" do
|
|
24
|
-
age = '8675309'
|
|
25
|
-
s = StrictTransportSecurity.new(:max_age => age)
|
|
26
|
-
s.value.should == "max-age=#{age}"
|
|
27
|
-
end
|
|
8
|
+
specify { StrictTransportSecurity.new.value.should == StrictTransportSecurity::Constants::DEFAULT_VALUE}
|
|
9
|
+
specify { StrictTransportSecurity.new("max-age=1234").value.should == "max-age=1234"}
|
|
10
|
+
specify { StrictTransportSecurity.new(:max_age => '1234').value.should == "max-age=1234"}
|
|
11
|
+
specify { StrictTransportSecurity.new(:max_age => 1234).value.should == "max-age=1234"}
|
|
12
|
+
specify { StrictTransportSecurity.new(:max_age => HSTS_MAX_AGE, :include_subdomains => true).value.should == "max-age=#{HSTS_MAX_AGE}; includeSubdomains"}
|
|
28
13
|
|
|
29
14
|
context "with an invalid configuration" do
|
|
30
15
|
context "with a hash argument" do
|
|
16
|
+
it "should allow string values for max-age" do
|
|
17
|
+
lambda {
|
|
18
|
+
StrictTransportSecurity.new(:max_age => '1234')
|
|
19
|
+
}.should_not raise_error
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "should allow integer values for max-age" do
|
|
23
|
+
lambda {
|
|
24
|
+
StrictTransportSecurity.new(:max_age => 1234)
|
|
25
|
+
}.should_not raise_error
|
|
26
|
+
end
|
|
27
|
+
|
|
31
28
|
it "raises an exception with an invalid max-age" do
|
|
32
29
|
lambda {
|
|
33
30
|
StrictTransportSecurity.new(:max_age => 'abc123')
|
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
module SecureHeaders
|
|
4
4
|
describe XFrameOptions do
|
|
5
|
-
specify{ XFrameOptions.new.name.should == "X-
|
|
5
|
+
specify{ XFrameOptions.new.name.should == "X-Frame-Options" }
|
|
6
6
|
|
|
7
7
|
describe "#value" do
|
|
8
8
|
specify { XFrameOptions.new.value.should == XFrameOptions::Constants::DEFAULT_VALUE}
|
|
@@ -24,7 +24,10 @@ describe SecureHeaders do
|
|
|
24
24
|
: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',
|
|
25
25
|
:ie => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',
|
|
26
26
|
:opera => 'Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00',
|
|
27
|
-
:ios5 => "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"
|
|
27
|
+
:ios5 => "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
|
|
28
|
+
:ios6 => "Mozilla/5.0 (iPhone; CPU iPhone OS 614 like Mac OS X) AppleWebKit/536.26 (KHTML like Gecko) Version/6.0 Mobile/10B350 Safari/8536.25",
|
|
29
|
+
:safari5 => "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3",
|
|
30
|
+
:safari6 => "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1"
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
def should_assign_header name, value
|
|
@@ -77,12 +80,10 @@ describe SecureHeaders do
|
|
|
77
80
|
it "sets all default headers for #{name} (smoke test)" do
|
|
78
81
|
stub_user_agent(useragent)
|
|
79
82
|
number_of_headers = case name
|
|
80
|
-
when :ie
|
|
83
|
+
when :ie, :chrome
|
|
81
84
|
5
|
|
82
|
-
when :
|
|
83
|
-
|
|
84
|
-
when :ios5
|
|
85
|
-
3 # csp is disabled for ios5
|
|
85
|
+
when :ios5, :safari5
|
|
86
|
+
3 # csp breaks these browsers
|
|
86
87
|
else
|
|
87
88
|
4
|
|
88
89
|
end
|
|
@@ -96,29 +97,28 @@ describe SecureHeaders do
|
|
|
96
97
|
end
|
|
97
98
|
end
|
|
98
99
|
|
|
99
|
-
it "does not set the X-Content-Type-Options
|
|
100
|
+
it "does not set the X-Content-Type-Options header if disabled" do
|
|
100
101
|
stub_user_agent(USER_AGENTS[:ie])
|
|
101
102
|
should_not_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME)
|
|
102
103
|
subject.set_x_content_type_options_header(false)
|
|
103
104
|
end
|
|
104
105
|
|
|
105
|
-
it "does not set the X-XSS-
|
|
106
|
-
stub_user_agent(USER_AGENTS[:ie])
|
|
106
|
+
it "does not set the X-XSS-Protection header if disabled" do
|
|
107
107
|
should_not_assign_header(X_XSS_PROTECTION_HEADER_NAME)
|
|
108
108
|
subject.set_x_xss_protection_header(false)
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
it "does not set the X-
|
|
111
|
+
it "does not set the X-Frame-Options header if disabled" do
|
|
112
112
|
should_not_assign_header(XFO_HEADER_NAME)
|
|
113
113
|
subject.set_x_frame_options_header(false)
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
-
it "does not set the
|
|
116
|
+
it "does not set the HSTS header if disabled" do
|
|
117
117
|
should_not_assign_header(HSTS_HEADER_NAME)
|
|
118
118
|
subject.set_hsts_header(false)
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
it "does not set the
|
|
121
|
+
it "does not set the HSTS header if request is over HTTP" do
|
|
122
122
|
subject.stub_chain(:request, :ssl?).and_return(false)
|
|
123
123
|
should_not_assign_header(HSTS_HEADER_NAME)
|
|
124
124
|
subject.set_hsts_header({:include_subdomains => true})
|
|
@@ -139,7 +139,7 @@ describe SecureHeaders do
|
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
context "when disabled by configuration settings" do
|
|
142
|
-
it "does not set
|
|
142
|
+
it "does not set any headers when disabled" do
|
|
143
143
|
::SecureHeaders::Configuration.configure do |config|
|
|
144
144
|
config.hsts = false
|
|
145
145
|
config.x_frame_options = false
|
|
@@ -155,48 +155,67 @@ describe SecureHeaders do
|
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
describe "#set_x_frame_options_header" do
|
|
158
|
-
it "sets the X-
|
|
158
|
+
it "sets the X-Frame-Options header" do
|
|
159
159
|
should_assign_header(XFO_HEADER_NAME, SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
|
|
160
160
|
subject.set_x_frame_options_header
|
|
161
161
|
end
|
|
162
162
|
|
|
163
|
-
it "allows a custom X-
|
|
163
|
+
it "allows a custom X-Frame-Options header" do
|
|
164
164
|
should_assign_header(XFO_HEADER_NAME, "DENY")
|
|
165
165
|
subject.set_x_frame_options_header(:value => 'DENY')
|
|
166
166
|
end
|
|
167
167
|
end
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
describe "#set_strict_transport_security" do
|
|
170
|
+
it "sets the Strict-Transport-Security header" do
|
|
171
|
+
should_assign_header(HSTS_HEADER_NAME, SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
|
|
172
|
+
subject.set_hsts_header
|
|
172
173
|
end
|
|
173
174
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
end
|
|
175
|
+
it "allows you to specific a custom max-age value" do
|
|
176
|
+
should_assign_header(HSTS_HEADER_NAME, 'max-age=1234')
|
|
177
|
+
subject.set_hsts_header(:max_age => 1234)
|
|
178
|
+
end
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
it "allows you to specify includeSubdomains" do
|
|
181
|
+
should_assign_header(HSTS_HEADER_NAME, "max-age=#{HSTS_MAX_AGE}; includeSubdomains")
|
|
182
|
+
subject.set_hsts_header(:max_age => HSTS_MAX_AGE, :include_subdomains => true)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
184
185
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
describe "#set_x_xss_protection" do
|
|
187
|
+
it "sets the X-XSS-Protection header" do
|
|
188
|
+
should_assign_header(X_XSS_PROTECTION_HEADER_NAME, SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
|
|
189
|
+
subject.set_x_xss_protection_header
|
|
189
190
|
end
|
|
190
191
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
it "sets a custom X-XSS-Protection header" do
|
|
193
|
+
should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '0')
|
|
194
|
+
subject.set_x_xss_protection_header("0")
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "sets the block flag" do
|
|
198
|
+
should_assign_header(X_XSS_PROTECTION_HEADER_NAME, '1; mode=block')
|
|
199
|
+
subject.set_x_xss_protection_header(:mode => 'block', :value => 1)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
196
202
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
203
|
+
describe "#set_x_content_type_options" do
|
|
204
|
+
[:ie, :chrome].each do |useragent|
|
|
205
|
+
context "when using #{useragent}" do
|
|
206
|
+
before(:each) do
|
|
207
|
+
stub_user_agent(USER_AGENTS[useragent])
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it "sets the X-Content-Type-Options header" do
|
|
211
|
+
should_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME, SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
|
|
212
|
+
subject.set_x_content_type_options_header
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it "lets you override X-Content-Type-Options" do
|
|
216
|
+
should_assign_header(X_CONTENT_TYPE_OPTIONS_HEADER_NAME, 'nosniff')
|
|
217
|
+
subject.set_x_content_type_options_header(:value => 'nosniff')
|
|
218
|
+
end
|
|
200
219
|
end
|
|
201
220
|
end
|
|
202
221
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: secure_headers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2013-
|
|
12
|
+
date: 2013-05-21 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: brwsr
|
|
@@ -190,7 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
190
190
|
version: '0'
|
|
191
191
|
requirements: []
|
|
192
192
|
rubyforge_project:
|
|
193
|
-
rubygems_version: 1.8.
|
|
193
|
+
rubygems_version: 1.8.25
|
|
194
194
|
signing_key:
|
|
195
195
|
specification_version: 3
|
|
196
196
|
summary: Add easily configured browser headers to responses including content security
|