Skip to content

Commit 9081657

Browse files
FEATURE: Support dynamic config.content_security_policy_nonce (#609)
CSP nonce values change on every request, so accepting a static string as an option doesn't really make sense. This commit allows `config.content_security_policy_nonce` to be set to a Proc which is run for each request, and can return a nonce based on the `env` and current response headers.
1 parent 68a69b3 commit 9081657

5 files changed

Lines changed: 52 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## Unreleased
4+
- [FEATURE] Support dynamic `config.content_security_policy_nonce` [#609](https://github.com/MiniProfiler/rack-mini-profiler/pull/609)
5+
6+
37
## 3.3.0 - 2023-12-07
48
- [FEATURE] Use `?pp=flamegraph?ignore_gc=true` or `config.flamegraph_ignore_gc` to ignore gc in flamegraphs. [#599](https://github.com/MiniProfiler/rack-mini-profiler/pull/599)
59

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ snapshots_transport_destination_url | `nil`
448448
snapshots_transport_auth_key | `nil` | `POST` requests made by the snapshots transporter to the destination URL will have a `Mini-Profiler-Transport-Auth` header with the value of this config. Make sure you use a secure and random key for this config.
449449
snapshots_redact_sql_queries | `true` | When this is true, SQL queries will be redacted from sampling snapshots, but the backtrace and duration of each SQL query will be saved with the snapshot to keep debugging performance issues possible.
450450
snapshots_transport_gzip_requests | `false` | Make the snapshots transporter gzip the requests it makes to `snapshots_transport_destination_url`.
451-
content_security_policy_nonce | Rails: Current nonce<br>Rack: nil | Set the content security policy nonce to use when inserting MiniProfiler's script block.
451+
content_security_policy_nonce | Rails: Current nonce<br>Rack: nil | Set the content security policy nonce to use when inserting MiniProfiler's script block. Can be set to a static string, or a Proc which receives `env` and `response_headers` as arguments and returns the nonce.
452452
enable_hotwire_turbo_drive_support | `false` | Enable support for Hotwire TurboDrive page transitions.
453453
profile_parameter | `'pp'` | The query parameter used to interact with this gem.
454454

lib/mini_profiler.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ def inject_profiler(env, status, headers, body)
440440

441441
if current.inject_js && content_type =~ /text\/html/
442442
response = Rack::Response.new([], status, headers)
443-
script = self.get_profile_script(env)
443+
script = self.get_profile_script(env, headers)
444444

445445
if String === body
446446
response.write inject(body, script)

lib/mini_profiler/views.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def generate_html(page_struct, env, result_json = page_struct.to_json)
2828
# Use it when:
2929
# * you have disabled auto append behaviour throught :auto_inject => false flag
3030
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
31-
def get_profile_script(env)
31+
def get_profile_script(env, response_headers = {})
3232
path = public_base_path(env)
3333
version = MiniProfiler::ASSET_VERSION
3434
if @config.assets_url
@@ -39,7 +39,12 @@ def get_profile_script(env)
3939
url = "#{path}includes.js?v=#{version}" if !url
4040
css_url = "#{path}includes.css?v=#{version}" if !css_url
4141

42-
content_security_policy_nonce = @config.content_security_policy_nonce ||
42+
configured_nonce = @config.content_security_policy_nonce
43+
if configured_nonce && !configured_nonce.is_a?(String)
44+
configured_nonce = configured_nonce.call(env, response_headers)
45+
end
46+
47+
content_security_policy_nonce = configured_nonce ||
4348
env["action_dispatch.content_security_policy_nonce"] ||
4449
env["secure_headers_content_security_policy_nonce"]
4550

spec/integration/middleware_spec.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,4 +242,43 @@ def with_profile_parameter(param)
242242
end
243243
end
244244
end
245+
246+
context "with CSP nonce" do
247+
def app
248+
Rack::Builder.new do
249+
use Rack::MiniProfiler
250+
run lambda { |env|
251+
env["action_dispatch.content_security_policy_nonce"] = "railsnonce"
252+
[200, { 'Content-Type' => 'text/html' }, [+'<html><body><h1>Hello world</h1></body></html>']]
253+
}
254+
end
255+
end
256+
257+
it 'uses Rails value when available' do
258+
do_get
259+
expect(last_response.body).to include("nonce=\"railsnonce\"")
260+
end
261+
262+
it 'uses configured string when available' do
263+
Rack::MiniProfiler.config.content_security_policy_nonce = "configurednonce"
264+
do_get
265+
expect(last_response.body).to include("nonce=\"configurednonce\"")
266+
end
267+
268+
it 'calls configured block when available' do
269+
proc_arguments = nil
270+
271+
Rack::MiniProfiler.config.content_security_policy_nonce = Proc.new do |env, response_headers|
272+
proc_arguments = [env, response_headers]
273+
"dynamicnonce"
274+
end
275+
276+
do_get
277+
expect(last_response.body).to include("nonce=\"dynamicnonce\"")
278+
279+
(env, response_headers) = proc_arguments
280+
expect(env["REQUEST_METHOD"]).to eq("GET")
281+
expect(response_headers["Content-Type"]).to eq("text/html")
282+
end
283+
end
245284
end

0 commit comments

Comments
 (0)