From 1d4be87a3e817abbf165b014810d050ec80b9131 Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 11 Jul 2025 10:45:10 -0400 Subject: [PATCH 1/2] Fix CSP nonce support for flamegraph rendering The flamegraph page was missing CSP nonce attributes on its inline script tags, causing Content Security Policy violations when nonces were configured. This change extracts the nonce retrieval logic into a shared method and applies it consistently to both the main profiler UI and flamegraph pages. --- CHANGELOG.md | 4 ++ lib/mini_profiler/views.rb | 27 +++++++----- spec/integration/middleware_spec.rb | 64 +++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62367f74..b4818dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## Unreleased + +- [FIX] Add CSP nonce support to flamegraph rendering [#](https://github.com/MiniProfiler/rack-mini-profiler/pull/) + ## 4.0.1 - 2025-07-31 - [FIX] Ensure Rack 2 / 3 cross compatibility [#653](https://github.com/MiniProfiler/rack-mini-profiler/pull/653) diff --git a/lib/mini_profiler/views.rb b/lib/mini_profiler/views.rb index 87566881..96100931 100644 --- a/lib/mini_profiler/views.rb +++ b/lib/mini_profiler/views.rb @@ -10,6 +10,18 @@ def share_template @share_template ||= ERB.new(::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))) end + def get_csp_nonce(env, response_headers = {}) + configured_nonce = @config.content_security_policy_nonce + if configured_nonce && !configured_nonce.is_a?(String) + configured_nonce = configured_nonce.call(env, response_headers) + end + + configured_nonce || + env["action_dispatch.content_security_policy_nonce"] || + env["secure_headers_content_security_policy_nonce"] || + "" + end + def generate_html(page_struct, env, result_json = page_struct.to_json) # double-assigning to suppress "assigned but unused variable" warnings path = path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}" @@ -39,15 +51,6 @@ def get_profile_script(env, response_headers = {}) url = "#{path}includes.js?v=#{version}" if !url css_url = "#{path}includes.css?v=#{version}" if !css_url - configured_nonce = @config.content_security_policy_nonce - if configured_nonce && !configured_nonce.is_a?(String) - configured_nonce = configured_nonce.call(env, response_headers) - end - - content_security_policy_nonce = configured_nonce || - env["action_dispatch.content_security_policy_nonce"] || - env["secure_headers_content_security_policy_nonce"] - settings = { path: path, url: url, @@ -66,7 +69,7 @@ def get_profile_script(env, response_headers = {}) collapseResults: @config.collapse_results, htmlContainer: @config.html_container, hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(','), - cspNonce: content_security_policy_nonce, + cspNonce: get_csp_nonce(env, response_headers), hotwireTurboDriveSupport: @config.enable_hotwire_turbo_drive_support, } @@ -112,6 +115,8 @@ def make_link(postfix, env) def flamegraph(graph, path, env) headers = { 'content-type' => 'text/html' } iframe_src = "#{public_base_path(env)}speedscope/index.html" + csp_nonce = get_csp_nonce(env, headers) + html = <<~HTML @@ -123,7 +128,7 @@ def flamegraph(graph, path, env) -