Skip to content

Commit

Permalink
Automatically add a nonce to script tag (brentd#100)
Browse files Browse the repository at this point in the history
... if a csp-nonce meta tag is available to get it from.

- Added test that confirms that nonce is added
- Added an end-to-end test that uses a strict CSP. Confirmed that this fails without the nonce added
  to javascript_include_tag calls.

Co-authored-by: Matt Brictson <[email protected]>
  • Loading branch information
TylerRick and mattbrictson committed Feb 24, 2021
1 parent c5a681b commit 7c6bcf8
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 3 deletions.
7 changes: 6 additions & 1 deletion lib/xray/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,15 @@ def script_matcher(script_name)
/x
end

def nonce_from_meta_tag(html)
html[/<meta name="csp-nonce" content="([^"]*)"/, 1].presence
end

# Appends the given `script_name` after the `after_script_name`.
def append_js!(html, after_script_name, script_name)
html.sub!(script_matcher(after_script_name)) do
"#{$~}\n" + helper.javascript_include_tag(script_name)
nonce = nonce_from_meta_tag(html)
"#{$~}\n" + helper.javascript_include_tag(script_name, nonce: nonce)
end
end

Expand Down
10 changes: 10 additions & 0 deletions spec/dummy/app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
class ApplicationController < ActionController::Base
protect_from_forgery

if respond_to?(:content_security_policy) # Rails >= 5.2
content_security_policy only: :strict_csp do |policy|
policy.script_src :self, :strict_dynamic
end
end

def root
end

def strict_csp
render :root
end

# For the tests
def non_html
render json: {foo: 'bar'}
Expand Down
5 changes: 3 additions & 2 deletions spec/dummy/app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
<html>
<head>
<title>Xray</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application", nonce: true %>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
</head>
<body>

Expand Down
7 changes: 7 additions & 0 deletions spec/dummy/config/initializers/content_security_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
if Gem.loaded_specs['rails'].version >= Gem::Version.new('5.2.0')
Rails.application.config.content_security_policy do |policy|
# Empty. Only need one endpoint (/strict_csp) to use a strict CSP in our tests.
end

Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
end
11 changes: 11 additions & 0 deletions spec/xray/e2e_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@
expect_to_work
end

if Gem.loaded_specs['rails'].version >= Gem::Version.new('5.2.0')
it "works when using a strict CSP" do
visit '/strict_csp'
expect_to_work
end
end

def expect_to_work
if Gem.loaded_specs['rails'].version >= Gem::Version.new('5.2.0')
expect(page).to have_selector('script[src^="/assets/xray"][nonce]')
expect(page.find('script[src^="/assets/xray"]')[:nonce]).to eq page.find('meta[name="csp-nonce"]')[:content]
end
expect(page).to have_selector('#xray-bar')

expect(page).to have_no_selector('.xray-specimen-handle.TemplateSpecimen', text: 'root.html.erb')
Expand Down
8 changes: 8 additions & 0 deletions spec/xray/middleware_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ def mock_response(status, content_type, body)
expect(page).to have_selector('script[src^="/assets/xray"]')
end

if Gem.loaded_specs['rails'].version >= Gem::Version.new('5.2.0')
it "adds nonce to the script tag" do
visit '/'
expect(page).to have_selector('script[src^="/assets/xray"][nonce]')
expect(page.find('script[src^="/assets/xray"]')[:nonce]).to eq page.find('meta[name="csp-nonce"]')[:content]
end
end

it "injects the xray bar into the response" do
visit '/'
expect(page).to have_selector('#xray-bar')
Expand Down

0 comments on commit 7c6bcf8

Please sign in to comment.