Skip to content

Commit 7a71c35

Browse files
authored
Use correct CSP nonce behavior when 'unsafe-inline' is set (#1041)
* fix: use correct CSP nonce behavior when 'unsafe-inline' is set * chore: pin rexml to ruby v2.0 compatible version for Rails 3 ci config * style: remove historical context from comment
1 parent c2de2cf commit 7a71c35

File tree

13 files changed

+232
-16
lines changed

13 files changed

+232
-16
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ gem 'redis'
7474
gem 'resque', '< 2.0.0'
7575
gem 'rubocop', '~> 0.93.0', :require => false
7676
gem 'rubocop-performance', :require => false
77+
gem 'secure_headers', '~> 6.3.2', :require => false
7778
gem 'sinatra'
7879
gem 'webmock', :require => false
7980
gemspec

gemfiles/rails50.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ gem 'generator_spec'
4545
gem 'girl_friday', '>= 0.11.1'
4646
gem 'redis', '<= 3.3.5'
4747
gem 'resque'
48+
gem 'secure_headers', '~> 6.3.2', :require => false
4849

4950
unless is_jruby
5051
# JRuby doesn't support fork, which is required for this test helper.

gemfiles/rails51.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ gem 'generator_spec'
4646
gem 'girl_friday', '>= 0.11.1'
4747
gem 'redis', '<= 3.3.5'
4848
gem 'resque'
49+
gem 'secure_headers', '~> 6.3.2', :require => false
4950

5051
unless is_jruby
5152
# JRuby doesn't support fork, which is required for this test helper.

gemfiles/rails52.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ gem 'generator_spec'
3838
gem 'girl_friday', '>= 0.11.1'
3939
gem 'redis'
4040
gem 'resque'
41+
gem 'secure_headers', '~> 6.3.2', :require => false
4142
gem 'simplecov', '<= 0.17.1'
4243

4344
unless is_jruby

gemfiles/rails60.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ gem 'generator_spec'
3535
gem 'girl_friday', '>= 0.11.1'
3636
gem 'redis'
3737
gem 'resque'
38+
gem 'secure_headers', '~> 6.3.2', :require => false
3839
gem 'simplecov'
3940

4041
unless is_jruby

gemfiles/rails61.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ gem 'generator_spec'
3434
gem 'girl_friday', '>= 0.11.1'
3535
gem 'redis'
3636
gem 'resque'
37+
gem 'secure_headers', '~> 6.3.2', :require => false
3738
gem 'simplecov'
3839

3940
unless is_jruby

lib/rollbar/middleware/js.rb

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,7 @@ def js_snippet
157157
def script_tag(content, env)
158158
if (nonce = rails5_nonce(env))
159159
script_tag_content = "\n<script type=\"text/javascript\" nonce=\"#{nonce}\">#{content}</script>"
160-
elsif secure_headers_nonce?
161-
nonce = ::SecureHeaders.content_security_policy_script_nonce(::Rack::Request.new(env))
160+
elsif (nonce = secure_headers_nonce(env))
162161
script_tag_content = "\n<script type=\"text/javascript\" nonce=\"#{nonce}\">#{content}</script>"
163162
else
164163
script_tag_content = "\n<script type=\"text/javascript\">#{content}</script>"
@@ -172,28 +171,40 @@ def html_safe_if_needed(string)
172171
string
173172
end
174173

175-
# Rails 5.2 Secure Content Policy
174+
# Rails 5.2+ Secure Content Policy
176175
def rails5_nonce(env)
177-
# The nonce is the preferred method, however 'unsafe-inline' is also possible.
178-
# The app gets to decide, so we handle both. If the script_src key is missing,
179-
# Rails will not add the nonce to the headers, so we should not add it either.
180-
# If the 'unsafe-inline' value is present, the app should not add a nonce and
181-
# we should ignore it if they do.
182-
req = ::ActionDispatch::Request.new env
176+
req = ::ActionDispatch::Request.new(env)
177+
178+
# Rails will only return a nonce if the app has set a nonce generator.
179+
# So if we get a valid nonce here, we know we should use it.
180+
#
181+
# Having both 'unsafe-inline' and a nonce is a valid and preferred
182+
# browser compatibility configuration.
183+
#
184+
# If the script_src key is missing, Rails will not add the nonce to the headers,
185+
# so we detect this and will not add it in this case.
183186
req.respond_to?(:content_security_policy) &&
184187
req.content_security_policy &&
185188
req.content_security_policy.directives['script-src'] &&
186189
req.content_security_policy_nonce
187190
end
188191

189192
# Secure Headers gem
190-
def secure_headers_nonce?
191-
secure_headers.append_nonce?
193+
def secure_headers_nonce(env)
194+
req = ::Rack::Request.new(env)
195+
196+
return unless secure_headers(req).append_nonce?
197+
198+
::SecureHeaders.content_security_policy_script_nonce(req)
192199
end
193200

194-
def secure_headers
201+
def secure_headers(req)
195202
return SecureHeadersFalse.new unless defined?(::SecureHeaders::Configuration)
196203

204+
# If the nonce key has been set, the app is using nonces for this request.
205+
# If it hasn't, we shouldn't cause one to be added to script_src, so return now.
206+
return SecureHeadersFalse.new unless secure_headers_nonce_key(req)
207+
197208
config = ::SecureHeaders::Configuration
198209

199210
secure_headers_cls = nil
@@ -211,6 +222,10 @@ def secure_headers
211222
secure_headers_cls.new
212223
end
213224

225+
def secure_headers_nonce_key(req)
226+
defined?(::SecureHeaders::NONCE_KEY) && req.env[::SecureHeaders::NONCE_KEY]
227+
end
228+
214229
class SecureHeadersResolver
215230
def append_nonce?
216231
csp_needs_nonce?(find_csp)

spec/dummyapp/app/controllers/home_controller.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ def test_rollbar_js
4848
render 'js/test', :layout => 'simple'
4949
end
5050

51+
def test_rollbar_js_with_nonce
52+
# Cause a secure_headers nonce to be added to script_src
53+
::SecureHeaders.content_security_policy_script_nonce(request)
54+
55+
render 'js/test', :layout => 'simple'
56+
end
57+
5158
def file_upload
5259
_this = will_crash
5360
end

spec/dummyapp/config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616
get '/set_session_data' => 'home#set_session_data'
1717
get '/use_session_data' => 'home#use_session_data'
1818
get '/test_rollbar_js' => 'home#test_rollbar_js'
19+
get '/test_rollbar_js_with_nonce' => 'home#test_rollbar_js_with_nonce'
1920
end

spec/rollbar/middleware/js_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
describe Rollbar::Middleware::Js do
3535
subject { described_class.new(app, config) }
3636

37-
let(:env) { {} }
37+
let(:env) { { SecureHeadersMocks::NONCE_KEY => SecureHeadersMocks::NONCE } }
3838
let(:config) { {} }
3939
let(:app) do
4040
proc do |_|
@@ -421,7 +421,7 @@
421421
let(:body) { [html] }
422422
let(:status) { 200 }
423423
let(:headers) do
424-
{
424+
{
425425
'Content-Type' => content_type,
426426
'Content-Length' => html.bytesize
427427
}

0 commit comments

Comments
 (0)