secure_headers 2.5.2 → 2.5.3
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.
- checksums.yaml +4 -4
- data/Gemfile +5 -1
- data/README.md +2 -2
- data/lib/secure_headers/controller_extension.rb +158 -0
- data/lib/secure_headers/railtie.rb +2 -2
- data/lib/secure_headers/version.rb +1 -1
- data/lib/secure_headers.rb +2 -149
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 294b9a718031dad10faeef1232529098e5ca6b1d
|
4
|
+
data.tar.gz: 4e937445b4e743d1e55934cf7355d99f793dd9cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1873f2f27ad028125dbef4aafb27cb2b95dfdac50521e33003771ac1a62de0543fbc4f8dac1ca2001df6d0264f108ab0d3ad70572c26519da6a43b1d3bb0ea11
|
7
|
+
data.tar.gz: df5a83f54557f074f8da741b869844fabf8ae55657c08cf01d0aa3abbb01062187bbafa45061b2b69cdc9ca2606e7c819f763ec28e84527a933b8f057a172132
|
data/Gemfile
CHANGED
@@ -3,7 +3,11 @@ source 'https://rubygems.org'
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
group :test do
|
6
|
-
gem
|
6
|
+
gem 'listen', '<= 3.0.8', :platforms => [:ruby_19, :ruby_20, :ruby_22]
|
7
|
+
gem "term-ansicolor", "< 1.4"
|
8
|
+
gem "tins", "< 1.3.4"
|
9
|
+
|
10
|
+
gem "guard-rspec", :platforms => [:ruby_19, :ruby_20, :ruby_21, :ruby_22]
|
7
11
|
gem 'test-unit', '~> 3.0'
|
8
12
|
gem 'rails', '3.2.22'
|
9
13
|
gem 'sqlite3', :platforms => [:ruby, :mswin, :mingw]
|
data/README.md
CHANGED
@@ -48,7 +48,7 @@ The following methods are going to be called, unless they are provided in a `ski
|
|
48
48
|
:enforce => proc {|controller| controller.my_feature_flag_api.enabled? },
|
49
49
|
:frame_src => "https: http:.twimg.com http://itunes.apple.com",
|
50
50
|
:img_src => "https:",
|
51
|
-
:connect_src => "wws:"
|
51
|
+
:connect_src => "wws:",
|
52
52
|
:font_src => "'self' data:",
|
53
53
|
:frame_src => "'self'",
|
54
54
|
:img_src => "mycdn.com data:",
|
@@ -61,7 +61,7 @@ The following methods are going to be called, unless they are provided in a `ski
|
|
61
61
|
:form_action => "'self' github.com",
|
62
62
|
:frame_ancestors => "'none'",
|
63
63
|
:plugin_types => 'application/x-shockwave-flash',
|
64
|
-
:block_all_mixed_content => '' # see [http://www.w3.org/TR/mixed-content/]()
|
64
|
+
:block_all_mixed_content => '', # see [http://www.w3.org/TR/mixed-content/]()
|
65
65
|
:report_uri => '//example.com/uri-directive'
|
66
66
|
}
|
67
67
|
config.hpkp = {
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module SecureHeaders
|
2
|
+
module ControllerExtension
|
3
|
+
class << self
|
4
|
+
def append_features(base)
|
5
|
+
base.module_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
include InstanceMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
attr_writer :secure_headers_options
|
14
|
+
def secure_headers_options
|
15
|
+
if @secure_headers_options
|
16
|
+
@secure_headers_options
|
17
|
+
elsif superclass.respond_to?(:secure_headers_options) # stop at application_controller
|
18
|
+
superclass.secure_headers_options
|
19
|
+
else
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def ensure_security_headers options = {}
|
25
|
+
if RUBY_VERSION == "1.8.7"
|
26
|
+
warn "[DEPRECATION] secure_headers ruby 1.8.7 support will dropped in the next release"
|
27
|
+
end
|
28
|
+
self.secure_headers_options = options
|
29
|
+
hook = respond_to?(:before_action) ? :before_action : :before_filter
|
30
|
+
::SecureHeaders::ALL_FILTER_METHODS.each do |method|
|
31
|
+
send(hook, method)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module InstanceMethods
|
37
|
+
def set_security_headers(options = self.class.secure_headers_options)
|
38
|
+
set_csp_header(request, options[:csp])
|
39
|
+
set_hsts_header(options[:hsts])
|
40
|
+
set_hpkp_header(options[:hpkp])
|
41
|
+
set_x_frame_options_header(options[:x_frame_options])
|
42
|
+
set_x_xss_protection_header(options[:x_xss_protection])
|
43
|
+
set_x_content_type_options_header(options[:x_content_type_options])
|
44
|
+
set_x_download_options_header(options[:x_download_options])
|
45
|
+
set_x_permitted_cross_domain_policies_header(options[:x_permitted_cross_domain_policies])
|
46
|
+
end
|
47
|
+
|
48
|
+
# set_csp_header - uses the request accessor and SecureHeader::Configuration settings
|
49
|
+
# set_csp_header(+Rack::Request+) - uses the parameter and and SecureHeader::Configuration settings
|
50
|
+
# set_csp_header(+Hash+) - uses the request accessor and options from parameters
|
51
|
+
# set_csp_header(+Rack::Request+, +Hash+)
|
52
|
+
def set_csp_header(req = nil, config=nil)
|
53
|
+
if req.is_a?(Hash) || req.is_a?(FalseClass)
|
54
|
+
config = req
|
55
|
+
end
|
56
|
+
|
57
|
+
config = self.class.secure_headers_options[:csp] if config.nil?
|
58
|
+
config = secure_header_options_for :csp, config
|
59
|
+
|
60
|
+
return if config == false
|
61
|
+
|
62
|
+
if config && config[:script_hash_middleware]
|
63
|
+
ContentSecurityPolicy.add_to_env(request, self, config)
|
64
|
+
else
|
65
|
+
csp_header = ContentSecurityPolicy.new(config, :request => request, :controller => self)
|
66
|
+
set_header(csp_header)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def prep_script_hash
|
72
|
+
if ::SecureHeaders::Configuration.script_hashes
|
73
|
+
@script_hashes = ::SecureHeaders::Configuration.script_hashes.dup
|
74
|
+
ActiveSupport::Notifications.subscribe("render_partial.action_view") do |event_name, start_at, end_at, id, payload|
|
75
|
+
save_hash_for_later payload
|
76
|
+
end
|
77
|
+
|
78
|
+
ActiveSupport::Notifications.subscribe("render_template.action_view") do |event_name, start_at, end_at, id, payload|
|
79
|
+
save_hash_for_later payload
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def save_hash_for_later payload
|
85
|
+
matching_hashes = @script_hashes[payload[:identifier].gsub(Rails.root.to_s + "/", "")] || []
|
86
|
+
|
87
|
+
if payload[:layout]
|
88
|
+
# We're assuming an html.erb layout for now. Will need to handle mustache too, just not sure of the best way to do this
|
89
|
+
layout_hashes = @script_hashes[File.join("app", "views", payload[:layout]) + '.html.erb']
|
90
|
+
|
91
|
+
matching_hashes << layout_hashes if layout_hashes
|
92
|
+
end
|
93
|
+
|
94
|
+
if matching_hashes.any?
|
95
|
+
request.env[HASHES_ENV_KEY] = ((request.env[HASHES_ENV_KEY] || []) << matching_hashes).flatten
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def set_x_frame_options_header(options=self.class.secure_headers_options[:x_frame_options])
|
100
|
+
set_a_header(:x_frame_options, XFrameOptions, options)
|
101
|
+
end
|
102
|
+
|
103
|
+
def set_x_content_type_options_header(options=self.class.secure_headers_options[:x_content_type_options])
|
104
|
+
set_a_header(:x_content_type_options, XContentTypeOptions, options)
|
105
|
+
end
|
106
|
+
|
107
|
+
def set_x_xss_protection_header(options=self.class.secure_headers_options[:x_xss_protection])
|
108
|
+
set_a_header(:x_xss_protection, XXssProtection, options)
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_hsts_header(options=self.class.secure_headers_options[:hsts])
|
112
|
+
return unless request.ssl?
|
113
|
+
set_a_header(:hsts, StrictTransportSecurity, options)
|
114
|
+
end
|
115
|
+
|
116
|
+
def set_hpkp_header(options=self.class.secure_headers_options[:hpkp])
|
117
|
+
return unless request.ssl?
|
118
|
+
config = secure_header_options_for :hpkp, options
|
119
|
+
|
120
|
+
return if config == false || config.nil?
|
121
|
+
|
122
|
+
hpkp_header = PublicKeyPins.new(config)
|
123
|
+
set_header(hpkp_header)
|
124
|
+
end
|
125
|
+
|
126
|
+
def set_x_download_options_header(options=self.class.secure_headers_options[:x_download_options])
|
127
|
+
set_a_header(:x_download_options, XDownloadOptions, options)
|
128
|
+
end
|
129
|
+
|
130
|
+
def set_x_permitted_cross_domain_policies_header(options=self.class.secure_headers_options[:x_permitted_cross_domain_policies])
|
131
|
+
set_a_header(:x_permitted_cross_domain_policies, XPermittedCrossDomainPolicies, options)
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# we can't use ||= because I'm overloading false => disable, nil => default
|
137
|
+
# both of which trigger the conditional assignment
|
138
|
+
def secure_header_options_for(type, options)
|
139
|
+
options.nil? ? ::SecureHeaders::Configuration.send(type) : options
|
140
|
+
end
|
141
|
+
|
142
|
+
def set_a_header(name, klass, options=nil)
|
143
|
+
options = secure_header_options_for(name, options)
|
144
|
+
return if options == false
|
145
|
+
set_header(SecureHeaders::get_a_header(klass, options))
|
146
|
+
end
|
147
|
+
|
148
|
+
def set_header(name_or_header, value=nil)
|
149
|
+
if name_or_header.is_a?(Header)
|
150
|
+
header = name_or_header
|
151
|
+
response.headers[header.name] = header.value
|
152
|
+
else
|
153
|
+
response.headers[name_or_header] = value
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -11,7 +11,7 @@ if defined?(Rails::Railtie)
|
|
11
11
|
|
12
12
|
initializer "secure_headers.action_controller" do
|
13
13
|
ActiveSupport.on_load(:action_controller) do
|
14
|
-
include ::SecureHeaders
|
14
|
+
include ::SecureHeaders::ControllerExtension
|
15
15
|
|
16
16
|
unless Rails.application.config.action_dispatch.default_headers.nil?
|
17
17
|
conflicting_headers.each do |header|
|
@@ -26,7 +26,7 @@ if defined?(Rails::Railtie)
|
|
26
26
|
else
|
27
27
|
module ActionController
|
28
28
|
class Base
|
29
|
-
include ::SecureHeaders
|
29
|
+
include ::SecureHeaders::ControllerExtension
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
data/lib/secure_headers.rb
CHANGED
@@ -8,6 +8,7 @@ require "secure_headers/headers/x_xss_protection"
|
|
8
8
|
require "secure_headers/headers/x_content_type_options"
|
9
9
|
require "secure_headers/headers/x_download_options"
|
10
10
|
require "secure_headers/headers/x_permitted_cross_domain_policies"
|
11
|
+
require "secure_headers/controller_extension"
|
11
12
|
require "secure_headers/railtie"
|
12
13
|
require "secure_headers/hash_helper"
|
13
14
|
require "secure_headers/view_helper"
|
@@ -62,10 +63,7 @@ module SecureHeaders
|
|
62
63
|
|
63
64
|
class << self
|
64
65
|
def append_features(base)
|
65
|
-
base.
|
66
|
-
extend ClassMethods
|
67
|
-
include InstanceMethods
|
68
|
-
end
|
66
|
+
base.send(:include, ControllerExtension)
|
69
67
|
end
|
70
68
|
|
71
69
|
def header_hash(options = nil)
|
@@ -92,149 +90,4 @@ module SecureHeaders
|
|
92
90
|
end
|
93
91
|
end
|
94
92
|
|
95
|
-
module ClassMethods
|
96
|
-
attr_writer :secure_headers_options
|
97
|
-
def secure_headers_options
|
98
|
-
if @secure_headers_options
|
99
|
-
@secure_headers_options
|
100
|
-
elsif superclass.respond_to?(:secure_headers_options) # stop at application_controller
|
101
|
-
superclass.secure_headers_options
|
102
|
-
else
|
103
|
-
{}
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def ensure_security_headers options = {}
|
108
|
-
if RUBY_VERSION == "1.8.7"
|
109
|
-
warn "[DEPRECATION] secure_headers ruby 1.8.7 support will dropped in the next release"
|
110
|
-
end
|
111
|
-
self.secure_headers_options = options
|
112
|
-
hook = respond_to?(:before_action) ? :before_action : :before_filter
|
113
|
-
ALL_FILTER_METHODS.each do |method|
|
114
|
-
send(hook, method)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
module InstanceMethods
|
120
|
-
def set_security_headers(options = self.class.secure_headers_options)
|
121
|
-
set_csp_header(request, options[:csp])
|
122
|
-
set_hsts_header(options[:hsts])
|
123
|
-
set_hpkp_header(options[:hpkp])
|
124
|
-
set_x_frame_options_header(options[:x_frame_options])
|
125
|
-
set_x_xss_protection_header(options[:x_xss_protection])
|
126
|
-
set_x_content_type_options_header(options[:x_content_type_options])
|
127
|
-
set_x_download_options_header(options[:x_download_options])
|
128
|
-
set_x_permitted_cross_domain_policies_header(options[:x_permitted_cross_domain_policies])
|
129
|
-
end
|
130
|
-
|
131
|
-
# set_csp_header - uses the request accessor and SecureHeader::Configuration settings
|
132
|
-
# set_csp_header(+Rack::Request+) - uses the parameter and and SecureHeader::Configuration settings
|
133
|
-
# set_csp_header(+Hash+) - uses the request accessor and options from parameters
|
134
|
-
# set_csp_header(+Rack::Request+, +Hash+)
|
135
|
-
def set_csp_header(req = nil, config=nil)
|
136
|
-
if req.is_a?(Hash) || req.is_a?(FalseClass)
|
137
|
-
config = req
|
138
|
-
end
|
139
|
-
|
140
|
-
config = self.class.secure_headers_options[:csp] if config.nil?
|
141
|
-
config = secure_header_options_for :csp, config
|
142
|
-
|
143
|
-
return if config == false
|
144
|
-
|
145
|
-
if config && config[:script_hash_middleware]
|
146
|
-
ContentSecurityPolicy.add_to_env(request, self, config)
|
147
|
-
else
|
148
|
-
csp_header = ContentSecurityPolicy.new(config, :request => request, :controller => self)
|
149
|
-
set_header(csp_header)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
|
154
|
-
def prep_script_hash
|
155
|
-
if ::SecureHeaders::Configuration.script_hashes
|
156
|
-
@script_hashes = ::SecureHeaders::Configuration.script_hashes.dup
|
157
|
-
ActiveSupport::Notifications.subscribe("render_partial.action_view") do |event_name, start_at, end_at, id, payload|
|
158
|
-
save_hash_for_later payload
|
159
|
-
end
|
160
|
-
|
161
|
-
ActiveSupport::Notifications.subscribe("render_template.action_view") do |event_name, start_at, end_at, id, payload|
|
162
|
-
save_hash_for_later payload
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
def save_hash_for_later payload
|
168
|
-
matching_hashes = @script_hashes[payload[:identifier].gsub(Rails.root.to_s + "/", "")] || []
|
169
|
-
|
170
|
-
if payload[:layout]
|
171
|
-
# We're assuming an html.erb layout for now. Will need to handle mustache too, just not sure of the best way to do this
|
172
|
-
layout_hashes = @script_hashes[File.join("app", "views", payload[:layout]) + '.html.erb']
|
173
|
-
|
174
|
-
matching_hashes << layout_hashes if layout_hashes
|
175
|
-
end
|
176
|
-
|
177
|
-
if matching_hashes.any?
|
178
|
-
request.env[HASHES_ENV_KEY] = ((request.env[HASHES_ENV_KEY] || []) << matching_hashes).flatten
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
def set_x_frame_options_header(options=self.class.secure_headers_options[:x_frame_options])
|
183
|
-
set_a_header(:x_frame_options, XFrameOptions, options)
|
184
|
-
end
|
185
|
-
|
186
|
-
def set_x_content_type_options_header(options=self.class.secure_headers_options[:x_content_type_options])
|
187
|
-
set_a_header(:x_content_type_options, XContentTypeOptions, options)
|
188
|
-
end
|
189
|
-
|
190
|
-
def set_x_xss_protection_header(options=self.class.secure_headers_options[:x_xss_protection])
|
191
|
-
set_a_header(:x_xss_protection, XXssProtection, options)
|
192
|
-
end
|
193
|
-
|
194
|
-
def set_hsts_header(options=self.class.secure_headers_options[:hsts])
|
195
|
-
return unless request.ssl?
|
196
|
-
set_a_header(:hsts, StrictTransportSecurity, options)
|
197
|
-
end
|
198
|
-
|
199
|
-
def set_hpkp_header(options=self.class.secure_headers_options[:hpkp])
|
200
|
-
return unless request.ssl?
|
201
|
-
config = secure_header_options_for :hpkp, options
|
202
|
-
|
203
|
-
return if config == false || config.nil?
|
204
|
-
|
205
|
-
hpkp_header = PublicKeyPins.new(config)
|
206
|
-
set_header(hpkp_header)
|
207
|
-
end
|
208
|
-
|
209
|
-
def set_x_download_options_header(options=self.class.secure_headers_options[:x_download_options])
|
210
|
-
set_a_header(:x_download_options, XDownloadOptions, options)
|
211
|
-
end
|
212
|
-
|
213
|
-
def set_x_permitted_cross_domain_policies_header(options=self.class.secure_headers_options[:x_permitted_cross_domain_policies])
|
214
|
-
set_a_header(:x_permitted_cross_domain_policies, XPermittedCrossDomainPolicies, options)
|
215
|
-
end
|
216
|
-
|
217
|
-
private
|
218
|
-
|
219
|
-
# we can't use ||= because I'm overloading false => disable, nil => default
|
220
|
-
# both of which trigger the conditional assignment
|
221
|
-
def secure_header_options_for(type, options)
|
222
|
-
options.nil? ? ::SecureHeaders::Configuration.send(type) : options
|
223
|
-
end
|
224
|
-
|
225
|
-
def set_a_header(name, klass, options=nil)
|
226
|
-
options = secure_header_options_for(name, options)
|
227
|
-
return if options == false
|
228
|
-
set_header(SecureHeaders::get_a_header(klass, options))
|
229
|
-
end
|
230
|
-
|
231
|
-
def set_header(name_or_header, value=nil)
|
232
|
-
if name_or_header.is_a?(Header)
|
233
|
-
header = name_or_header
|
234
|
-
response.headers[header.name] = header.value
|
235
|
-
else
|
236
|
-
response.headers[name_or_header] = value
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
93
|
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.5.
|
4
|
+
version: 2.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Matatall
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -138,6 +138,7 @@ files:
|
|
138
138
|
- fixtures/rails_4_1_8/vendor/assets/javascripts/.keep
|
139
139
|
- fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep
|
140
140
|
- lib/secure_headers.rb
|
141
|
+
- lib/secure_headers/controller_extension.rb
|
141
142
|
- lib/secure_headers/hash_helper.rb
|
142
143
|
- lib/secure_headers/header.rb
|
143
144
|
- lib/secure_headers/headers/content_security_policy.rb
|