secure_headers 2.2.0 → 2.4.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +1 -1
- data/Gemfile +3 -2
- data/README.md +89 -18
- data/Rakefile +2 -2
- data/fixtures/{rails_3_2_12_no_init → rails_3_2_22}/Gemfile +2 -1
- data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/initializers/secure_headers.rb +2 -3
- data/fixtures/{rails_3_2_12 → rails_3_2_22}/spec/controllers/other_things_controller_spec.rb +1 -1
- data/fixtures/{rails_3_2_12 → rails_3_2_22_no_init}/Gemfile +2 -2
- data/fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb +20 -0
- data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/spec/controllers/other_things_controller_spec.rb +7 -1
- data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +1 -1
- data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +2 -3
- data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +1 -1
- data/lib/secure_headers/headers/content_security_policy.rb +41 -41
- data/lib/secure_headers/headers/public_key_pins.rb +1 -0
- data/lib/secure_headers/headers/strict_transport_security.rb +1 -0
- data/lib/secure_headers/headers/x_content_type_options.rb +2 -1
- data/lib/secure_headers/headers/x_download_options.rb +1 -0
- data/lib/secure_headers/headers/x_frame_options.rb +1 -0
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +1 -0
- data/lib/secure_headers/headers/x_xss_protection.rb +1 -0
- data/lib/secure_headers/version.rb +1 -1
- data/lib/secure_headers.rb +59 -27
- data/secure_headers.gemspec +1 -1
- data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +2 -2
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +64 -36
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +1 -1
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +1 -1
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +1 -1
- data/spec/lib/secure_headers_spec.rb +51 -5
- data/spec/spec_helper.rb +8 -1
- metadata +72 -59
- data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +0 -6
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/.rspec +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/README.rdoc +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/Rakefile +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/controllers/application_controller.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/controllers/other_things_controller.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/controllers/things_controller.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/models/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/views/layouts/application.html.erb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/views/other_things/index.html.erb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/app/views/things/index.html.erb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/application.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/boot.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/environment.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/environments/test.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/routes.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config/script_hashes.yml +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/config.ru +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/lib/assets/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/lib/tasks/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/log/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/spec/controllers/things_controller_spec.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/spec/spec_helper.rb +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/vendor/assets/javascripts/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/vendor/assets/stylesheets/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12 → rails_3_2_22}/vendor/plugins/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/.rspec +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/README.rdoc +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/Rakefile +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/controllers/application_controller.rb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/controllers/things_controller.rb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/models/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/views/layouts/application.html.erb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/views/other_things/index.html.erb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/app/views/things/index.html.erb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/application.rb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/boot.rb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/environment.rb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/environments/test.rb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config/routes.rb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/config.ru +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/lib/assets/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/lib/tasks/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/log/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/spec/controllers/things_controller_spec.rb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/spec/spec_helper.rb +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/vendor/assets/javascripts/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/vendor/assets/stylesheets/.gitkeep +0 -0
- /data/fixtures/{rails_3_2_12_no_init → rails_3_2_22_no_init}/vendor/plugins/.gitkeep +0 -0
data/lib/secure_headers.rb
CHANGED
|
@@ -1,7 +1,32 @@
|
|
|
1
|
+
require "secure_headers/version"
|
|
2
|
+
require "secure_headers/header"
|
|
3
|
+
require "secure_headers/headers/public_key_pins"
|
|
4
|
+
require "secure_headers/headers/content_security_policy"
|
|
5
|
+
require "secure_headers/headers/x_frame_options"
|
|
6
|
+
require "secure_headers/headers/strict_transport_security"
|
|
7
|
+
require "secure_headers/headers/x_xss_protection"
|
|
8
|
+
require "secure_headers/headers/x_content_type_options"
|
|
9
|
+
require "secure_headers/headers/x_download_options"
|
|
10
|
+
require "secure_headers/headers/x_permitted_cross_domain_policies"
|
|
11
|
+
require "secure_headers/railtie"
|
|
12
|
+
require "secure_headers/hash_helper"
|
|
13
|
+
require "secure_headers/view_helper"
|
|
14
|
+
|
|
1
15
|
module SecureHeaders
|
|
2
16
|
SCRIPT_HASH_CONFIG_FILE = 'config/script_hashes.yml'
|
|
3
17
|
HASHES_ENV_KEY = 'secure_headers.script_hashes'
|
|
4
18
|
|
|
19
|
+
ALL_HEADER_CLASSES = [
|
|
20
|
+
SecureHeaders::ContentSecurityPolicy,
|
|
21
|
+
SecureHeaders::StrictTransportSecurity,
|
|
22
|
+
SecureHeaders::PublicKeyPins,
|
|
23
|
+
SecureHeaders::XContentTypeOptions,
|
|
24
|
+
SecureHeaders::XDownloadOptions,
|
|
25
|
+
SecureHeaders::XFrameOptions,
|
|
26
|
+
SecureHeaders::XPermittedCrossDomainPolicies,
|
|
27
|
+
SecureHeaders::XXssProtection
|
|
28
|
+
]
|
|
29
|
+
|
|
5
30
|
module Configuration
|
|
6
31
|
class << self
|
|
7
32
|
attr_accessor :hsts, :x_frame_options, :x_content_type_options,
|
|
@@ -24,6 +49,27 @@ module SecureHeaders
|
|
|
24
49
|
include InstanceMethods
|
|
25
50
|
end
|
|
26
51
|
end
|
|
52
|
+
|
|
53
|
+
def header_hash(options = nil)
|
|
54
|
+
ALL_HEADER_CLASSES.inject({}) do |memo, klass|
|
|
55
|
+
config = if options.is_a?(Hash) && options[klass::Constants::CONFIG_KEY]
|
|
56
|
+
options[klass::Constants::CONFIG_KEY]
|
|
57
|
+
else
|
|
58
|
+
::SecureHeaders::Configuration.send(klass::Constants::CONFIG_KEY)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless klass == SecureHeaders::PublicKeyPins && !config.is_a?(Hash)
|
|
62
|
+
header = get_a_header(klass::Constants::CONFIG_KEY, klass, config)
|
|
63
|
+
memo[header.name] = header.value
|
|
64
|
+
end
|
|
65
|
+
memo
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def get_a_header(name, klass, options)
|
|
70
|
+
return if options == false
|
|
71
|
+
klass.new(options)
|
|
72
|
+
end
|
|
27
73
|
end
|
|
28
74
|
|
|
29
75
|
module ClassMethods
|
|
@@ -39,6 +85,9 @@ module SecureHeaders
|
|
|
39
85
|
end
|
|
40
86
|
|
|
41
87
|
def ensure_security_headers options = {}
|
|
88
|
+
if RUBY_VERSION == "1.8.7"
|
|
89
|
+
warn "[DEPRECATION] secure_headers ruby 1.8.7 support will dropped in the next release"
|
|
90
|
+
end
|
|
42
91
|
self.secure_headers_options = options
|
|
43
92
|
before_filter :prep_script_hash
|
|
44
93
|
before_filter :set_hsts_header
|
|
@@ -50,12 +99,6 @@ module SecureHeaders
|
|
|
50
99
|
before_filter :set_x_download_options_header
|
|
51
100
|
before_filter :set_x_permitted_cross_domain_policies_header
|
|
52
101
|
end
|
|
53
|
-
|
|
54
|
-
# we can't use ||= because I'm overloading false => disable, nil => default
|
|
55
|
-
# both of which trigger the conditional assignment
|
|
56
|
-
def options_for(type, options)
|
|
57
|
-
options.nil? ? ::SecureHeaders::Configuration.send(type) : options
|
|
58
|
-
end
|
|
59
102
|
end
|
|
60
103
|
|
|
61
104
|
module InstanceMethods
|
|
@@ -80,7 +123,7 @@ module SecureHeaders
|
|
|
80
123
|
end
|
|
81
124
|
|
|
82
125
|
config = self.class.secure_headers_options[:csp] if config.nil?
|
|
83
|
-
config =
|
|
126
|
+
config = secure_header_options_for :csp, config
|
|
84
127
|
|
|
85
128
|
return if config == false
|
|
86
129
|
|
|
@@ -140,7 +183,7 @@ module SecureHeaders
|
|
|
140
183
|
|
|
141
184
|
def set_hpkp_header(options=self.class.secure_headers_options[:hpkp])
|
|
142
185
|
return unless request.ssl?
|
|
143
|
-
config =
|
|
186
|
+
config = secure_header_options_for :hpkp, options
|
|
144
187
|
|
|
145
188
|
return if config == false || config.nil?
|
|
146
189
|
|
|
@@ -158,12 +201,16 @@ module SecureHeaders
|
|
|
158
201
|
|
|
159
202
|
private
|
|
160
203
|
|
|
204
|
+
# we can't use ||= because I'm overloading false => disable, nil => default
|
|
205
|
+
# both of which trigger the conditional assignment
|
|
206
|
+
def secure_header_options_for(type, options)
|
|
207
|
+
options.nil? ? ::SecureHeaders::Configuration.send(type) : options
|
|
208
|
+
end
|
|
209
|
+
|
|
161
210
|
def set_a_header(name, klass, options=nil)
|
|
162
|
-
options =
|
|
211
|
+
options = secure_header_options_for(name, options)
|
|
163
212
|
return if options == false
|
|
164
|
-
|
|
165
|
-
header = klass.new(options)
|
|
166
|
-
set_header(header)
|
|
213
|
+
set_header(SecureHeaders::get_a_header(name, klass, options))
|
|
167
214
|
end
|
|
168
215
|
|
|
169
216
|
def set_header(name_or_header, value=nil)
|
|
@@ -176,18 +223,3 @@ module SecureHeaders
|
|
|
176
223
|
end
|
|
177
224
|
end
|
|
178
225
|
end
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
require "secure_headers/version"
|
|
182
|
-
require "secure_headers/header"
|
|
183
|
-
require "secure_headers/headers/public_key_pins"
|
|
184
|
-
require "secure_headers/headers/content_security_policy"
|
|
185
|
-
require "secure_headers/headers/x_frame_options"
|
|
186
|
-
require "secure_headers/headers/strict_transport_security"
|
|
187
|
-
require "secure_headers/headers/x_xss_protection"
|
|
188
|
-
require "secure_headers/headers/x_content_type_options"
|
|
189
|
-
require "secure_headers/headers/x_download_options"
|
|
190
|
-
require "secure_headers/headers/x_permitted_cross_domain_policies"
|
|
191
|
-
require "secure_headers/railtie"
|
|
192
|
-
require "secure_headers/hash_helper"
|
|
193
|
-
require "secure_headers/view_helper"
|
data/secure_headers.gemspec
CHANGED
|
@@ -19,5 +19,5 @@ Gem::Specification.new do |gem|
|
|
|
19
19
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
20
20
|
gem.require_paths = ["lib"]
|
|
21
21
|
gem.add_development_dependency "rake"
|
|
22
|
-
gem.
|
|
22
|
+
gem.add_dependency "user_agent_parser"
|
|
23
23
|
end
|
|
@@ -13,8 +13,8 @@ module SecureHeaders
|
|
|
13
13
|
:disable_fill_missing => true,
|
|
14
14
|
:default_src => 'https://*',
|
|
15
15
|
:report_uri => '/csp_report',
|
|
16
|
-
:script_src => 'inline eval https://* data:
|
|
17
|
-
:style_src => "inline https://* about:"
|
|
16
|
+
:script_src => "'unsafe-inline' 'unsafe-eval' https://* data:",
|
|
17
|
+
:style_src => "'unsafe-inline' https://* about:"
|
|
18
18
|
}
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -4,20 +4,21 @@ module SecureHeaders
|
|
|
4
4
|
describe ContentSecurityPolicy do
|
|
5
5
|
let(:default_opts) do
|
|
6
6
|
{
|
|
7
|
-
:disable_fill_missing => true,
|
|
8
7
|
:default_src => 'https:',
|
|
9
8
|
:report_uri => '/csp_report',
|
|
10
|
-
:script_src => 'inline eval https: data:
|
|
11
|
-
:style_src => "inline https: about:"
|
|
9
|
+
:script_src => "'unsafe-inline' 'unsafe-eval' https: data:",
|
|
10
|
+
:style_src => "'unsafe-inline' https: about:"
|
|
12
11
|
}
|
|
13
12
|
end
|
|
13
|
+
let(:controller) { DummyClass.new }
|
|
14
14
|
|
|
15
15
|
IE = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"
|
|
16
16
|
FIREFOX = "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/20121223 Ubuntu/9.25 (jaunty) Firefox/3.8"
|
|
17
17
|
FIREFOX_23 = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0"
|
|
18
18
|
CHROME = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4"
|
|
19
19
|
CHROME_25 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/537.22 (KHTML like Gecko) Chrome/25.0.1364.99 Safari/537.22"
|
|
20
|
-
|
|
20
|
+
SAFARI = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"
|
|
21
|
+
OPERA = "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16"
|
|
21
22
|
|
|
22
23
|
def request_for user_agent, request_uri=nil, options={:ssl => false}
|
|
23
24
|
double(:ssl? => options[:ssl], :env => {'HTTP_USER_AGENT' => user_agent}, :url => (request_uri || 'http://areallylongdomainexample.com') )
|
|
@@ -55,6 +56,23 @@ module SecureHeaders
|
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
58
|
|
|
59
|
+
it "exports a policy to JSON" do
|
|
60
|
+
policy = ContentSecurityPolicy.new(default_opts)
|
|
61
|
+
expected = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"],"style-src":["'unsafe-inline'","https:","about:"],"img-src":["https:","data:"]})
|
|
62
|
+
expect(policy.to_json).to eq(expected)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "imports JSON to build a policy" do
|
|
66
|
+
json1 = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"]})
|
|
67
|
+
json2 = %({"style-src":["'unsafe-inline'"],"img-src":["https:","data:"]})
|
|
68
|
+
json3 = %({"style-src":["https:","about:"]})
|
|
69
|
+
config = ContentSecurityPolicy.from_json(json1, json2, json3)
|
|
70
|
+
policy = ContentSecurityPolicy.new(config)
|
|
71
|
+
|
|
72
|
+
expected = %({"default-src":["https:"],"script-src":["'unsafe-inline'","'unsafe-eval'","https:","data:"],"style-src":["'unsafe-inline'","https:","about:"],"img-src":["https:","data:"]})
|
|
73
|
+
expect(policy.to_json).to eq(expected)
|
|
74
|
+
end
|
|
75
|
+
|
|
58
76
|
context "when using hash sources" do
|
|
59
77
|
it "adds hashes and unsafe-inline to the script-src" do
|
|
60
78
|
policy = ContentSecurityPolicy.new(default_opts.merge(:script_hashes => ['sha256-abc123']))
|
|
@@ -64,8 +82,7 @@ module SecureHeaders
|
|
|
64
82
|
|
|
65
83
|
describe "#normalize_csp_options" do
|
|
66
84
|
before(:each) do
|
|
67
|
-
default_opts
|
|
68
|
-
default_opts[:script_src] << ' self none'
|
|
85
|
+
default_opts[:script_src] << " 'self' 'none'"
|
|
69
86
|
@opts = default_opts
|
|
70
87
|
end
|
|
71
88
|
|
|
@@ -89,7 +106,7 @@ module SecureHeaders
|
|
|
89
106
|
|
|
90
107
|
it "accepts procs for report-uris" do
|
|
91
108
|
opts = {
|
|
92
|
-
:default_src => 'self',
|
|
109
|
+
:default_src => "'self'",
|
|
93
110
|
:report_uri => proc { "http://lambda/result" }
|
|
94
111
|
}
|
|
95
112
|
|
|
@@ -101,7 +118,6 @@ module SecureHeaders
|
|
|
101
118
|
opts = {
|
|
102
119
|
:default_src => proc { "http://lambda/result" },
|
|
103
120
|
:enforce => proc { true },
|
|
104
|
-
:disable_fill_missing => proc { true }
|
|
105
121
|
}
|
|
106
122
|
|
|
107
123
|
csp = ContentSecurityPolicy.new(opts)
|
|
@@ -115,8 +131,7 @@ module SecureHeaders
|
|
|
115
131
|
|
|
116
132
|
allow(controller).to receive(:current_user).and_return(user)
|
|
117
133
|
opts = {
|
|
118
|
-
:
|
|
119
|
-
:default_src => "self",
|
|
134
|
+
:default_src => "'self'",
|
|
120
135
|
:enforce => lambda { |c| c.current_user.beta_testing? }
|
|
121
136
|
}
|
|
122
137
|
csp = ContentSecurityPolicy.new(opts, :controller => controller)
|
|
@@ -133,35 +148,26 @@ module SecureHeaders
|
|
|
133
148
|
}.to raise_error(RuntimeError)
|
|
134
149
|
end
|
|
135
150
|
|
|
136
|
-
context "CSP level 2 directives" do
|
|
137
|
-
let(:config) { {:default_src => 'self'} }
|
|
138
|
-
::SecureHeaders::ContentSecurityPolicy::Constants::NON_DEFAULT_SOURCES.each do |non_default_source|
|
|
139
|
-
it "supports all level 2 directives" do
|
|
140
|
-
directive_name = ::SecureHeaders::ContentSecurityPolicy.send(:symbol_to_hyphen_case, non_default_source)
|
|
141
|
-
config.merge!({ non_default_source => "value" })
|
|
142
|
-
csp = ContentSecurityPolicy.new(config, :request => request_for(CHROME))
|
|
143
|
-
expect(csp.value).to match(/#{directive_name} value;/)
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
151
|
context "auto-whitelists data: uris for img-src" do
|
|
149
152
|
it "sets the value if no img-src specified" do
|
|
150
|
-
csp = ContentSecurityPolicy.new({:default_src => 'self'
|
|
153
|
+
csp = ContentSecurityPolicy.new({:default_src => "'self'"}, :request => request_for(CHROME))
|
|
151
154
|
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
152
155
|
end
|
|
153
156
|
|
|
154
157
|
it "appends the value if img-src is specified" do
|
|
155
|
-
csp = ContentSecurityPolicy.new({:default_src => 'self', :img_src => 'self'
|
|
158
|
+
csp = ContentSecurityPolicy.new({:default_src => "'self'", :img_src => "'self'"}, :request => request_for(CHROME))
|
|
156
159
|
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
157
160
|
end
|
|
158
|
-
end
|
|
159
161
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
it "doesn't add a duplicate data uri if img-src specifies it already" do
|
|
163
|
+
csp = ContentSecurityPolicy.new({:default_src => "'self'", :img_src => "'self' data:"}, :request => request_for(CHROME))
|
|
164
|
+
expect(csp.value).to eq("default-src 'self'; img-src 'self' data:;")
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "allows the user to disable img-src data: uris auto-whitelisting" do
|
|
168
|
+
csp = ContentSecurityPolicy.new({:default_src => "'self'", :img_src => "'self'", :disable_img_src_data_uri => true}, :request => request_for(CHROME))
|
|
169
|
+
expect(csp.value).to eq("default-src 'self'; img-src 'self';")
|
|
170
|
+
end
|
|
165
171
|
end
|
|
166
172
|
|
|
167
173
|
it "sends the standard csp header if an unknown browser is supplied" do
|
|
@@ -184,18 +190,40 @@ module SecureHeaders
|
|
|
184
190
|
end
|
|
185
191
|
|
|
186
192
|
context "when using a nonce" do
|
|
187
|
-
it "adds a nonce and unsafe-inline to the script-src value" do
|
|
188
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce"), :request => request_for(CHROME))
|
|
193
|
+
it "adds a nonce and unsafe-inline to the script-src value when using chrome" do
|
|
194
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(CHROME), :controller => controller)
|
|
195
|
+
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it "adds a nonce and unsafe-inline to the script-src value when using firefox" do
|
|
199
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(FIREFOX), :controller => controller)
|
|
200
|
+
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "adds a nonce and unsafe-inline to the script-src value when using opera" do
|
|
204
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(OPERA), :controller => controller)
|
|
189
205
|
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
190
206
|
end
|
|
191
207
|
|
|
208
|
+
it "does not add a nonce and unsafe-inline to the script-src value when using Safari" do
|
|
209
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(SAFARI), :controller => controller)
|
|
210
|
+
expect(header.value).to include("script-src 'self' 'unsafe-inline'")
|
|
211
|
+
expect(header.value).not_to include("nonce")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "does not add a nonce and unsafe-inline to the script-src value when using IE" do
|
|
215
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce"), :request => request_for(IE), :controller => controller)
|
|
216
|
+
expect(header.value).to include("script-src 'self' 'unsafe-inline'")
|
|
217
|
+
expect(header.value).not_to include("nonce")
|
|
218
|
+
end
|
|
219
|
+
|
|
192
220
|
it "adds a nonce and unsafe-inline to the style-src value" do
|
|
193
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce"), :request => request_for(CHROME))
|
|
221
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "'self' nonce"), :request => request_for(CHROME), :controller => controller)
|
|
194
222
|
expect(header.value).to include("style-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline'")
|
|
195
223
|
end
|
|
196
224
|
|
|
197
225
|
it "adds an identical nonce to the style and script-src directives" do
|
|
198
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "self nonce", :script_src => "self nonce"), :request => request_for(CHROME))
|
|
226
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:style_src => "'self' nonce", :script_src => "'self' nonce"), :request => request_for(CHROME), :controller => controller)
|
|
199
227
|
nonce = header.nonce
|
|
200
228
|
value = header.value
|
|
201
229
|
expect(value).to include("style-src 'self' 'nonce-#{nonce}' 'unsafe-inline'")
|
|
@@ -203,7 +231,7 @@ module SecureHeaders
|
|
|
203
231
|
end
|
|
204
232
|
|
|
205
233
|
it "does not add 'unsafe-inline' twice" do
|
|
206
|
-
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "self nonce inline"), :request => request_for(CHROME))
|
|
234
|
+
header = ContentSecurityPolicy.new(default_opts.merge(:script_src => "'self' nonce 'unsafe-inline'"), :request => request_for(CHROME), :controller => controller)
|
|
207
235
|
expect(header.value).to include("script-src 'self' 'nonce-#{header.nonce}' 'unsafe-inline';")
|
|
208
236
|
end
|
|
209
237
|
end
|
|
@@ -251,7 +279,7 @@ module SecureHeaders
|
|
|
251
279
|
|
|
252
280
|
describe ".add_to_env" do
|
|
253
281
|
let(:controller) { double }
|
|
254
|
-
let(:config) { {:default_src => 'self'} }
|
|
282
|
+
let(:config) { {:default_src => "'self'"} }
|
|
255
283
|
let(:options) { {:controller => controller} }
|
|
256
284
|
|
|
257
285
|
it "adds metadata to env" do
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe SecureHeaders do
|
|
4
|
-
class DummyClass
|
|
5
|
-
include ::SecureHeaders
|
|
6
|
-
end
|
|
7
|
-
|
|
8
4
|
subject {DummyClass.new}
|
|
9
5
|
let(:headers) {double}
|
|
10
6
|
let(:response) {double(:headers => headers)}
|
|
@@ -131,7 +127,7 @@ describe SecureHeaders do
|
|
|
131
127
|
|
|
132
128
|
it "saves the options to the env when using script hashes" do
|
|
133
129
|
opts = {
|
|
134
|
-
:default_src => 'self',
|
|
130
|
+
:default_src => "'self'",
|
|
135
131
|
:script_hash_middleware => true
|
|
136
132
|
}
|
|
137
133
|
stub_user_agent(USER_AGENTS[:chrome])
|
|
@@ -159,6 +155,56 @@ describe SecureHeaders do
|
|
|
159
155
|
end
|
|
160
156
|
end
|
|
161
157
|
|
|
158
|
+
describe "SecureHeaders#header_hash" do
|
|
159
|
+
def expect_default_values(hash)
|
|
160
|
+
expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
|
|
161
|
+
expect(hash[XDO_HEADER_NAME]).to eq(SecureHeaders::XDownloadOptions::Constants::DEFAULT_VALUE)
|
|
162
|
+
expect(hash[HSTS_HEADER_NAME]).to eq(SecureHeaders::StrictTransportSecurity::Constants::DEFAULT_VALUE)
|
|
163
|
+
expect(hash[X_XSS_PROTECTION_HEADER_NAME]).to eq(SecureHeaders::XXssProtection::Constants::DEFAULT_VALUE)
|
|
164
|
+
expect(hash[X_CONTENT_TYPE_OPTIONS_HEADER_NAME]).to eq(SecureHeaders::XContentTypeOptions::Constants::DEFAULT_VALUE)
|
|
165
|
+
expect(hash[XPCDP_HEADER_NAME]).to eq(SecureHeaders::XPermittedCrossDomainPolicies::Constants::DEFAULT_VALUE)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "produces a hash of headers given a hash as config" do
|
|
169
|
+
hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:", :disable_fill_missing => true})
|
|
170
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
|
|
171
|
+
expect_default_values(hash)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "produces a hash with a mix of config values, override values, and default values" do
|
|
175
|
+
::SecureHeaders::Configuration.configure do |config|
|
|
176
|
+
config.hsts = { :max_age => '123456'}
|
|
177
|
+
config.hpkp = {
|
|
178
|
+
:enforce => true,
|
|
179
|
+
:max_age => 1000000,
|
|
180
|
+
:include_subdomains => true,
|
|
181
|
+
:report_uri => '//example.com/uri-directive',
|
|
182
|
+
:pins => [
|
|
183
|
+
{:sha256 => 'abc'},
|
|
184
|
+
{:sha256 => '123'}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
hash = SecureHeaders::header_hash(:csp => {:default_src => "'none'", :img_src => "data:", :disable_fill_missing => true})
|
|
190
|
+
::SecureHeaders::Configuration.configure do |config|
|
|
191
|
+
config.hsts = nil
|
|
192
|
+
config.hpkp = nil
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq("default-src 'none'; img-src data:;")
|
|
196
|
+
expect(hash[XFO_HEADER_NAME]).to eq(SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
|
|
197
|
+
expect(hash[HSTS_HEADER_NAME]).to eq("max-age=123456")
|
|
198
|
+
expect(hash[HPKP_HEADER_NAME]).to eq(%{max-age=1000000; pin-sha256="abc"; pin-sha256="123"; report-uri="//example.com/uri-directive"; includeSubDomains})
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it "produces a hash of headers with default config" do
|
|
202
|
+
hash = SecureHeaders::header_hash
|
|
203
|
+
expect(hash['Content-Security-Policy-Report-Only']).to eq(SecureHeaders::ContentSecurityPolicy::Constants::DEFAULT_CSP_HEADER)
|
|
204
|
+
expect_default_values(hash)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
162
208
|
describe "#set_x_frame_options_header" do
|
|
163
209
|
it "sets the X-Frame-Options header" do
|
|
164
210
|
should_assign_header(XFO_HEADER_NAME, SecureHeaders::XFrameOptions::Constants::DEFAULT_VALUE)
|
data/spec/spec_helper.rb
CHANGED
|
@@ -3,8 +3,11 @@ require 'rspec'
|
|
|
3
3
|
|
|
4
4
|
require File.join(File.dirname(__FILE__), '..', 'lib', 'secure_headers')
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
begin
|
|
7
|
+
require 'coveralls'
|
|
7
8
|
Coveralls.wear!
|
|
9
|
+
rescue LoadError
|
|
10
|
+
# damn you 1.8.7
|
|
8
11
|
end
|
|
9
12
|
|
|
10
13
|
include ::SecureHeaders::PublicKeyPins::Constants
|
|
@@ -28,6 +31,10 @@ USER_AGENTS = {
|
|
|
28
31
|
: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"
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
class DummyClass
|
|
35
|
+
include ::SecureHeaders
|
|
36
|
+
end
|
|
37
|
+
|
|
31
38
|
def should_assign_header name, value
|
|
32
39
|
expect(response.headers).to receive(:[]=).with(name, value)
|
|
33
40
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: secure_headers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Neil Matatall
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2015-
|
|
11
|
+
date: 2015-10-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: user_agent_parser
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
27
41
|
description: Security related headers all in one gem.
|
|
28
42
|
email:
|
|
29
43
|
- neil.matatall@gmail.com
|
|
@@ -39,60 +53,60 @@ files:
|
|
|
39
53
|
- LICENSE
|
|
40
54
|
- README.md
|
|
41
55
|
- Rakefile
|
|
42
|
-
- fixtures/
|
|
43
|
-
- fixtures/
|
|
44
|
-
- fixtures/
|
|
45
|
-
- fixtures/
|
|
46
|
-
- fixtures/
|
|
47
|
-
- fixtures/
|
|
48
|
-
- fixtures/
|
|
49
|
-
- fixtures/
|
|
50
|
-
- fixtures/
|
|
51
|
-
- fixtures/
|
|
52
|
-
- fixtures/
|
|
53
|
-
- fixtures/
|
|
54
|
-
- fixtures/
|
|
55
|
-
- fixtures/
|
|
56
|
-
- fixtures/
|
|
57
|
-
- fixtures/
|
|
58
|
-
- fixtures/
|
|
59
|
-
- fixtures/
|
|
60
|
-
- fixtures/
|
|
61
|
-
- fixtures/
|
|
62
|
-
- fixtures/
|
|
63
|
-
- fixtures/
|
|
64
|
-
- fixtures/
|
|
65
|
-
- fixtures/
|
|
66
|
-
- fixtures/
|
|
67
|
-
- fixtures/
|
|
68
|
-
- fixtures/
|
|
69
|
-
- fixtures/
|
|
70
|
-
- fixtures/
|
|
71
|
-
- fixtures/
|
|
72
|
-
- fixtures/
|
|
73
|
-
- fixtures/
|
|
74
|
-
- fixtures/
|
|
75
|
-
- fixtures/
|
|
76
|
-
- fixtures/
|
|
77
|
-
- fixtures/
|
|
78
|
-
- fixtures/
|
|
79
|
-
- fixtures/
|
|
80
|
-
- fixtures/
|
|
81
|
-
- fixtures/
|
|
82
|
-
- fixtures/
|
|
83
|
-
- fixtures/
|
|
84
|
-
- fixtures/
|
|
85
|
-
- fixtures/
|
|
86
|
-
- fixtures/
|
|
87
|
-
- fixtures/
|
|
88
|
-
- fixtures/
|
|
89
|
-
- fixtures/
|
|
90
|
-
- fixtures/
|
|
91
|
-
- fixtures/
|
|
92
|
-
- fixtures/
|
|
93
|
-
- fixtures/
|
|
94
|
-
- fixtures/
|
|
95
|
-
- fixtures/
|
|
56
|
+
- fixtures/rails_3_2_22/.rspec
|
|
57
|
+
- fixtures/rails_3_2_22/Gemfile
|
|
58
|
+
- fixtures/rails_3_2_22/README.rdoc
|
|
59
|
+
- fixtures/rails_3_2_22/Rakefile
|
|
60
|
+
- fixtures/rails_3_2_22/app/controllers/application_controller.rb
|
|
61
|
+
- fixtures/rails_3_2_22/app/controllers/other_things_controller.rb
|
|
62
|
+
- fixtures/rails_3_2_22/app/controllers/things_controller.rb
|
|
63
|
+
- fixtures/rails_3_2_22/app/models/.gitkeep
|
|
64
|
+
- fixtures/rails_3_2_22/app/views/layouts/application.html.erb
|
|
65
|
+
- fixtures/rails_3_2_22/app/views/other_things/index.html.erb
|
|
66
|
+
- fixtures/rails_3_2_22/app/views/things/index.html.erb
|
|
67
|
+
- fixtures/rails_3_2_22/config.ru
|
|
68
|
+
- fixtures/rails_3_2_22/config/application.rb
|
|
69
|
+
- fixtures/rails_3_2_22/config/boot.rb
|
|
70
|
+
- fixtures/rails_3_2_22/config/environment.rb
|
|
71
|
+
- fixtures/rails_3_2_22/config/environments/test.rb
|
|
72
|
+
- fixtures/rails_3_2_22/config/initializers/secure_headers.rb
|
|
73
|
+
- fixtures/rails_3_2_22/config/routes.rb
|
|
74
|
+
- fixtures/rails_3_2_22/config/script_hashes.yml
|
|
75
|
+
- fixtures/rails_3_2_22/lib/assets/.gitkeep
|
|
76
|
+
- fixtures/rails_3_2_22/lib/tasks/.gitkeep
|
|
77
|
+
- fixtures/rails_3_2_22/log/.gitkeep
|
|
78
|
+
- fixtures/rails_3_2_22/spec/controllers/other_things_controller_spec.rb
|
|
79
|
+
- fixtures/rails_3_2_22/spec/controllers/things_controller_spec.rb
|
|
80
|
+
- fixtures/rails_3_2_22/spec/spec_helper.rb
|
|
81
|
+
- fixtures/rails_3_2_22/vendor/assets/javascripts/.gitkeep
|
|
82
|
+
- fixtures/rails_3_2_22/vendor/assets/stylesheets/.gitkeep
|
|
83
|
+
- fixtures/rails_3_2_22/vendor/plugins/.gitkeep
|
|
84
|
+
- fixtures/rails_3_2_22_no_init/.rspec
|
|
85
|
+
- fixtures/rails_3_2_22_no_init/Gemfile
|
|
86
|
+
- fixtures/rails_3_2_22_no_init/README.rdoc
|
|
87
|
+
- fixtures/rails_3_2_22_no_init/Rakefile
|
|
88
|
+
- fixtures/rails_3_2_22_no_init/app/controllers/application_controller.rb
|
|
89
|
+
- fixtures/rails_3_2_22_no_init/app/controllers/other_things_controller.rb
|
|
90
|
+
- fixtures/rails_3_2_22_no_init/app/controllers/things_controller.rb
|
|
91
|
+
- fixtures/rails_3_2_22_no_init/app/models/.gitkeep
|
|
92
|
+
- fixtures/rails_3_2_22_no_init/app/views/layouts/application.html.erb
|
|
93
|
+
- fixtures/rails_3_2_22_no_init/app/views/other_things/index.html.erb
|
|
94
|
+
- fixtures/rails_3_2_22_no_init/app/views/things/index.html.erb
|
|
95
|
+
- fixtures/rails_3_2_22_no_init/config.ru
|
|
96
|
+
- fixtures/rails_3_2_22_no_init/config/application.rb
|
|
97
|
+
- fixtures/rails_3_2_22_no_init/config/boot.rb
|
|
98
|
+
- fixtures/rails_3_2_22_no_init/config/environment.rb
|
|
99
|
+
- fixtures/rails_3_2_22_no_init/config/environments/test.rb
|
|
100
|
+
- fixtures/rails_3_2_22_no_init/config/routes.rb
|
|
101
|
+
- fixtures/rails_3_2_22_no_init/lib/assets/.gitkeep
|
|
102
|
+
- fixtures/rails_3_2_22_no_init/lib/tasks/.gitkeep
|
|
103
|
+
- fixtures/rails_3_2_22_no_init/log/.gitkeep
|
|
104
|
+
- fixtures/rails_3_2_22_no_init/spec/controllers/other_things_controller_spec.rb
|
|
105
|
+
- fixtures/rails_3_2_22_no_init/spec/controllers/things_controller_spec.rb
|
|
106
|
+
- fixtures/rails_3_2_22_no_init/spec/spec_helper.rb
|
|
107
|
+
- fixtures/rails_3_2_22_no_init/vendor/assets/javascripts/.gitkeep
|
|
108
|
+
- fixtures/rails_3_2_22_no_init/vendor/assets/stylesheets/.gitkeep
|
|
109
|
+
- fixtures/rails_3_2_22_no_init/vendor/plugins/.gitkeep
|
|
96
110
|
- fixtures/rails_4_1_8/Gemfile
|
|
97
111
|
- fixtures/rails_4_1_8/README.rdoc
|
|
98
112
|
- fixtures/rails_4_1_8/Rakefile
|
|
@@ -156,8 +170,7 @@ homepage: https://github.com/twitter/secureheaders
|
|
|
156
170
|
licenses:
|
|
157
171
|
- Apache Public License 2.0
|
|
158
172
|
metadata: {}
|
|
159
|
-
post_install_message:
|
|
160
|
-
|controller|. e.g. :enforce => lambda { |controller| some_expression }'
|
|
173
|
+
post_install_message:
|
|
161
174
|
rdoc_options: []
|
|
162
175
|
require_paths:
|
|
163
176
|
- lib
|
|
@@ -173,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
173
186
|
version: '0'
|
|
174
187
|
requirements: []
|
|
175
188
|
rubyforge_project:
|
|
176
|
-
rubygems_version: 2.
|
|
189
|
+
rubygems_version: 2.4.5.1
|
|
177
190
|
signing_key:
|
|
178
191
|
specification_version: 4
|
|
179
192
|
summary: Add easily configured security headers to responses including content-security-policy,
|