secure_headers 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of secure_headers might be problematic. Click here for more details.
- 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
|