secure_headers 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -8
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +8 -3
- data/Gemfile +6 -8
- data/README.md +208 -134
- data/Rakefile +11 -116
- data/fixtures/rails_3_2_12/Gemfile +0 -8
- data/fixtures/rails_3_2_12/app/controllers/things_controller.rb +0 -1
- data/fixtures/rails_3_2_12/app/views/layouts/application.html.erb +1 -4
- data/fixtures/rails_3_2_12/app/views/other_things/index.html.erb +2 -1
- data/fixtures/rails_3_2_12/app/views/things/index.html.erb +1 -21
- data/fixtures/rails_3_2_12/config/application.rb +0 -54
- data/fixtures/rails_3_2_12/config/environments/test.rb +2 -2
- data/fixtures/rails_3_2_12/config/initializers/secure_headers.rb +3 -1
- data/fixtures/rails_3_2_12/config/routes.rb +0 -57
- data/fixtures/rails_3_2_12/config/script_hashes.yml +5 -0
- data/fixtures/rails_3_2_12/config.ru +3 -0
- data/fixtures/rails_3_2_12/spec/controllers/other_things_controller_spec.rb +57 -19
- data/fixtures/rails_3_2_12/spec/controllers/things_controller_spec.rb +18 -16
- data/fixtures/rails_3_2_12/spec/spec_helper.rb +1 -5
- data/fixtures/rails_3_2_12_no_init/Gemfile +0 -9
- data/fixtures/rails_3_2_12_no_init/app/controllers/other_things_controller.rb +1 -2
- data/fixtures/rails_3_2_12_no_init/app/controllers/things_controller.rb +1 -1
- data/fixtures/rails_3_2_12_no_init/app/views/layouts/application.html.erb +0 -2
- data/fixtures/rails_3_2_12_no_init/app/views/things/index.html.erb +0 -21
- data/fixtures/rails_3_2_12_no_init/config/application.rb +0 -51
- data/fixtures/rails_3_2_12_no_init/config/environments/test.rb +2 -2
- data/fixtures/rails_3_2_12_no_init/config/routes.rb +0 -57
- data/fixtures/rails_3_2_12_no_init/spec/controllers/other_things_controller_spec.rb +17 -12
- data/fixtures/rails_3_2_12_no_init/spec/controllers/things_controller_spec.rb +17 -12
- data/fixtures/rails_3_2_12_no_init/spec/spec_helper.rb +3 -18
- data/fixtures/rails_4_1_8/Gemfile +5 -0
- data/fixtures/rails_4_1_8/README.rdoc +28 -0
- data/fixtures/rails_4_1_8/Rakefile +6 -0
- data/fixtures/rails_4_1_8/app/controllers/application_controller.rb +4 -0
- data/fixtures/rails_4_1_8/app/controllers/concerns/.keep +0 -0
- data/fixtures/rails_4_1_8/app/controllers/other_things_controller.rb +5 -0
- data/fixtures/rails_4_1_8/app/controllers/things_controller.rb +5 -0
- data/fixtures/rails_4_1_8/app/models/.keep +0 -0
- data/fixtures/rails_4_1_8/app/models/concerns/.keep +0 -0
- data/fixtures/rails_4_1_8/app/views/layouts/application.html.erb +11 -0
- data/fixtures/rails_4_1_8/app/views/other_things/index.html.erb +2 -0
- data/fixtures/rails_4_1_8/app/views/things/index.html.erb +1 -0
- data/fixtures/rails_4_1_8/config/application.rb +15 -0
- data/fixtures/rails_4_1_8/config/boot.rb +4 -0
- data/fixtures/rails_4_1_8/config/environment.rb +5 -0
- data/fixtures/rails_4_1_8/config/environments/test.rb +10 -0
- data/fixtures/rails_4_1_8/config/initializers/secure_headers.rb +17 -0
- data/fixtures/rails_4_1_8/config/routes.rb +4 -0
- data/fixtures/rails_4_1_8/config/script_hashes.yml +5 -0
- data/fixtures/rails_4_1_8/config/secrets.yml +22 -0
- data/fixtures/rails_4_1_8/config.ru +4 -0
- data/fixtures/rails_4_1_8/lib/assets/.keep +0 -0
- data/fixtures/rails_4_1_8/lib/tasks/.keep +0 -0
- data/fixtures/rails_4_1_8/log/.keep +0 -0
- data/fixtures/rails_4_1_8/spec/controllers/other_things_controller_spec.rb +83 -0
- data/fixtures/rails_4_1_8/spec/controllers/things_controller_spec.rb +59 -0
- data/fixtures/rails_4_1_8/spec/spec_helper.rb +15 -0
- data/fixtures/rails_4_1_8/vendor/assets/javascripts/.keep +0 -0
- data/fixtures/rails_4_1_8/vendor/assets/stylesheets/.keep +0 -0
- data/lib/secure_headers/hash_helper.rb +7 -0
- data/lib/secure_headers/headers/content_security_policy/script_hash_middleware.rb +22 -0
- data/lib/secure_headers/headers/content_security_policy.rb +160 -161
- data/lib/secure_headers/headers/strict_transport_security.rb +2 -1
- data/lib/secure_headers/headers/x_download_options.rb +39 -0
- data/lib/secure_headers/headers/x_permitted_cross_domain_policies.rb +40 -0
- data/lib/secure_headers/headers/x_xss_protection.rb +2 -1
- data/lib/secure_headers/padrino.rb +14 -0
- data/lib/secure_headers/railtie.rb +19 -24
- data/lib/secure_headers/version.rb +1 -1
- data/lib/secure_headers/view_helper.rb +68 -0
- data/lib/secure_headers.rb +70 -29
- data/lib/tasks/tasks.rake +48 -0
- data/secure_headers.gemspec +4 -5
- data/spec/lib/secure_headers/headers/content_security_policy/script_hash_middleware_spec.rb +47 -0
- data/spec/lib/secure_headers/headers/content_security_policy_spec.rb +127 -299
- data/spec/lib/secure_headers/headers/strict_transport_security_spec.rb +21 -20
- data/spec/lib/secure_headers/headers/x_content_type_options_spec.rb +12 -12
- data/spec/lib/secure_headers/headers/x_download_options_spec.rb +32 -0
- data/spec/lib/secure_headers/headers/x_frame_options_spec.rb +12 -13
- data/spec/lib/secure_headers/headers/x_permitted_cross_domain_policies_spec.rb +64 -0
- data/spec/lib/secure_headers/headers/x_xss_protection_spec.rb +20 -19
- data/spec/lib/secure_headers_spec.rb +68 -83
- data/spec/spec_helper.rb +29 -18
- metadata +51 -66
- data/.rvmrc +0 -1
- data/Guardfile +0 -10
- data/HISTORY.md +0 -100
- data/app/controllers/content_security_policy_controller.rb +0 -53
- data/config/curl-ca-bundle.crt +0 -5420
- data/config/routes.rb +0 -3
- data/fixtures/rails_3_2_12/Guardfile +0 -14
- data/fixtures/rails_3_2_12/app/models/thing.rb +0 -3
- data/fixtures/rails_3_2_12/config/database.yml +0 -25
- data/fixtures/rails_3_2_12/config/environments/development.rb +0 -37
- data/fixtures/rails_3_2_12/config/environments/production.rb +0 -67
- data/fixtures/rails_3_2_12/config/initializers/backtrace_silencers.rb +0 -7
- data/fixtures/rails_3_2_12/config/initializers/inflections.rb +0 -15
- data/fixtures/rails_3_2_12/config/initializers/mime_types.rb +0 -5
- data/fixtures/rails_3_2_12/config/initializers/secret_token.rb +0 -7
- data/fixtures/rails_3_2_12/config/initializers/session_store.rb +0 -8
- data/fixtures/rails_3_2_12/config/initializers/wrap_parameters.rb +0 -14
- data/fixtures/rails_3_2_12/config/locales/en.yml +0 -5
- data/fixtures/rails_3_2_12/db/schema.rb +0 -16
- data/fixtures/rails_3_2_12/db/seeds.rb +0 -7
- data/fixtures/rails_3_2_12_no_init/Guardfile +0 -14
- data/fixtures/rails_3_2_12_no_init/app/models/thing.rb +0 -3
- data/fixtures/rails_3_2_12_no_init/app/views/things/_form.html.erb +0 -17
- data/fixtures/rails_3_2_12_no_init/app/views/things/edit.html.erb +0 -6
- data/fixtures/rails_3_2_12_no_init/app/views/things/new.html.erb +0 -5
- data/fixtures/rails_3_2_12_no_init/app/views/things/show.html.erb +0 -5
- data/fixtures/rails_3_2_12_no_init/config/database.yml +0 -25
- data/fixtures/rails_3_2_12_no_init/config/environments/development.rb +0 -37
- data/fixtures/rails_3_2_12_no_init/config/environments/production.rb +0 -67
- data/fixtures/rails_3_2_12_no_init/config/initializers/backtrace_silencers.rb +0 -7
- data/fixtures/rails_3_2_12_no_init/config/initializers/inflections.rb +0 -15
- data/fixtures/rails_3_2_12_no_init/config/initializers/mime_types.rb +0 -5
- data/fixtures/rails_3_2_12_no_init/config/initializers/secret_token.rb +0 -7
- data/fixtures/rails_3_2_12_no_init/config/initializers/session_store.rb +0 -8
- data/fixtures/rails_3_2_12_no_init/config/initializers/wrap_parameters.rb +0 -14
- data/fixtures/rails_3_2_12_no_init/config/locales/en.yml +0 -5
- data/fixtures/rails_3_2_12_no_init/db/schema.rb +0 -16
- data/fixtures/rails_3_2_12_no_init/db/seeds.rb +0 -7
- 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/spec/controllers/content_security_policy_controller_spec.rb +0 -74
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module SecureHeaders
|
|
2
|
+
class UnexpectedHashedScriptException < StandardError
|
|
3
|
+
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
module ViewHelpers
|
|
7
|
+
include SecureHeaders::HashHelper
|
|
8
|
+
SECURE_HEADERS_RAKE_TASK = "rake secure_headers:generate_hashes"
|
|
9
|
+
|
|
10
|
+
def nonced_style_tag(content = nil, &block)
|
|
11
|
+
nonced_tag(content, :style, block)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def nonced_javascript_tag(content = nil, &block)
|
|
15
|
+
nonced_tag(content, :script, block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def hashed_javascript_tag(raise_error_on_unrecognized_hash = false, &block)
|
|
19
|
+
content = capture(&block)
|
|
20
|
+
|
|
21
|
+
if ['development', 'test'].include?(ENV["RAILS_ENV"])
|
|
22
|
+
hash_value = hash_source(content)
|
|
23
|
+
file_path = File.join('app', 'views', self.instance_variable_get(:@virtual_path) + '.html.erb')
|
|
24
|
+
script_hashes = controller.instance_variable_get(:@script_hashes)[file_path]
|
|
25
|
+
unless script_hashes && script_hashes.include?(hash_value)
|
|
26
|
+
message = unexpected_hash_error_message(file_path, hash_value, content)
|
|
27
|
+
if raise_error_on_unrecognized_hash
|
|
28
|
+
raise UnexpectedHashedScriptException.new(message)
|
|
29
|
+
else
|
|
30
|
+
puts message
|
|
31
|
+
request.env[HASHES_ENV_KEY] = (request.env[HASHES_ENV_KEY] || []) << hash_value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
content_tag :script, content
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def nonced_tag(content, type, block)
|
|
42
|
+
content = if block
|
|
43
|
+
capture(&block)
|
|
44
|
+
else
|
|
45
|
+
content.html_safe # :'(
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
content_tag type, content, :nonce => @content_security_policy_nonce
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def unexpected_hash_error_message(file_path, hash_value, content)
|
|
52
|
+
<<-EOF
|
|
53
|
+
\n\n*** WARNING: Unrecognized hash in #{file_path}!!! Value: #{hash_value} ***
|
|
54
|
+
<script>#{content}</script>
|
|
55
|
+
*** This is fine in dev/test, but will raise exceptions in production. ***
|
|
56
|
+
*** Run #{SECURE_HEADERS_RAKE_TASK} or add the following to config/script_hashes.yml:***
|
|
57
|
+
#{file_path}:
|
|
58
|
+
- #{hash_value}\n\n
|
|
59
|
+
EOF
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module ActionView #:nodoc:
|
|
65
|
+
module Helpers #:nodoc:
|
|
66
|
+
include SecureHeaders::ViewHelpers
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/secure_headers.rb
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
module SecureHeaders
|
|
2
|
+
SCRIPT_HASH_CONFIG_FILE = 'config/script_hashes.yml'
|
|
3
|
+
HASHES_ENV_KEY = 'secure_headers.script_hashes'
|
|
4
|
+
|
|
2
5
|
module Configuration
|
|
3
6
|
class << self
|
|
4
7
|
attr_accessor :hsts, :x_frame_options, :x_content_type_options,
|
|
5
|
-
:x_xss_protection, :csp
|
|
8
|
+
:x_xss_protection, :csp, :x_download_options, :script_hashes,
|
|
9
|
+
:x_permitted_cross_domain_policies
|
|
6
10
|
|
|
7
11
|
def configure &block
|
|
8
12
|
instance_eval &block
|
|
13
|
+
if File.exists?(SCRIPT_HASH_CONFIG_FILE)
|
|
14
|
+
::SecureHeaders::Configuration.script_hashes = YAML.load(File.open(SCRIPT_HASH_CONFIG_FILE))
|
|
15
|
+
end
|
|
9
16
|
end
|
|
10
17
|
end
|
|
11
18
|
end
|
|
@@ -33,11 +40,14 @@ module SecureHeaders
|
|
|
33
40
|
|
|
34
41
|
def ensure_security_headers options = {}
|
|
35
42
|
self.secure_headers_options = options
|
|
43
|
+
before_filter :prep_script_hash
|
|
36
44
|
before_filter :set_hsts_header
|
|
37
45
|
before_filter :set_x_frame_options_header
|
|
38
46
|
before_filter :set_csp_header
|
|
39
47
|
before_filter :set_x_xss_protection_header
|
|
40
48
|
before_filter :set_x_content_type_options_header
|
|
49
|
+
before_filter :set_x_download_options_header
|
|
50
|
+
before_filter :set_x_permitted_cross_domain_policies_header
|
|
41
51
|
end
|
|
42
52
|
|
|
43
53
|
# we can't use ||= because I'm overloading false => disable, nil => default
|
|
@@ -48,35 +58,64 @@ module SecureHeaders
|
|
|
48
58
|
end
|
|
49
59
|
|
|
50
60
|
module InstanceMethods
|
|
51
|
-
def
|
|
52
|
-
|
|
61
|
+
def set_security_headers(options = self.class.secure_headers_options)
|
|
62
|
+
set_csp_header(request, options[:csp])
|
|
63
|
+
set_hsts_header(options[:hsts])
|
|
64
|
+
set_x_frame_options_header(options[:x_frame_options])
|
|
65
|
+
set_x_xss_protection_header(options[:x_xss_protection])
|
|
66
|
+
set_x_content_type_options_header(options[:x_content_type_options])
|
|
67
|
+
set_x_download_options_header(options[:x_download_options])
|
|
68
|
+
set_x_permitted_cross_domain_policies_header(options[:x_permitted_cross_domain_policies])
|
|
53
69
|
end
|
|
54
70
|
|
|
55
|
-
# backwards compatibility jank, to be removed in 1.0. Old API required a request
|
|
56
|
-
# object when it didn't really need to.
|
|
57
71
|
# set_csp_header - uses the request accessor and SecureHeader::Configuration settings
|
|
58
72
|
# set_csp_header(+Rack::Request+) - uses the parameter and and SecureHeader::Configuration settings
|
|
59
73
|
# set_csp_header(+Hash+) - uses the request accessor and options from parameters
|
|
60
74
|
# set_csp_header(+Rack::Request+, +Hash+)
|
|
61
|
-
def set_csp_header(req = nil,
|
|
62
|
-
|
|
75
|
+
def set_csp_header(req = nil, config=nil)
|
|
76
|
+
if req.is_a?(Hash) || req.is_a?(FalseClass)
|
|
77
|
+
config = req
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
config = self.class.secure_headers_options[:csp] if config.nil?
|
|
81
|
+
config = self.class.options_for :csp, config
|
|
63
82
|
|
|
64
|
-
if
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
return if config == false
|
|
84
|
+
|
|
85
|
+
if config && config[:script_hash_middleware]
|
|
86
|
+
ContentSecurityPolicy.add_to_env(request, self, config)
|
|
87
|
+
else
|
|
88
|
+
csp_header = ContentSecurityPolicy.new(config, :request => request, :controller => self)
|
|
89
|
+
set_header(csp_header)
|
|
68
90
|
end
|
|
91
|
+
end
|
|
69
92
|
|
|
70
|
-
options = self.class.secure_headers_options[:csp] if options.nil?
|
|
71
|
-
options = self.class.options_for :csp, options
|
|
72
93
|
|
|
73
|
-
|
|
94
|
+
def prep_script_hash
|
|
95
|
+
if ::SecureHeaders::Configuration.script_hashes
|
|
96
|
+
@script_hashes = ::SecureHeaders::Configuration.script_hashes.dup
|
|
97
|
+
ActiveSupport::Notifications.subscribe("render_partial.action_view") do |event_name, start_at, end_at, id, payload|
|
|
98
|
+
save_hash_for_later payload
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
ActiveSupport::Notifications.subscribe("render_template.action_view") do |event_name, start_at, end_at, id, payload|
|
|
102
|
+
save_hash_for_later payload
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
74
106
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
def save_hash_for_later payload
|
|
108
|
+
matching_hashes = @script_hashes[payload[:identifier].gsub(Rails.root.to_s + "/", "")] || []
|
|
109
|
+
|
|
110
|
+
if payload[:layout]
|
|
111
|
+
# 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
|
|
112
|
+
layout_hashes = @script_hashes[File.join("app", "views", payload[:layout]) + '.html.erb']
|
|
113
|
+
|
|
114
|
+
matching_hashes << layout_hashes if layout_hashes
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if matching_hashes.any?
|
|
118
|
+
request.env[HASHES_ENV_KEY] = ((request.env[HASHES_ENV_KEY] || []) << matching_hashes).flatten
|
|
80
119
|
end
|
|
81
120
|
end
|
|
82
121
|
|
|
@@ -85,7 +124,6 @@ module SecureHeaders
|
|
|
85
124
|
end
|
|
86
125
|
|
|
87
126
|
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
127
|
set_a_header(:x_content_type_options, XContentTypeOptions, options)
|
|
90
128
|
end
|
|
91
129
|
|
|
@@ -98,6 +136,14 @@ module SecureHeaders
|
|
|
98
136
|
set_a_header(:hsts, StrictTransportSecurity, options)
|
|
99
137
|
end
|
|
100
138
|
|
|
139
|
+
def set_x_download_options_header(options=self.class.secure_headers_options[:x_download_options])
|
|
140
|
+
set_a_header(:x_download_options, XDownloadOptions, options)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def set_x_permitted_cross_domain_policies_header(options=self.class.secure_headers_options[:x_permitted_cross_domain_policies])
|
|
144
|
+
set_a_header(:x_permitted_cross_domain_policies, XPermittedCrossDomainPolicies, options)
|
|
145
|
+
end
|
|
146
|
+
|
|
101
147
|
private
|
|
102
148
|
|
|
103
149
|
def set_a_header(name, klass, options=nil)
|
|
@@ -116,10 +162,6 @@ module SecureHeaders
|
|
|
116
162
|
response.headers[name_or_header] = value
|
|
117
163
|
end
|
|
118
164
|
end
|
|
119
|
-
|
|
120
|
-
def broken_implementation?(browser)
|
|
121
|
-
return browser.ios5? || (browser.safari? && browser.version == '5')
|
|
122
|
-
end
|
|
123
165
|
end
|
|
124
166
|
end
|
|
125
167
|
|
|
@@ -127,13 +169,12 @@ end
|
|
|
127
169
|
require "secure_headers/version"
|
|
128
170
|
require "secure_headers/header"
|
|
129
171
|
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
172
|
require "secure_headers/headers/x_frame_options"
|
|
135
173
|
require "secure_headers/headers/strict_transport_security"
|
|
136
174
|
require "secure_headers/headers/x_xss_protection"
|
|
137
175
|
require "secure_headers/headers/x_content_type_options"
|
|
176
|
+
require "secure_headers/headers/x_download_options"
|
|
177
|
+
require "secure_headers/headers/x_permitted_cross_domain_policies"
|
|
138
178
|
require "secure_headers/railtie"
|
|
139
|
-
require "
|
|
179
|
+
require "secure_headers/hash_helper"
|
|
180
|
+
require "secure_headers/view_helper"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
INLINE_SCRIPT_REGEX = /(<script(\s*(?!src)([\w\-])+=([\"\'])[^\"\']+\4)*\s*>)(.*?)<\/script>/mx
|
|
2
|
+
INLINE_HASH_HELPER_REGEX = /<%=\s?hashed_javascript_tag(.*?)\s+do\s?%>(.*?)<%\s*end\s*%>/mx
|
|
3
|
+
SCRIPT_HASH_CONFIG_FILE = 'config/script_hashes.yml'
|
|
4
|
+
|
|
5
|
+
namespace :secure_headers do
|
|
6
|
+
include SecureHeaders::HashHelper
|
|
7
|
+
|
|
8
|
+
def is_erb?(filename)
|
|
9
|
+
filename =~ /\.erb\Z/
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def generate_inline_script_hashes(filename)
|
|
13
|
+
file = File.read(filename)
|
|
14
|
+
hashes = []
|
|
15
|
+
|
|
16
|
+
[INLINE_SCRIPT_REGEX, INLINE_HASH_HELPER_REGEX].each do |regex|
|
|
17
|
+
file.gsub(regex) do # TODO don't use gsub
|
|
18
|
+
inline_script = Regexp.last_match.captures.last
|
|
19
|
+
if (filename =~ /\.mustache\Z/ && inline_script =~ /\{\{.*\}\}/) || (is_erb?(filename) && inline_script =~ /<%.*%>/)
|
|
20
|
+
puts "Looks like there's some dynamic content inside of a script tag :-/"
|
|
21
|
+
puts "That pretty much means the hash value will never match."
|
|
22
|
+
puts "Code: " + inline_script
|
|
23
|
+
puts "=" * 20
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
hashes << hash_source(inline_script)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
hashes
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
task :generate_hashes do |t, args|
|
|
34
|
+
script_hashes = {}
|
|
35
|
+
Dir.glob("app/{views,templates}/**/*.{erb,mustache}") do |filename|
|
|
36
|
+
hashes = generate_inline_script_hashes(filename)
|
|
37
|
+
if hashes.any?
|
|
38
|
+
script_hashes[filename] = hashes
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
File.open(SCRIPT_HASH_CONFIG_FILE, 'w') do |file|
|
|
43
|
+
file.write(script_hashes.to_yaml)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
puts "Script hashes from " + script_hashes.keys.size.to_s + " files added to #{SCRIPT_HASH_CONFIG_FILE}"
|
|
47
|
+
end
|
|
48
|
+
end
|
data/secure_headers.gemspec
CHANGED
|
@@ -8,16 +8,15 @@ Gem::Specification.new do |gem|
|
|
|
8
8
|
gem.version = SecureHeaders::VERSION
|
|
9
9
|
gem.authors = ["Neil Matatall"]
|
|
10
10
|
gem.email = ["neil.matatall@gmail.com"]
|
|
11
|
-
gem.description = %q{
|
|
12
|
-
gem.summary = %q{Add easily configured
|
|
13
|
-
including content
|
|
14
|
-
strict-transport-security
|
|
11
|
+
gem.description = %q{Security related headers all in one gem.}
|
|
12
|
+
gem.summary = %q{Add easily configured security headers to responses
|
|
13
|
+
including content-security-policy, x-frame-options,
|
|
14
|
+
strict-transport-security, etc.}
|
|
15
15
|
gem.homepage = "https://github.com/twitter/secureheaders"
|
|
16
16
|
gem.license = "Apache Public License 2.0"
|
|
17
17
|
gem.files = `git ls-files`.split($/)
|
|
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
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'secure_headers/headers/content_security_policy/script_hash_middleware'
|
|
3
|
+
|
|
4
|
+
module SecureHeaders
|
|
5
|
+
describe ContentSecurityPolicy::ScriptHashMiddleware do
|
|
6
|
+
|
|
7
|
+
let(:app) { double(:call => [200, headers, '']) }
|
|
8
|
+
let(:env) { double }
|
|
9
|
+
let(:headers) { double }
|
|
10
|
+
|
|
11
|
+
let(:default_config) do
|
|
12
|
+
{
|
|
13
|
+
:disable_fill_missing => true,
|
|
14
|
+
:default_src => 'https://*',
|
|
15
|
+
:report_uri => '/csp_report',
|
|
16
|
+
:script_src => 'inline eval https://* data:',
|
|
17
|
+
:style_src => "inline https://* about:"
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def should_assign_header name, value
|
|
22
|
+
expect(headers).to receive(:[]=).with(name, value)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call_middleware(hashes = [])
|
|
26
|
+
options = {
|
|
27
|
+
:ua => USER_AGENTS[:chrome]
|
|
28
|
+
}
|
|
29
|
+
expect(env).to receive(:[]).with(HASHES_ENV_KEY).and_return(hashes)
|
|
30
|
+
expect(env).to receive(:[]).with(ENV_KEY).and_return(
|
|
31
|
+
:config => default_config,
|
|
32
|
+
:options => options
|
|
33
|
+
)
|
|
34
|
+
ContentSecurityPolicy::ScriptHashMiddleware.new(app).call(env)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "adds hashes stored in env to the header" do
|
|
38
|
+
should_assign_header(HEADER_NAME + "-Report-Only", /script-src[^;]*'sha256-/)
|
|
39
|
+
call_middleware(['sha256-abc123'])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "leaves things alone when no hashes are saved to env" do
|
|
43
|
+
should_assign_header(HEADER_NAME + "-Report-Only", /script-src[^;]*(?!'sha256-)/)
|
|
44
|
+
call_middleware()
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|