secure_headers 1.0.0 → 1.1.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.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- data/Gemfile +5 -2
- data/HISTORY.md +8 -0
- data/README.md +33 -42
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +1 -1
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +1 -4
- data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +1 -1
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +1 -1
- data/lib/secure_headers.rb +8 -16
- data/lib/secure_headers/headers/content_security_policy.rb +32 -58
- data/lib/secure_headers/version.rb +1 -1
- data/secure_headers.gemspec +0 -1
- data/spec/controllers/content_security_policy_controller_spec.rb +1 -1
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +16 -95
- data/spec/lib/secure_headers_spec.rb +10 -26
- metadata +2 -22
- data/lib/secure_headers/headers/content_security_policy/browser_strategy.rb +0 -78
- data/lib/secure_headers/headers/content_security_policy/firefox_browser_strategy.rb +0 -72
- data/lib/secure_headers/headers/content_security_policy/ie_browser_strategy.rb +0 -6
- data/lib/secure_headers/headers/content_security_policy/standard_browser_strategy.rb +0 -22
data/Gemfile
CHANGED
@@ -7,8 +7,11 @@ group :test do
|
|
7
7
|
gem 'sqlite3', :platform => [:ruby, :mswin, :mingw]
|
8
8
|
gem 'jdbc-sqlite3', :platform => :jruby
|
9
9
|
gem 'rspec-rails'
|
10
|
-
gem '
|
11
|
-
gem '
|
10
|
+
gem 'spork'
|
11
|
+
gem 'pry'
|
12
|
+
gem 'rspec'
|
13
|
+
gem 'guard-spork', :platform => :ruby_19
|
14
|
+
gem 'guard-rspec', :platform => :ruby_19
|
12
15
|
gem 'growl'
|
13
16
|
gem 'rb-fsevent'
|
14
17
|
gem 'simplecov'
|
data/HISTORY.md
CHANGED
data/README.md
CHANGED
@@ -49,13 +49,11 @@ The following methods are going to be called, unless they are provided in a `ski
|
|
49
49
|
* `:set_x_xss_protection_header`
|
50
50
|
* `:set_x_content_type_options_header`
|
51
51
|
|
52
|
-
###
|
52
|
+
### Bonus Features
|
53
53
|
|
54
54
|
This gem makes a few assumptions about how you will use some features. For example:
|
55
55
|
|
56
|
-
* It
|
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
|
-
* It copies the connect\-src value to xhr\-src for AJAX requests when using Firefox.
|
56
|
+
* 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`. This is referred to as the "effective-directive" in the spec, but is not well supported as of Nov 5, 2013.
|
59
57
|
* 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
58
|
|
61
59
|
|
@@ -65,29 +63,31 @@ This gem makes a few assumptions about how you will use some features. For exam
|
|
65
63
|
|
66
64
|
```ruby
|
67
65
|
::SecureHeaders::Configuration.configure do |config|
|
68
|
-
config.hsts = {:max_age =>
|
66
|
+
config.hsts = {:max_age => 20.years.to_i, :include_subdomains => true}
|
69
67
|
config.x_frame_options = 'DENY'
|
70
68
|
config.x_content_type_options = "nosniff"
|
71
|
-
config.x_xss_protection = {:value => 1, :mode =>
|
69
|
+
config.x_xss_protection = {:value => 1, :mode => 'block'}
|
72
70
|
config.csp = {
|
73
|
-
:default_src => "https://*
|
74
|
-
:
|
75
|
-
:img_src => "https://*
|
76
|
-
:
|
71
|
+
:default_src => "https://* self",
|
72
|
+
:frame_src => "https://* http://*.twimg.com http://itunes.apple.com",
|
73
|
+
:img_src => "https://*",
|
74
|
+
:report_uri => '//example.com/uri-directive'
|
77
75
|
}
|
78
76
|
end
|
79
77
|
|
80
|
-
# and then simply include this in application_controller
|
81
|
-
|
78
|
+
# and then simply include this in application_controller.rb
|
79
|
+
class ApplicationController < ActionController::Base
|
80
|
+
ensure_security_headers
|
81
|
+
end
|
82
82
|
```
|
83
83
|
|
84
|
-
Or simply add it to application controller
|
84
|
+
Or simply add it to application controller
|
85
85
|
|
86
86
|
```ruby
|
87
|
-
ensure_security_headers
|
87
|
+
ensure_security_headers(
|
88
88
|
:hsts => {:include_subdomains, :x_frame_options => false},
|
89
89
|
:x_frame_options => 'DENY',
|
90
|
-
:csp => false
|
90
|
+
:csp => false)
|
91
91
|
```
|
92
92
|
|
93
93
|
## Options for ensure\_security\_headers
|
@@ -98,12 +98,15 @@ Each header configuration can take a hash, or a string, or both. If a string
|
|
98
98
|
is provided, that value is inserted verbatim. If a hash is supplied, a
|
99
99
|
header will be constructed using the supplied options.
|
100
100
|
|
101
|
-
###
|
101
|
+
### The Easy Headers
|
102
|
+
|
103
|
+
This configuration will likely work for most applications without modification.
|
102
104
|
|
103
105
|
```ruby
|
104
|
-
:hsts => {:max_age => 631138519, :include_subdomains =>
|
106
|
+
:hsts => {:max_age => 631138519, :include_subdomains => false}
|
105
107
|
:x_frame_options => {:value => 'SAMEORIGIN'}
|
106
|
-
:x_xss_protection => {:value => 1, :mode =>
|
108
|
+
:x_xss_protection => {:value => 1, :mode => 'block'} # set the :mode option to false to use "warning only" mode
|
109
|
+
:x_content_type_options => {:value => 'nosniff'}
|
107
110
|
```
|
108
111
|
|
109
112
|
### Content Security Policy (CSP)
|
@@ -159,28 +162,18 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
|
|
159
162
|
:img_src => 'http://mycdn.example.com'
|
160
163
|
}
|
161
164
|
}
|
162
|
-
|
165
|
+
|
163
166
|
# script-nonce is an experimental feature of CSP 1.1 available in Chrome. It allows
|
164
167
|
# you to whitelist inline script blocks. For more information, see
|
165
168
|
# https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#script-nonce
|
166
|
-
:script_nonce => {
|
167
|
-
|
168
|
-
# you can also use lambdas to use dynamically generated nonces
|
169
|
-
:script_nonce => lambda { @script_nonce] = 'something' }
|
169
|
+
:script_nonce => lambda { @script_nonce = SecureRandom.hex }
|
170
170
|
# which can be used to whitelist a script block:
|
171
171
|
# script_tag :nonce = @script_nonce { inline_script_call() }
|
172
172
|
}
|
173
173
|
```
|
174
174
|
|
175
|
-
### Only applied to IE
|
176
|
-
|
177
|
-
```ruby
|
178
|
-
:x_content_type_options => {:value => 'nosniff'}
|
179
|
-
```
|
180
|
-
|
181
175
|
### Example CSP header config
|
182
176
|
|
183
|
-
**Configure the CSP header as if it were the webkit-style header, no need to supply 'options' or 'allow' directives.**
|
184
177
|
|
185
178
|
```ruby
|
186
179
|
# most basic example
|
@@ -188,20 +181,16 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
|
|
188
181
|
:default_src => "https://* inline eval",
|
189
182
|
:report_uri => '/uri-directive'
|
190
183
|
}
|
191
|
-
|
192
|
-
> "default-src 'unsafe-inline' 'unsafe-eval' https
|
193
|
-
# Firefox
|
194
|
-
> "options inline-script eval-script; allow https://*; report-uri /uri-directive;"
|
184
|
+
|
185
|
+
> "default-src 'unsafe-inline' 'unsafe-eval' https://*; report-uri /uri-directive;"
|
195
186
|
|
196
187
|
# turn off inline scripting/eval
|
197
188
|
:csp => {
|
198
189
|
:default_src => 'https://*',
|
199
190
|
:report_uri => '/uri-directive'
|
200
191
|
}
|
201
|
-
|
192
|
+
|
202
193
|
> "default-src https://*; report-uri /uri-directive;"
|
203
|
-
# Firefox
|
204
|
-
> "allow https://*; report-uri /uri-directive;"
|
205
194
|
|
206
195
|
# Auction site wants to allow images from anywhere, plugin content from a list of trusted media providers (including a content distribution network), and scripts only from its server hosting sanitized JavaScript
|
207
196
|
:csp => {
|
@@ -211,19 +200,14 @@ and [Mozilla CSP specification](https://wiki.mozilla.org/Security/CSP/Specificat
|
|
211
200
|
# alternatively (NOT csv) :object_src => 'media1.com media2.com *.cdn.com'
|
212
201
|
:script_src => 'trustedscripts.example.com'
|
213
202
|
}
|
214
|
-
# Chrome
|
215
203
|
"default-src 'self'; img-src *; object-src media1.com media2.com *.cdn.com; script-src trustedscripts.example.com;"
|
216
|
-
# Firefox
|
217
|
-
"allow 'self'; img-src *; object-src media1.com media2.com *.cdn.com; script-src trustedscripts.example.com;"
|
218
204
|
```
|
219
205
|
|
220
206
|
## Note on Firefox handling of CSP
|
221
207
|
|
222
208
|
Currently, Firefox does not support the w3c draft standard. So there are a few steps taken to make the two interchangeable.
|
223
209
|
|
224
|
-
* inline\-script or eval\-script values in default/style/script\-src directives are moved to the options directive. Note: the style\-src directive is not fully supported in Firefox \- see https://bugzilla.mozilla.org/show_bug.cgi?id=763879.
|
225
210
|
* CSP reports will not POST cross\-origin. This sets up an internal endpoint in the application that will forward the request. Set the `forward_endpoint` value in the CSP section if you need to post cross origin for firefox. The internal endpoint that receives the initial request will forward the request to `forward_endpoint`
|
226
|
-
* Ffirefox adds port numbers to each /https?/ value which can make local development tricky with mocked services. Add environment specific code to configure this.
|
227
211
|
|
228
212
|
### Adding the Firefox report forwarding endpoint
|
229
213
|
|
@@ -315,6 +299,13 @@ module Web
|
|
315
299
|
end
|
316
300
|
```
|
317
301
|
|
302
|
+
## Similar libraries
|
303
|
+
|
304
|
+
* Node.js (express) [helmet](https://github.com/evilpacket/helmet) and [hood](https://github.com/seanmonstar/hood)
|
305
|
+
* J2EE Servlet >= 3.0 [highlines](https://github.com/sourceclear/headlines)
|
306
|
+
* ASP.NET - [NWebsec](http://nwebsec.codeplex.com/)
|
307
|
+
* Python - [django-csp](https://github.com/mozilla/django-csp/tree/master/csp) + [commonware](https://github.com/jsocol/commonware/tree/master/commonware/request)
|
308
|
+
* Go - [secureheader](https://github.com/kr/secureheader)
|
318
309
|
|
319
310
|
## Authors
|
320
311
|
|
@@ -19,7 +19,7 @@ describe OtherThingsController do
|
|
19
19
|
|
20
20
|
it "sets the X-WebKit-CSP header" do
|
21
21
|
get :index
|
22
|
-
response.headers['
|
22
|
+
response.headers['Content-Security-Policy-Report-Only'].should == "default-src 'self'; img-src data:; report-uri somewhere;"
|
23
23
|
end
|
24
24
|
|
25
25
|
#mock ssl
|
@@ -23,7 +23,7 @@ describe ThingsController do
|
|
23
23
|
|
24
24
|
it "sets the X-WebKit-CSP header" do
|
25
25
|
get :index
|
26
|
-
response.headers['
|
26
|
+
response.headers['Content-Security-Policy-Report-Only'].should == nil
|
27
27
|
end
|
28
28
|
|
29
29
|
#mock ssl
|
@@ -47,6 +47,3 @@ describe ThingsController do
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
50
|
-
|
51
|
-
|
52
|
-
# response.headers['X-WebKit-CSP-Report-Only'].should == "default-src 'self'; report-uri somewhere"
|
@@ -19,7 +19,7 @@ describe OtherThingsController do
|
|
19
19
|
|
20
20
|
it "sets the X-WebKit-CSP header" do
|
21
21
|
get :index
|
22
|
-
response.headers['
|
22
|
+
response.headers['Content-Security-Policy-Report-Only'].should == "default-src 'self'; img-src data:;"
|
23
23
|
end
|
24
24
|
|
25
25
|
#mock ssl
|
data/lib/secure_headers.rb
CHANGED
@@ -48,8 +48,13 @@ module SecureHeaders
|
|
48
48
|
end
|
49
49
|
|
50
50
|
module InstanceMethods
|
51
|
-
|
52
|
-
|
51
|
+
# Re-added for backwards compat.
|
52
|
+
def set_security_headers(options = self.class.secure_headers_options)
|
53
|
+
set_csp_header(request, options[:csp])
|
54
|
+
set_hsts_header(options[:hsts])
|
55
|
+
set_x_frame_options_header(options[:x_frame_options])
|
56
|
+
set_x_xss_protection_header(options[:x_xss_protection])
|
57
|
+
set_x_content_type_options_header(options[:x_content_type_options])
|
53
58
|
end
|
54
59
|
|
55
60
|
# backwards compatibility jank, to be removed in 1.0. Old API required a request
|
@@ -59,12 +64,9 @@ module SecureHeaders
|
|
59
64
|
# set_csp_header(+Hash+) - uses the request accessor and options from parameters
|
60
65
|
# set_csp_header(+Rack::Request+, +Hash+)
|
61
66
|
def set_csp_header(req = nil, options=nil)
|
62
|
-
|
63
|
-
|
67
|
+
# hack to help generating headers statically
|
64
68
|
if req.is_a?(Hash)
|
65
69
|
options = req
|
66
|
-
elsif req
|
67
|
-
@secure_headers_brwsr = Brwsr::Browser.new(:ua => req.env['HTTP_USER_AGENT'])
|
68
70
|
end
|
69
71
|
|
70
72
|
options = self.class.secure_headers_options[:csp] if options.nil?
|
@@ -85,7 +87,6 @@ module SecureHeaders
|
|
85
87
|
end
|
86
88
|
|
87
89
|
def set_x_content_type_options_header(options=self.class.secure_headers_options[:x_content_type_options])
|
88
|
-
return unless brwsr.ie? || brwsr.chrome?
|
89
90
|
set_a_header(:x_content_type_options, XContentTypeOptions, options)
|
90
91
|
end
|
91
92
|
|
@@ -116,10 +117,6 @@ module SecureHeaders
|
|
116
117
|
response.headers[name_or_header] = value
|
117
118
|
end
|
118
119
|
end
|
119
|
-
|
120
|
-
def broken_implementation?(browser)
|
121
|
-
return browser.ios5? || (browser.safari? && browser.version == '5')
|
122
|
-
end
|
123
120
|
end
|
124
121
|
end
|
125
122
|
|
@@ -127,13 +124,8 @@ end
|
|
127
124
|
require "secure_headers/version"
|
128
125
|
require "secure_headers/header"
|
129
126
|
require "secure_headers/headers/content_security_policy"
|
130
|
-
require "secure_headers/headers/content_security_policy/browser_strategy"
|
131
|
-
require "secure_headers/headers/content_security_policy/firefox_browser_strategy"
|
132
|
-
require "secure_headers/headers/content_security_policy/ie_browser_strategy"
|
133
|
-
require "secure_headers/headers/content_security_policy/standard_browser_strategy"
|
134
127
|
require "secure_headers/headers/x_frame_options"
|
135
128
|
require "secure_headers/headers/strict_transport_security"
|
136
129
|
require "secure_headers/headers/x_xss_protection"
|
137
130
|
require "secure_headers/headers/x_content_type_options"
|
138
131
|
require "secure_headers/railtie"
|
139
|
-
require "brwsr"
|
@@ -1,26 +1,19 @@
|
|
1
1
|
require 'uri'
|
2
|
-
require 'brwsr'
|
3
2
|
|
4
3
|
module SecureHeaders
|
5
4
|
class ContentSecurityPolicyBuildError < StandardError; end
|
6
5
|
class ContentSecurityPolicy < Header
|
7
6
|
module Constants
|
8
|
-
|
9
|
-
FIREFOX_CSP_HEADER = "options eval-script inline-script; allow https://* data:; frame-src https://* about: javascript:; img-src chrome-extension:"
|
10
|
-
|
11
|
-
FIREFOX_CSP_HEADER_NAME = 'X-Content-Security-Policy'
|
12
|
-
WEBKIT_CSP_HEADER_NAME = 'X-WebKit-CSP'
|
7
|
+
DEFAULT_CSP_HEADER = "default-src https: data: 'unsafe-inline' 'unsafe-eval'; frame-src https://* about: javascript:; img-src data:"
|
13
8
|
STANDARD_HEADER_NAME = "Content-Security-Policy"
|
14
|
-
|
15
9
|
FF_CSP_ENDPOINT = "/content_security_policy/forward_report"
|
16
|
-
|
17
|
-
FIREFOX_DIRECTIVES = DIRECTIVES + [:xhr_src, :frame_ancestors] - [:connect_src]
|
10
|
+
DIRECTIVES = [:default_src, :script_src, :frame_src, :style_src, :img_src, :media_src, :font_src, :object_src, :connect_src]
|
18
11
|
META = [:enforce, :http_additions, :disable_chrome_extension, :disable_fill_missing, :forward_endpoint]
|
19
12
|
end
|
20
13
|
include Constants
|
21
14
|
|
22
15
|
attr_accessor *META
|
23
|
-
attr_reader :browser, :ssl_request, :report_uri, :request_uri, :experimental
|
16
|
+
attr_reader :browser, :ssl_request, :report_uri, :request_uri, :experimental
|
24
17
|
|
25
18
|
alias :disable_chrome_extension? :disable_chrome_extension
|
26
19
|
alias :disable_fill_missing? :disable_fill_missing
|
@@ -40,7 +33,7 @@ module SecureHeaders
|
|
40
33
|
if options[:request]
|
41
34
|
parse_request(options[:request])
|
42
35
|
else
|
43
|
-
@
|
36
|
+
@ua = options[:ua]
|
44
37
|
# fails open, assumes http. Bad idea? Will always include http additions.
|
45
38
|
# could also fail if not supplied.
|
46
39
|
@ssl_request = !!options.delete(:ssl)
|
@@ -52,8 +45,8 @@ module SecureHeaders
|
|
52
45
|
configure(config) if config
|
53
46
|
end
|
54
47
|
|
55
|
-
def configure
|
56
|
-
@config =
|
48
|
+
def configure(config)
|
49
|
+
@config = config.dup
|
57
50
|
|
58
51
|
experimental_config = @config.delete(:experimental)
|
59
52
|
if @experimental && experimental_config
|
@@ -70,40 +63,34 @@ module SecureHeaders
|
|
70
63
|
|
71
64
|
normalize_csp_options
|
72
65
|
normalize_reporting_endpoint
|
73
|
-
|
66
|
+
fill_directives unless disable_fill_missing?
|
74
67
|
end
|
75
68
|
|
76
69
|
def name
|
77
|
-
|
70
|
+
base = STANDARD_HEADER_NAME
|
71
|
+
if !enforce || experimental
|
72
|
+
base += "-Report-Only"
|
73
|
+
end
|
74
|
+
base
|
78
75
|
end
|
79
76
|
|
80
77
|
def value
|
81
78
|
return @config if @config.is_a?(String)
|
82
|
-
|
83
79
|
if @config
|
84
80
|
build_value
|
85
81
|
else
|
86
|
-
|
82
|
+
DEFAULT_CSP_HEADER
|
87
83
|
end
|
88
84
|
end
|
89
85
|
|
90
86
|
private
|
91
87
|
|
92
|
-
def browser_strategy
|
93
|
-
@browser_strategy ||= BrowserStrategy.build(self)
|
94
|
-
end
|
95
|
-
|
96
|
-
def directives
|
97
|
-
browser_strategy.directives
|
98
|
-
end
|
99
|
-
|
100
88
|
def build_value
|
101
|
-
|
102
|
-
browser_strategy.add_missing_extension_values unless disable_chrome_extension?
|
89
|
+
raise "Expected to find default_src directive value" unless @config[:default_src]
|
103
90
|
append_http_additions unless ssl_request?
|
104
|
-
|
105
91
|
header_value = [
|
106
|
-
|
92
|
+
# ensure default-src is first
|
93
|
+
build_directive(:default_src),
|
107
94
|
generic_directives(@config),
|
108
95
|
report_uri_directive,
|
109
96
|
script_nonce_directive,
|
@@ -118,9 +105,8 @@ module SecureHeaders
|
|
118
105
|
|
119
106
|
def fill_directives
|
120
107
|
return unless @config[:default_src]
|
121
|
-
|
122
108
|
default = @config[:default_src]
|
123
|
-
|
109
|
+
DIRECTIVES.each do |directive|
|
124
110
|
unless @config[directive]
|
125
111
|
@config[directive] = default
|
126
112
|
end
|
@@ -130,7 +116,6 @@ module SecureHeaders
|
|
130
116
|
|
131
117
|
def append_http_additions
|
132
118
|
return unless http_additions
|
133
|
-
|
134
119
|
http_additions.each do |k, v|
|
135
120
|
@config[k] ||= []
|
136
121
|
@config[k] << v
|
@@ -146,14 +131,10 @@ module SecureHeaders
|
|
146
131
|
end
|
147
132
|
end
|
148
133
|
|
149
|
-
def filter_unsupported_directives
|
150
|
-
@config = browser_strategy.filter_unsupported_directives(@config)
|
151
|
-
end
|
152
|
-
|
153
134
|
# translates 'inline','self', 'none' and 'eval' to their respective impl-specific values.
|
154
135
|
def translate_dir_value val
|
155
136
|
if %w{inline eval}.include?(val)
|
156
|
-
|
137
|
+
val == 'inline' ? "'unsafe-inline'" : "'unsafe-eval'"
|
157
138
|
# self/none are special sources/src-dir-values and need to be quoted in chrome
|
158
139
|
elsif %{self none}.include?(val)
|
159
140
|
"'#{val}'"
|
@@ -162,33 +143,21 @@ module SecureHeaders
|
|
162
143
|
end
|
163
144
|
end
|
164
145
|
|
165
|
-
def translate_inline_or_eval val
|
166
|
-
browser_strategy.translate_inline_or_eval(val)
|
167
|
-
end
|
168
|
-
|
169
146
|
# if we have a forwarding endpoint setup and we are not on the same origin as our report_uri
|
170
147
|
# or only a path was supplied (in which case we assume cross-host)
|
171
148
|
# we need to forward the request for Firefox.
|
172
149
|
def normalize_reporting_endpoint
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
150
|
+
if @ua && @ua =~ /Firefox/
|
151
|
+
if same_origin? || report_uri.nil? || URI.parse(report_uri).host.nil?
|
152
|
+
return
|
153
|
+
end
|
177
154
|
|
178
|
-
|
179
|
-
|
155
|
+
if forward_endpoint
|
156
|
+
@report_uri = FF_CSP_ENDPOINT
|
157
|
+
end
|
180
158
|
end
|
181
159
|
end
|
182
160
|
|
183
|
-
def build_impl_specific_directives
|
184
|
-
default = expect_directive_value(:default_src)
|
185
|
-
browser_strategy.build_impl_specific_directives(default)
|
186
|
-
end
|
187
|
-
|
188
|
-
def expect_directive_value key
|
189
|
-
@config.delete(key) {|k| raise ContentSecurityPolicyBuildError.new("Expected to find #{k} directive value")}
|
190
|
-
end
|
191
|
-
|
192
161
|
def same_origin?
|
193
162
|
return unless report_uri && request_uri
|
194
163
|
|
@@ -232,19 +201,24 @@ module SecureHeaders
|
|
232
201
|
end
|
233
202
|
|
234
203
|
config.keys.sort_by{|k| k.to_s}.each do |k| # ensure consistent ordering
|
235
|
-
header_value +=
|
204
|
+
header_value += build_directive(k)
|
236
205
|
end
|
237
206
|
|
238
207
|
header_value
|
239
208
|
end
|
240
209
|
|
210
|
+
# build and deletes the directive
|
211
|
+
def build_directive(key)
|
212
|
+
"#{symbol_to_hyphen_case(key)} #{@config.delete(key).join(" ")}; "
|
213
|
+
end
|
214
|
+
|
241
215
|
def symbol_to_hyphen_case sym
|
242
216
|
sym.to_s.gsub('_', '-')
|
243
217
|
end
|
244
218
|
|
245
219
|
def parse_request request
|
246
|
-
@browser = Brwsr::Browser.new(:ua => request.env['HTTP_USER_AGENT'])
|
247
220
|
@ssl_request = request.ssl?
|
221
|
+
@ua = request.env['HTTP_USER_AGENT']
|
248
222
|
@request_uri = if request.respond_to?(:original_url)
|
249
223
|
# rails 3.1+
|
250
224
|
request.original_url
|
data/secure_headers.gemspec
CHANGED
@@ -18,6 +18,5 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
19
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
20
|
gem.require_paths = ["lib"]
|
21
|
-
gem.add_dependency "brwsr", ">= 1.1.1"
|
22
21
|
gem.add_development_dependency "rake"
|
23
22
|
end
|
@@ -5,7 +5,7 @@ describe ContentSecurityPolicyController do
|
|
5
5
|
{
|
6
6
|
"csp-report" => {
|
7
7
|
"document-uri" => "http://localhost:3001/csp","violated-directive" => "script-src 'none'",
|
8
|
-
"original-policy" => "default-src https://* 'unsafe-eval'; frame-src 'self'; img-src
|
8
|
+
"original-policy" => "default-src https://* 'unsafe-eval'; frame-src 'self'; img-src https://*; report-uri http://localhost:3001/scribes/csp_report; script-src 'none'; style-src 'unsafe-inline' 'self';",
|
9
9
|
"blocked-uri" => "http://localhost:3001/stuff.js"
|
10
10
|
}
|
11
11
|
}
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'brwsr'
|
3
2
|
|
4
3
|
module SecureHeaders
|
5
4
|
describe ContentSecurityPolicy do
|
@@ -32,17 +31,17 @@ module SecureHeaders
|
|
32
31
|
describe "#name" do
|
33
32
|
context "when supplying options to override request" do
|
34
33
|
specify { ContentSecurityPolicy.new(default_opts, :ua => IE).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
35
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name.should ==
|
34
|
+
specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
36
35
|
specify { ContentSecurityPolicy.new(default_opts, :ua => FIREFOX_23).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
37
|
-
specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME).name.should ==
|
36
|
+
specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
38
37
|
specify { ContentSecurityPolicy.new(default_opts, :ua => CHROME_25).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
39
38
|
end
|
40
39
|
|
41
40
|
context "when in report-only mode" do
|
42
41
|
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
43
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name.should ==
|
42
|
+
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
44
43
|
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
45
|
-
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name.should ==
|
44
|
+
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
46
45
|
specify { ContentSecurityPolicy.new(default_opts, :request => request_for(CHROME_25)).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
47
46
|
end
|
48
47
|
|
@@ -50,18 +49,18 @@ module SecureHeaders
|
|
50
49
|
let(:opts) { default_opts.merge(:enforce => true)}
|
51
50
|
|
52
51
|
specify { ContentSecurityPolicy.new(opts, :request => request_for(IE)).name.should == STANDARD_HEADER_NAME}
|
53
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name.should ==
|
52
|
+
specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX)).name.should == STANDARD_HEADER_NAME}
|
54
53
|
specify { ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX_23)).name.should == STANDARD_HEADER_NAME}
|
55
|
-
specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name.should ==
|
54
|
+
specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME)).name.should == STANDARD_HEADER_NAME}
|
56
55
|
specify { ContentSecurityPolicy.new(opts, :request => request_for(CHROME_25)).name.should == STANDARD_HEADER_NAME}
|
57
56
|
end
|
58
57
|
|
59
58
|
context "when in experimental mode" do
|
60
59
|
let(:opts) { default_opts.merge(:enforce => true).merge(:experimental => {})}
|
61
60
|
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(IE)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
62
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX)}).name.should ==
|
61
|
+
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
63
62
|
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(FIREFOX_23)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
64
|
-
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name.should ==
|
63
|
+
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
65
64
|
specify { ContentSecurityPolicy.new(opts, {:experimental => true, :request => request_for(CHROME_25)}).name.should == STANDARD_HEADER_NAME + "-Report-Only"}
|
66
65
|
end
|
67
66
|
end
|
@@ -74,43 +73,10 @@ module SecureHeaders
|
|
74
73
|
@opts = default_opts
|
75
74
|
end
|
76
75
|
|
77
|
-
context "
|
78
|
-
it "converts the script values to their equivilents" do
|
79
|
-
csp = ContentSecurityPolicy.new(@opts, :request => request_for(FIREFOX))
|
80
|
-
csp.value.should include("script-src https://* data: 'self' 'none'")
|
81
|
-
csp.value.should include('options inline-script eval-script')
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
context "X-Webkit-CSP" do
|
76
|
+
context "Content-Security-Policy" do
|
86
77
|
it "converts the script values to their equivilents" do
|
87
78
|
csp = ContentSecurityPolicy.new(@opts, :request => request_for(CHROME))
|
88
|
-
csp.value.should include("script-src 'unsafe-inline' 'unsafe-eval' https://* data: 'self' 'none'
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
describe "#build_impl_specific_directives" do
|
94
|
-
context "X-Content-Security-Policy" do
|
95
|
-
it "moves script-src inline and eval values to the options directive" do
|
96
|
-
opts = {
|
97
|
-
:default_src => 'https://*',
|
98
|
-
:script_src => "inline eval https://*"
|
99
|
-
}
|
100
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
101
|
-
browser_specific = csp.send :build_impl_specific_directives
|
102
|
-
browser_specific.should include('options inline-script eval-script;')
|
103
|
-
end
|
104
|
-
|
105
|
-
it "does not move values from style-src into options" do
|
106
|
-
opts = {
|
107
|
-
:default_src => 'https://*',
|
108
|
-
:style_src => "inline eval https://*"
|
109
|
-
}
|
110
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
111
|
-
browser_specific = csp.send :build_impl_specific_directives
|
112
|
-
browser_specific.should_not include('inline-script')
|
113
|
-
browser_specific.should_not include('eval-script')
|
79
|
+
csp.value.should include("script-src 'unsafe-inline' 'unsafe-eval' https://* data: 'self' 'none'")
|
114
80
|
end
|
115
81
|
end
|
116
82
|
end
|
@@ -162,8 +128,6 @@ module SecureHeaders
|
|
162
128
|
csp = ContentSecurityPolicy.new({:report_uri => 'https://example.com'}, :request => request_for(FIREFOX, "https://anotherexample.com"))
|
163
129
|
csp.send(:same_origin?).should be_false
|
164
130
|
end
|
165
|
-
|
166
|
-
|
167
131
|
end
|
168
132
|
|
169
133
|
describe "#normalize_reporting_endpoint" do
|
@@ -252,7 +216,7 @@ module SecureHeaders
|
|
252
216
|
csp.value.should == value
|
253
217
|
end
|
254
218
|
|
255
|
-
it "sends the
|
219
|
+
it "sends the standard csp header if an unknown browser is supplied" do
|
256
220
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(IE))
|
257
221
|
csp.value.should match "default-src"
|
258
222
|
end
|
@@ -260,30 +224,7 @@ module SecureHeaders
|
|
260
224
|
context "Firefox" do
|
261
225
|
it "builds a csp header for firefox" do
|
262
226
|
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX))
|
263
|
-
csp.value.should == "
|
264
|
-
end
|
265
|
-
|
266
|
-
it "does not append chrome-extension to directives" do
|
267
|
-
csp = ContentSecurityPolicy.new(default_opts.merge(:disable_chrome_extension => false), :request => request_for(FIREFOX))
|
268
|
-
csp.value.should_not match "chrome-extension:"
|
269
|
-
end
|
270
|
-
|
271
|
-
it "copies connect-src values to xhr_src values" do
|
272
|
-
opts = {
|
273
|
-
:default_src => 'http://twitter.com',
|
274
|
-
:connect_src => 'self http://*.localhost.com:*',
|
275
|
-
:disable_chrome_extension => true,
|
276
|
-
:disable_fill_missing => true
|
277
|
-
}
|
278
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(FIREFOX))
|
279
|
-
csp.value.should =~ /xhr-src 'self' http:/
|
280
|
-
end
|
281
|
-
|
282
|
-
context "Firefox >= 23" do
|
283
|
-
it "builds a csp header for firefox" do
|
284
|
-
csp = ContentSecurityPolicy.new(default_opts, :request => request_for(FIREFOX_23))
|
285
|
-
csp.value.should == "default-src https://*; img-src data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;"
|
286
|
-
end
|
227
|
+
csp.value.should == "default-src https://*; img-src data:; script-src 'unsafe-inline' 'unsafe-eval' https://* data:; style-src 'unsafe-inline' https://* about:; report-uri /csp_report;"
|
287
228
|
end
|
288
229
|
end
|
289
230
|
|
@@ -297,20 +238,6 @@ module SecureHeaders
|
|
297
238
|
csp = ContentSecurityPolicy.new(@options_with_forwarding, :request => request_for(CHROME))
|
298
239
|
csp.value.should =~ /report-uri #{@options_with_forwarding[:report_uri]};/
|
299
240
|
end
|
300
|
-
|
301
|
-
it "whitelists chrome_extensions by default" do
|
302
|
-
opts = {
|
303
|
-
:default_src => 'https://*',
|
304
|
-
:report_uri => '/csp_report',
|
305
|
-
:script_src => 'inline eval https://* data:',
|
306
|
-
:style_src => "inline https://* chrome-extension: about:"
|
307
|
-
}
|
308
|
-
|
309
|
-
csp = ContentSecurityPolicy.new(opts, :request => request_for(CHROME))
|
310
|
-
|
311
|
-
# ignore the report-uri directive
|
312
|
-
csp.value.split(';')[0...-1].each{|directive| directive.should =~ /chrome-extension:/}
|
313
|
-
end
|
314
241
|
end
|
315
242
|
|
316
243
|
context "when supplying a experimental values" do
|
@@ -324,7 +251,6 @@ module SecureHeaders
|
|
324
251
|
}
|
325
252
|
}}
|
326
253
|
|
327
|
-
let(:header) {}
|
328
254
|
it "returns the original value" do
|
329
255
|
header = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
330
256
|
header.value.should == "default-src 'self'; img-src data:; script-src https://*;"
|
@@ -384,21 +310,21 @@ module SecureHeaders
|
|
384
310
|
# for comparison purposes, if not using the experimental header this would produce
|
385
311
|
# "allow 'self'; script-src https://*" for https requests
|
386
312
|
# and
|
387
|
-
# "allow 'self; script-src https://* http://*" for http requests
|
313
|
+
# "allow 'self'; script-src https://* http://*" for http requests
|
388
314
|
|
389
315
|
it "uses the value in the experimental block over SSL" do
|
390
316
|
csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX, '/', :ssl => true))
|
391
|
-
csp.value.should == "
|
317
|
+
csp.value.should == "default-src 'self'; img-src data:; script-src 'self';"
|
392
318
|
end
|
393
319
|
|
394
320
|
it "detects the :ssl => true option" do
|
395
321
|
csp = ContentSecurityPolicy.new(options, :experimental => true, :ua => FIREFOX, :ssl => true)
|
396
|
-
csp.value.should == "
|
322
|
+
csp.value.should == "default-src 'self'; img-src data:; script-src 'self';"
|
397
323
|
end
|
398
324
|
|
399
325
|
it "merges the values from experimental/http_additions when not over SSL" do
|
400
326
|
csp = ContentSecurityPolicy.new(options, :experimental => true, :request => request_for(FIREFOX))
|
401
|
-
csp.value.should == "
|
327
|
+
csp.value.should == "default-src 'self'; img-src data:; script-src 'self' https://mycdn.example.com;"
|
402
328
|
end
|
403
329
|
end
|
404
330
|
end
|
@@ -415,11 +341,6 @@ module SecureHeaders
|
|
415
341
|
csp.value.should match "script-nonce random;"
|
416
342
|
end
|
417
343
|
|
418
|
-
it "uses the value in the X-Content-Security-Policy" do
|
419
|
-
csp = ContentSecurityPolicy.new(options, :request => request_for(FIREFOX))
|
420
|
-
csp.value.should match "script-nonce random;"
|
421
|
-
end
|
422
|
-
|
423
344
|
it "runs a dynamic nonce generator" do
|
424
345
|
options[:script_nonce] = lambda { 'something' }
|
425
346
|
csp = ContentSecurityPolicy.new(options, :request => request_for(CHROME))
|
@@ -92,15 +92,7 @@ describe SecureHeaders do
|
|
92
92
|
USER_AGENTS.each do |name, useragent|
|
93
93
|
it "sets all default headers for #{name} (smoke test)" do
|
94
94
|
stub_user_agent(useragent)
|
95
|
-
number_of_headers =
|
96
|
-
when :ie, :chrome
|
97
|
-
5
|
98
|
-
when :ios5, :safari5, :safari5_1
|
99
|
-
3 # csp breaks these browsers
|
100
|
-
else
|
101
|
-
4
|
102
|
-
end
|
103
|
-
|
95
|
+
number_of_headers = 5
|
104
96
|
subject.should_receive(:set_header).exactly(number_of_headers).times # a request for a given header
|
105
97
|
subject.set_csp_header
|
106
98
|
subject.set_x_frame_options_header
|
@@ -139,18 +131,10 @@ describe SecureHeaders do
|
|
139
131
|
|
140
132
|
it "does not set the CSP header if disabled" do
|
141
133
|
stub_user_agent(USER_AGENTS[:chrome])
|
142
|
-
should_not_assign_header(
|
134
|
+
should_not_assign_header(STANDARD_HEADER_NAME)
|
143
135
|
subject.set_csp_header(options_for(:csp).merge(:csp => false))
|
144
136
|
end
|
145
137
|
|
146
|
-
# apparently iOS5 safari with CSP in enforce mode causes nothing to render
|
147
|
-
# it has no effect in report-only mode (as in no report is sent)
|
148
|
-
it "does not set CSP header if using ios5" do
|
149
|
-
stub_user_agent(USER_AGENTS[:ios5])
|
150
|
-
subject.should_not_receive(:set_header)
|
151
|
-
subject.set_csp_header(options_for(:csp))
|
152
|
-
end
|
153
|
-
|
154
138
|
context "when disabled by configuration settings" do
|
155
139
|
it "does not set any headers when disabled" do
|
156
140
|
::SecureHeaders::Configuration.configure do |config|
|
@@ -214,7 +198,7 @@ describe SecureHeaders do
|
|
214
198
|
end
|
215
199
|
|
216
200
|
describe "#set_x_content_type_options" do
|
217
|
-
|
201
|
+
USER_AGENTS.each do |useragent|
|
218
202
|
context "when using #{useragent}" do
|
219
203
|
before(:each) do
|
220
204
|
stub_user_agent(USER_AGENTS[useragent])
|
@@ -237,7 +221,7 @@ describe SecureHeaders do
|
|
237
221
|
context "when using Firefox" do
|
238
222
|
it "sets CSP headers" do
|
239
223
|
stub_user_agent(USER_AGENTS[:firefox])
|
240
|
-
should_assign_header(
|
224
|
+
should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
|
241
225
|
subject.set_csp_header
|
242
226
|
end
|
243
227
|
end
|
@@ -245,7 +229,7 @@ describe SecureHeaders do
|
|
245
229
|
context "when using Chrome" do
|
246
230
|
it "sets default CSP header" do
|
247
231
|
stub_user_agent(USER_AGENTS[:chrome])
|
248
|
-
should_assign_header(
|
232
|
+
should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
|
249
233
|
subject.set_csp_header
|
250
234
|
end
|
251
235
|
end
|
@@ -253,7 +237,7 @@ describe SecureHeaders do
|
|
253
237
|
context "when using a browser besides chrome/firefox" do
|
254
238
|
it "sets the CSP header" do
|
255
239
|
stub_user_agent(USER_AGENTS[:opera])
|
256
|
-
should_assign_header(
|
240
|
+
should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", DEFAULT_CSP_HEADER)
|
257
241
|
subject.set_csp_header
|
258
242
|
end
|
259
243
|
end
|
@@ -273,14 +257,14 @@ describe SecureHeaders do
|
|
273
257
|
|
274
258
|
it "does not set the header in enforce mode if experimental is supplied, but enforce is disabled" do
|
275
259
|
opts = @opts.merge(:enforce => false)
|
276
|
-
should_assign_header(
|
277
|
-
should_not_assign_header(
|
260
|
+
should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", anything)
|
261
|
+
should_not_assign_header(STANDARD_HEADER_NAME)
|
278
262
|
subject.set_csp_header(opts)
|
279
263
|
end
|
280
264
|
|
281
265
|
it "sets a header in enforce mode as well as report-only mode" do
|
282
|
-
should_assign_header(
|
283
|
-
should_assign_header(
|
266
|
+
should_assign_header(STANDARD_HEADER_NAME, anything)
|
267
|
+
should_assign_header(STANDARD_HEADER_NAME + "-Report-Only", anything)
|
284
268
|
subject.set_csp_header(@opts)
|
285
269
|
end
|
286
270
|
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: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,24 +9,8 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: brwsr
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ! '>='
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: 1.1.1
|
22
|
-
type: :runtime
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ! '>='
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 1.1.1
|
30
14
|
- !ruby/object:Gem::Dependency
|
31
15
|
name: rake
|
32
16
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,10 +134,6 @@ files:
|
|
150
134
|
- lib/secure_headers.rb
|
151
135
|
- lib/secure_headers/header.rb
|
152
136
|
- lib/secure_headers/headers/content_security_policy.rb
|
153
|
-
- lib/secure_headers/headers/content_security_policy/browser_strategy.rb
|
154
|
-
- lib/secure_headers/headers/content_security_policy/firefox_browser_strategy.rb
|
155
|
-
- lib/secure_headers/headers/content_security_policy/ie_browser_strategy.rb
|
156
|
-
- lib/secure_headers/headers/content_security_policy/standard_browser_strategy.rb
|
157
137
|
- lib/secure_headers/headers/strict_transport_security.rb
|
158
138
|
- lib/secure_headers/headers/x_content_type_options.rb
|
159
139
|
- lib/secure_headers/headers/x_frame_options.rb
|
@@ -1,78 +0,0 @@
|
|
1
|
-
require "forwardable"
|
2
|
-
|
3
|
-
module SecureHeaders
|
4
|
-
class ContentSecurityPolicy
|
5
|
-
class BrowserStrategy
|
6
|
-
extend Forwardable
|
7
|
-
|
8
|
-
def_delegators :@content_security_policy, :browser, :experimental, :enforce, :config
|
9
|
-
|
10
|
-
def self.build(content_security_policy)
|
11
|
-
browser = content_security_policy.browser
|
12
|
-
klass = if browser.ie?
|
13
|
-
IeBrowserStrategy
|
14
|
-
elsif browser.firefox?
|
15
|
-
if browser.version.to_i >= 23
|
16
|
-
StandardBrowserStrategy
|
17
|
-
else
|
18
|
-
FirefoxBrowserStrategy
|
19
|
-
end
|
20
|
-
else
|
21
|
-
StandardBrowserStrategy
|
22
|
-
end
|
23
|
-
|
24
|
-
klass.new content_security_policy
|
25
|
-
end
|
26
|
-
|
27
|
-
def initialize(content_security_policy)
|
28
|
-
@content_security_policy = content_security_policy
|
29
|
-
end
|
30
|
-
|
31
|
-
def base_name
|
32
|
-
SecureHeaders::ContentSecurityPolicy::STANDARD_HEADER_NAME
|
33
|
-
end
|
34
|
-
|
35
|
-
def name
|
36
|
-
base = base_name
|
37
|
-
if !enforce || experimental
|
38
|
-
base += "-Report-Only"
|
39
|
-
end
|
40
|
-
base
|
41
|
-
end
|
42
|
-
|
43
|
-
def csp_header
|
44
|
-
SecureHeaders::ContentSecurityPolicy::WEBKIT_CSP_HEADER
|
45
|
-
end
|
46
|
-
|
47
|
-
def directives
|
48
|
-
SecureHeaders::ContentSecurityPolicy::WEBKIT_DIRECTIVES
|
49
|
-
end
|
50
|
-
|
51
|
-
def filter_unsupported_directives(config)
|
52
|
-
config = config.dup
|
53
|
-
config.delete(:frame_ancestors)
|
54
|
-
config
|
55
|
-
end
|
56
|
-
|
57
|
-
def translate_inline_or_eval val
|
58
|
-
val == 'inline' ? "'unsafe-inline'" : "'unsafe-eval'"
|
59
|
-
end
|
60
|
-
|
61
|
-
def build_impl_specific_directives(default)
|
62
|
-
if default.any?
|
63
|
-
"default-src #{default.join(" ")}; "
|
64
|
-
else
|
65
|
-
""
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def normalize_reporting_endpoint?
|
70
|
-
# noop except for Firefox for now
|
71
|
-
end
|
72
|
-
|
73
|
-
def add_missing_extension_values
|
74
|
-
# noop except for chrome for now
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
module SecureHeaders
|
2
|
-
class ContentSecurityPolicy
|
3
|
-
class FirefoxBrowserStrategy < BrowserStrategy
|
4
|
-
def base_name
|
5
|
-
SecureHeaders::ContentSecurityPolicy::FIREFOX_CSP_HEADER_NAME
|
6
|
-
end
|
7
|
-
|
8
|
-
def csp_header
|
9
|
-
SecureHeaders::ContentSecurityPolicy::FIREFOX_CSP_HEADER
|
10
|
-
end
|
11
|
-
|
12
|
-
def directives
|
13
|
-
SecureHeaders::ContentSecurityPolicy::FIREFOX_DIRECTIVES
|
14
|
-
end
|
15
|
-
|
16
|
-
def filter_unsupported_directives(config)
|
17
|
-
config = config.dup
|
18
|
-
config[:xhr_src] = config.delete(:connect_src) if config[:connect_src]
|
19
|
-
config
|
20
|
-
end
|
21
|
-
|
22
|
-
def translate_inline_or_eval val
|
23
|
-
val == 'inline' ? 'inline-script' : 'eval-script'
|
24
|
-
end
|
25
|
-
|
26
|
-
def build_impl_specific_directives(default)
|
27
|
-
build_firefox_specific_preamble(default) || ''
|
28
|
-
end
|
29
|
-
|
30
|
-
def build_firefox_specific_preamble(default_src_value)
|
31
|
-
header_value = ''
|
32
|
-
header_value += "allow #{default_src_value.join(" ")}; " if default_src_value.any?
|
33
|
-
|
34
|
-
options_directive = build_options_directive
|
35
|
-
header_value += "options #{options_directive.join(" ")}; " if options_directive.any?
|
36
|
-
header_value
|
37
|
-
end
|
38
|
-
|
39
|
-
# moves inline/eval values from script-src to options
|
40
|
-
# discards those values in the style-src directive
|
41
|
-
def build_options_directive
|
42
|
-
options_directive = []
|
43
|
-
config.each do |directive, val|
|
44
|
-
next if val.is_a?(String)
|
45
|
-
new_val = []
|
46
|
-
val.each do |token|
|
47
|
-
if ['inline-script', 'eval-script'].include?(token)
|
48
|
-
# Firefox does not support blocking inline styles ATM
|
49
|
-
# https://bugzilla.mozilla.org/show_bug.cgi?id=763879
|
50
|
-
unless directive?(directive, "style_src") || options_directive.include?(token)
|
51
|
-
options_directive << token
|
52
|
-
end
|
53
|
-
else
|
54
|
-
new_val << token
|
55
|
-
end
|
56
|
-
end
|
57
|
-
config[directive] = new_val
|
58
|
-
end
|
59
|
-
|
60
|
-
options_directive
|
61
|
-
end
|
62
|
-
|
63
|
-
def directive? val, name
|
64
|
-
val.to_s.casecmp(name) == 0
|
65
|
-
end
|
66
|
-
|
67
|
-
def normalize_reporting_endpoint?
|
68
|
-
true
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module SecureHeaders
|
2
|
-
class ContentSecurityPolicy
|
3
|
-
class StandardBrowserStrategy < BrowserStrategy
|
4
|
-
def base_name
|
5
|
-
if (browser.firefox? && browser.version.to_i >= 23) || (browser.chrome? && browser.version.to_i >= 25)
|
6
|
-
SecureHeaders::ContentSecurityPolicy::STANDARD_HEADER_NAME
|
7
|
-
else
|
8
|
-
SecureHeaders::ContentSecurityPolicy::WEBKIT_CSP_HEADER_NAME
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def add_missing_extension_values
|
13
|
-
directives.each do |directive|
|
14
|
-
next unless config[directive]
|
15
|
-
if !config[directive].include?('chrome-extension:')
|
16
|
-
config[directive] << 'chrome-extension:'
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|