8000 Add ability to enforce strict-dynamic Content-Security-Policy · Issue #585 · danmayer/coverband · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
Add ability to enforce strict-dynamic Content-Security-Policy #585
Open
@stanhu

Description

@stanhu

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:

  1. jQuery 1.6.2 - The core jQuery library
  2. highlight.js - A code syntax highlighting library
  3. Colorbox - A lightweight customizable lightbox plugin
  4. DataTables - A table enhancement plugin that adds sorting, filtering, and pagination to HTML tables
    • Including a custom percent sorting extension
  5. timeago - A plugin for automatically updating fuzzy timestamps (e.g., "4 minutes ago")
  6. 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

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0