Skip to content

Commit 66dee5e

Browse files
Content Security Policy for inline scripts
1 parent 133f795 commit 66dee5e

File tree

3 files changed

+23
-6
lines changed

3 files changed

+23
-6
lines changed

lib/better_errors/error_page.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def id
2626
@id ||= SecureRandom.hex(8)
2727
end
2828

29-
def render(template_name = "main", csrf_token = nil)
29+
def render(template_name = "main", csrf_token = nil, csp_nonce = nil)
3030
binding.eval(self.class.template(template_name).src)
3131
rescue => e
3232
# Fix the backtrace, which doesn't identify the template that failed (within Better Errors).

lib/better_errors/middleware.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,13 @@ def protected_app_call(env)
9494
def show_error_page(env, exception=nil)
9595
request = Rack::Request.new(env)
9696
csrf_token = request.cookies[CSRF_TOKEN_COOKIE_NAME] || SecureRandom.uuid
97+
csp_nonce = SecureRandom.base64(12)
9798

9899
type, content = if @error_page
99100
if text?(env)
100101
[ 'plain', @error_page.render('text') ]
101102
else
102-
[ 'html', @error_page.render('main', csrf_token) ]
103+
[ 'html', @error_page.render('main', csrf_token, csp_nonce) ]
103104
end
104105
else
105106
[ 'html', no_errors_page ]
@@ -110,7 +111,23 @@ def show_error_page(env, exception=nil)
110111
status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
111112
end
112113

113-
response = Rack::Response.new(content, status_code, { "Content-Type" => "text/#{type}; charset=utf-8" })
114+
headers = {
115+
"Content-Type" => "text/#{type}; charset=utf-8",
116+
"Content-Security-Policy" => [
117+
"default-src 'self' https:", # TODO: remove https:?
118+
"font-src 'self' https: data:",
119+
"img-src 'self' https: data:",
120+
"object-src 'none'",
121+
# Specifying nonce makes a modern browser ignore 'unsafe-inline' which could still be set
122+
# for older browsers without nonce support.
123+
"script-src 'self' https: 'nonce-#{csp_nonce}' 'unsafe-inline'",
124+
# Inline style is required by the syntax highlighter.
125+
"style-src 'self' https: 'unsafe-inline'",
126+
"connect-src 'self' https:",
127+
].join('; '),
128+
}
129+
130+
response = Rack::Response.new(content, status_code, headers)
114131

115132
unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
116133
response.set_cookie(CSRF_TOKEN_COOKIE_NAME, value: csrf_token, path: "/", httponly: true, same_site: :strict)

lib/better_errors/templates/main.erb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@
701701
</style>
702702

703703
<%# IE8 compatibility crap %>
704-
<script>
704+
<script nonce="<%= csp_nonce %>">
705705
(function() {
706706
var elements = ["section", "nav", "header", "footer", "audio"];
707707
for (var i = 0; i < elements.length; i++) {
@@ -715,7 +715,7 @@
715715
rendered in the host app's layout. Let's empty out the styles of the
716716
host app.
717717
%>
718-
<script>
718+
<script nonce="<%= csp_nonce %>">
719719
if (window.Turbolinks) {
720720
for(var i=0; i < document.styleSheets.length; i++) {
721721
if(document.styleSheets[i].href)
@@ -791,7 +791,7 @@
791791
<% end %>
792792
</section>
793793
</body>
794-
<script>
794+
<script nonce="<%= csp_nonce %>">
795795
(function() {
796796

797797
var OID = "<%= id %>";

0 commit comments

Comments
 (0)