Skip to content

Commit 63c0210

Browse files
Automatically add a nonce to script tag (#100)
... 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. - With Sprockets 4, it doesn't add a separate script tag for jquery, so add an explicit javascript_include_tag "jquery" for that case Co-authored-by: Matt Brictson <[email protected]>
1 parent 44a878e commit 63c0210

File tree

7 files changed

+49
-4
lines changed

7 files changed

+49
-4
lines changed

lib/xray/middleware.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,15 @@ def script_matcher(script_name)
111111
/x
112112
end
113113

114+
def nonce_from_meta_tag(html)
115+
html[/<meta name="csp-nonce" content="([^"]*)"/, 1].presence
116+
end
117+
114118
# Appends the given `script_name` after the `after_script_name`.
115119
def append_js!(html, after_script_name, script_name)
116120
html.sub!(script_matcher(after_script_name)) do
117-
"#{$~}\n" + helper.javascript_include_tag(script_name)
121+
nonce = nonce_from_meta_tag(html)
122+
"#{$~}\n" + helper.javascript_include_tag(script_name, nonce: nonce)
118123
end
119124
end
120125

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
//= link_directory ../javascripts .js
21
//= link_directory ../stylesheets .css
2+
//= link_directory ../javascripts .js
3+
//= link jquery.js

spec/dummy/app/controllers/application_controller.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
class ApplicationController < ActionController::Base
22
protect_from_forgery
33

4+
if respond_to?(:content_security_policy) # Rails >= 5.2
5+
content_security_policy only: :strict_csp do |policy|
6+
policy.script_src :self, :strict_dynamic
7+
end
8+
end
9+
410
def root
511
end
612

13+
def strict_csp
14+
render :root
15+
end
16+
717
# For the tests
818
def non_html
919
render json: {foo: 'bar'}

spec/dummy/app/views/layouts/application.html.erb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
<html>
33
<head>
44
<title>Xray</title>
5-
<%= stylesheet_link_tag "application", :media => "all" %>
6-
<%= javascript_include_tag "application" %>
5+
<%= stylesheet_link_tag "application", media: "all" %>
6+
<%= javascript_include_tag "application", nonce: true %>
7+
<%# With Sprockets 4, it doesn't add a separate script tag for jquery, but one is needed for xray %>
8+
<%= javascript_include_tag "jquery", nonce: true if Gem.loaded_specs['sprockets'].version >= Gem::Version.new('4.0.0') %>
79
<%= csrf_meta_tags %>
10+
<%= csp_meta_tag if Gem.loaded_specs['rails'].version >= Gem::Version.new('5.2.0') %>
811
</head>
912
<body>
1013

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
if Gem.loaded_specs['rails'].version >= Gem::Version.new('5.2.0')
2+
Rails.application.config.content_security_policy do |policy|
3+
# Empty. Only need one endpoint (/strict_csp) to use a strict CSP in our tests.
4+
end
5+
6+
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
7+
end

spec/xray/e2e_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@
66
expect_to_work
77
end
88

9+
if Gem.loaded_specs['rails'].version >= Gem::Version.new('5.2.0')
10+
it "works when using a strict CSP" do
11+
visit '/strict_csp'
12+
expect_to_work
13+
end
14+
end
15+
916
def expect_to_work
17+
if Gem.loaded_specs['rails'].version >= Gem::Version.new('5.2.0')
18+
expect(page).to have_selector('script[src^="/assets/xray"][nonce]')
19+
expect(page.find('script[src^="/assets/xray"]')[:nonce]).to eq page.find('meta[name="csp-nonce"]')[:content]
20+
end
1021
expect(page).to have_selector('#xray-bar')
1122

1223
expect(page).to have_no_selector('.xray-specimen-handle.TemplateSpecimen', text: 'root.html.erb')

spec/xray/middleware_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ def mock_response(status, content_type, body)
9595
expect(page).to have_selector('script[src^="/assets/xray"]')
9696
end
9797

98+
if Gem.loaded_specs['rails'].version >= Gem::Version.new('5.2.0')
99+
it "adds nonce to the script tag" do
100+
visit '/'
101+
expect(page).to have_selector('script[src^="/assets/xray"][nonce]')
102+
expect(page.find('script[src^="/assets/xray"]')[:nonce]).to eq page.find('meta[name="csp-nonce"]')[:content]
103+
end
104+
end
105+
98106
it "injects the xray bar into the response" do
99107
visit '/'
100108
expect(page).to have_selector('#xray-bar')

0 commit comments

Comments
 (0)