Open
Description
Currently if the csp_policy
option is enabled, the Content-Security-Policy-Report-Only
headers are set, which effectively disables Content-Security-Policy
headers.
I attempted to add CSP nonces to make it possible to enable Content-Security-Policy
with strict-dynamic
, but the main issue I ran into is that public/dependencies.js
has a number of inline scripts and styles. Claude tells me this bundle consists:
- jQuery 1.6.2 - The core jQuery library
- highlight.js - A code syntax highlighting library
- Colorbox - A lightweight customizable lightbox plugin
- DataTables - A table enhancement plugin that adds sorting, filtering, and pagination to HTML tables
- Including a custom percent sorting extension
- timeago - A plugin for automatically updating fuzzy timestamps (e.g., "4 minutes ago")
- jQuery URL Parser - A URL parsing utility
Is the original source saved somewhere?
This is what I have modified so far:
diff --git a/lib/coverband/reporters/html_report.rb b/lib/coverband/reporters/html_report.rb
index b89494f..f0fb6ea 100644
--- a/lib/coverband/reporters/html_report.rb
+++ b/lib/coverband/reporters/html_report.rb
@@ -4,7 +4,7 @@ module Coverband
module Reporters
class HTMLReport < Base
attr_accessor :filtered_report_files, :open_report, :notice,
- :base_path, :filename, :page
+ :base_path, :filename, :page, :csp_nonce
def initialize(store, options = {})
self.page = options.fetch(:page) { nil }
@@ -13,6 +13,7 @@ module Coverband
self.notice = options.fetch(:notice) { nil }
self.base_path = options.fetch(:base_path) { "./" }
self.filename = options.fetch(:filename) { nil }
+ self.csp_nonce = options.fetch(:csp_nonce) { nil }
coverband_reports = Coverband::Reporters::Base.report(store, options)
# NOTE: at the moment the optimization around paging and filenames only works for hash redis store
@@ -26,6 +27,7 @@ module Coverband
def file_details
Coverband::Utils::HTMLFormatter.new(filtered_report_files,
base_path: base_path,
+ csp_nonce: csp_nonce,
notice: notice).format_source_file!(filename)
end
@@ -43,6 +45,7 @@ module Coverband
Coverband::Utils::HTMLFormatter.new(filtered_report_files,
base_path: base_path,
notice: notice,
+ csp_nonce: csp_nonce,
page: page).format_dynamic_html!
end
@@ -50,6 +53,7 @@ module Coverband
Coverband::Utils::HTMLFormatter.new(filtered_report_files,
base_path: base_path,
page: page,
+ csp_nonce: csp_nonce,
notice: notice).format_dynamic_data!
end
end
diff --git a/lib/coverband/reporters/web.rb b/lib/coverband/reporters/web.rb
index 279411e..78a8ff0 100644
--- a/lib/coverband/reporters/web.rb
+++ b/lib/coverband/reporters/web.rb
@@ -24,8 +24,8 @@ module Coverband
"manifest-src 'self'",
"media-src 'self'",
"object-src 'none'",
- "script-src 'self' https: http: 'unsafe-inline'",
- "style-src 'self' https: http: 'unsafe-inline'",
+ "script-src 'self' 'nonce-!placeholder!' https: http: 'unsafe-inline' ",
+ "style-src 'self' 'nonce-!placeholder!' https: http: 'unsafe-inline'",
"worker-src 'self'",
"base-uri 'self'"
].join("; ").freeze
@@ -53,6 +53,8 @@ module Coverband
end
def call(env)
+ env[:csp_nonce] = SecureRandom.base64(16)
@request = Rack::Request.new(env)
return [401, {"www-authenticate" => 'Basic realm=""'}, [""]] unless check_auth
@@ -110,6 +112,7 @@ module Coverband
end
def index
notice = "<strong>Notice:</strong> #{Rack::Utils.escape_html(request.params["notice"])}<br/>"
notice = request.params["notice"] ? notice : ""
page = (request.params["page"] || 1).to_i
@@ -117,7 +120,8 @@ module Coverband
static: false,
base_path: base_path,
notice: notice,
- open_report: false
+ open_report: false,
+ csp_nonce: csp_nonce
}
options[:page] = page if Coverband.configuration.paged_reporting
Coverband::Reporters::HTMLReport.new(Coverband.configuration.store,
@@ -142,7 +146,7 @@ module Coverband
def settings
return "" if Coverband.configuration.hide_settings
- Coverband::Utils::HTMLFormatter.new(nil, base_path: base_path).format_settings!
+ Coverband::Utils::HTMLFormatter.new(nil, base_path: base_path, csp_nonce: csp_nonce).format_settings!
end
def display_abstract_tracker(tracker)
@@ -151,7 +155,8 @@ module Coverband
options = {
tracker: tracker,
notice: notice,
- base_path: base_path
+ base_path: base_path,
+ csp_nonce: csp_nonce
}
Coverband::Utils::HTMLFormatter.new(nil, options).format_abstract_tracker!
end
@@ -228,10 +233,15 @@ module Coverband
web_headers = {
"content-type" => content_type
}
- web_headers["content-security-policy-report-only"] = CSP_HEADER if Coverband.configuration.csp_policy
+ # web_headers["content-security-policy-report-only"] = CSP_HEADER if Coverband.configuration.csp_policy
+ web_headers["content-security-policy"] = csp_headers
web_headers
end
+ def csp_headers
+ CSP_HEADER.gsub("!placeholder!", csp_nonce)
+ end
+
# This method should get the root mounted endpoint
# for example if the app is mounted like so:
# mount Coverband::Web, at: '/coverage'
@@ -243,6 +253,10 @@ module Coverband
def base_path
(request.path =~ %r{\/.*\/}) ? request.path.match("/.*/")[0] : "/"
end
+
+ def csp_nonce
+ request.env[:csp_nonce]
+ end
end
end
end
diff --git a/lib/coverband/utils/html_formatter.rb b/lib/coverband/utils/html_formatter.rb
index fd5479c..d3f1f7e 100644
--- a/lib/coverband/utils/html_formatter.rb
+++ b/lib/coverband/utils/html_formatter.rb
@@ -13,13 +13,15 @@ require "time"
module Coverband
module Utils
class HTMLFormatter
- attr_reader :notice, :base_path, :tracker, :page
+ attr_reader :notice, :base_path, :tracker, :page, :csp_nonce
def initialize(report, options = {})
@notice = options.fetch(:notice, nil)
@base_path = options.fetch(:base_path, "./")
@tracker = options.fetch(:tracker, nil)
@page = options.fetch(:page, nil)
+ @csp_nonce = options.fetch(:csp_nonce, nil)
@coverage_result = Coverband::Utils::Results.new(report) if report
end
@@ -177,7 +179,7 @@ module Coverband
def link_to_source_file(source_file)
data_loader_url = "#{base_path}load_file_details?filename=#{source_file.filename}"
- %(<a href="##{id source_file}" class="src_link" title="#{shortened_filename source_file}" data-loader-url="#{data_loader_url}" >#{truncate(shortened_filename(source_file))}</a>)
+ %(<a href="##{id source_file}" class="src_link" title="#{shortened_filename source_file}" data-loader-url="#{data_loader_url}">#{truncate(shortened_filename(source_file))}</a>)
end
def truncate(text, length: 50)
diff --git a/public/application.js b/public/application.js
index 5828b8c..5d8a7b9 100644
--- a/public/application.js
+++ b/public/application.js
@@ -80,6 +80,10 @@ $(document).ready(function () {
}
}
+ $('.src_link').on('click', function() {
+ src_link_click(this);
+ });
+
src_link_click = (trigger_element) => {
var loader_url = $(trigger_element).attr("data-loader-url");
auto_click_anchor = null;
diff --git a/views/abstract_tracker.erb b/views/abstract_tracker.erb
index 8dec2f3..4d8209a 100644
--- a/views/abstract_tracker.erb
+++ b/views/abstract_tracker.erb
@@ -3,8 +3,8 @@
<head>
<title>Coverband Info: <%= Coverband::VERSION %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <script src='<%= assets_path('dependencies.js') %>' type='text/javascript'></script>
- <script src='<%= assets_path('application.js') %>' type='text/javascript'></script>
+ <script src='<%= assets_path('dependencies.js') %>' type='text/javascript' nonce="<%= csp_nonce %>"></script>
+ <script src='<%= assets_path('application.js') %>' type='text/javascript' nonce="<%= csp_nonce %>"></script>
<link href='<%= assets_path('application.css') %>' media='screen, projection, print' rel='stylesheet' type='text/css'>
<link rel="icon" type="image/png" href="<%= assets_path('favicon.png') %>" />
</head>
diff --git a/views/layout.erb b/views/layout.erb
index 3bbd9f2..a903dec 100644
--- a/views/layout.erb
+++ b/views/layout.erb
@@ -3,9 +3,9 @@
<head>
<title>Coverband: <%= Coverband::VERSION %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <script src='<%= assets_path('dependencies.js') %>' type='text/javascript'></script>
- <script src='<%= assets_path('application.js') %>' type='text/javascript'></script>
- <link href='<%= assets_path('application.css') %>' media='screen, projection, print' rel='stylesheet' type='text/css'>
+ <script src='<%= assets_path('dependencies.js') %>' type='text/javascript' nonce="<%= csp_nonce %>"></script>
+ <script src='<%= assets_path('application.js') %>' type='text/javascript' nonce="<%= csp_nonce %>"></script>
+ <link href='<%= assets_path('application.css') %>' media='screen, projection, print' rel='stylesheet' type='text/css' nonce="<%= csp_nonce %>">
<link rel="shortcut icon" type="image/png" href="<%= assets_path("favicon_#{coverage_css_class(result.source_files.covered_percent)}.png") %>" />
<link rel="icon" type="image/png" href="<%= assets_path('favicon.png') %>" />
</head>
diff --git a/views/settings.erb b/views/settings.erb
index e12edaa..655a023 100644
--- a/views/settings.erb
+++ b/views/settings.erb
@@ -3,8 +3,8 @@
<head>
<title>Coverband Info: <%= Coverband::VERSION %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <script src='<%= assets_path('application.js') %>' type='text/javascript'></script>
- <link href='<%= assets_path('application.css') %>' media='screen, projection, print' rel='stylesheet' type='text/css'>
+ <script src='<%= assets_path('application.js') %>' type='text/javascript' nonce="<%= csp_nonce %>"></script>
+ <link href='<%= assets_path('application.css') %>' media='screen, projection, print' rel='stylesheet' type='text/css' nonce="<%= csp_nonce %>">
<link rel="icon" type="image/png" href="<%= assets_path('favicon.png') %>" />
</head>
Metadata
Metadata
Assignees
Labels
No labels