dispatch-rails 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a712af989dbaa59fcd32135400b74a6ddb41058f291d60dce865516e117a50c6
4
- data.tar.gz: 1e1d88267bcffb7177556c6a0dd92c7e9e191356288e647881a1ef04410bbeb3
3
+ metadata.gz: 5f742aa0ca8db65f9db9c6b5a135ae7d926fdf57dffde8d1d011ae3449298d32
4
+ data.tar.gz: e7b3df1d90a904e8c2383a03d3231c0ff7398b4f269632d697c5d728c42c386a
5
5
  SHA512:
6
- metadata.gz: 891dee4bfc449f10ba4e4478ebd3fdd695d023f1853179563f63ccbbfa0a67c5431151576b403db02520c6132a0bb0ea6c93d64fe4999bde11006d820c39af26
7
- data.tar.gz: f5be0e31f9ecbfce518e7533fc6a483c7bc12bcb5a624aae24a8617666232bf2d7a10f1787367313c7b741bf5ffe88dc07fff8e05bc0805e70012a50c65ef517
6
+ metadata.gz: b9cb75b00f87e29843b6837051100d1cac96448ab6db77a048837303b62cd98d18a1ab7dac1bd1f25a8f527b17cf644f2dab6ef46a4a62cf7edc88f1b02d71fd
7
+ data.tar.gz: f409e09011d0492ccf0ea168ac6f89129ee6fcb83431617a5560ea977c1872a4346586996679538db4b14ea3ae50396d95d47df42f4626f77fff59cd3a00d804
data/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to `dispatch-rails` are documented here. The format is based
4
4
  on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.10.2] - 2026-06-18
8
+
9
+ ### Fixed
10
+ - The browser tracker/widget bootstrapped via an **inline** `<script>` (a nonced
11
+ `javascript_tag` that ran `import("/dispatch/…")`). Inline scripts are fragile
12
+ under Turbo + CSP — the nonce attribute is blanked after parse and must be
13
+ re-applied from the `csp-nonce` meta tag on every navigation — so hosts kept
14
+ seeing `script-src-elem blocked inline`. Both loaders are now plain **external
15
+ same-origin** `<script type="module" src="/dispatch/…">` tags, which
16
+ `script-src 'self'` allows outright — no nonce, no `'unsafe-inline'`, no Turbo
17
+ nonce drift. The nonce is still attached when the host generates one, so strict
18
+ nonce-only / `strict-dynamic` policies keep working. (`type="module"` also lets
19
+ the browser de-dupe the load across Turbo navigations via the module map.)
20
+ - Browser CSP/Reporting-API events were attributed to the `/dispatch/reports`
21
+ endpoint the browser POSTs to, collapsing every violation across the app onto
22
+ one bogus URL. CSP reports now carry the violation's `document-uri` as the event
23
+ URL, so the dashboard groups and links them to the page that actually violated.
24
+
7
25
  ## [0.10.1] - 2026-06-18
8
26
 
9
27
  ### Fixed
@@ -1,9 +1,9 @@
1
1
  <script type="application/json" id="dispatch-error-config"><%= raw error_config_json.gsub("</", "<\\/") %></script>
2
- <%# nonce: true picks up the host app's CSP nonce; the JSON config island above is non-executable and needs none %>
3
- <%= javascript_tag type: "module", nonce: true do %>
4
- if (!window.__dispatchErrorTrackerLoaded) {
5
- import("/dispatch/error_tracker.js").catch(() => {
6
- console.error("[dispatch-rails] failed to load error_tracker.js ensure the engine assets are mounted");
7
- });
8
- }
9
- <% end %>
2
+ <%# The config island above is a non-executable application/json data block, so CSP
3
+ never applies to it. The tracker itself loads as an EXTERNAL same-origin script
4
+ (served by the engine's AssetMiddleware at this exact path), which script-src
5
+ 'self' allows on its own — no nonce, no 'unsafe-inline', and none of the inline-
6
+ script-under-Turbo nonce drift that an inline loader is prone to. We still attach
7
+ the nonce when the host generates one, so strict nonce-only / strict-dynamic
8
+ policies (where 'self' alone isn't enough) keep working. %>
9
+ <%= tag.script type: "module", src: "/dispatch/error_tracker.js", nonce: content_security_policy_nonce %>
@@ -60,12 +60,10 @@
60
60
  </div>
61
61
  </div>
62
62
 
63
- <%# nonce: true picks up the host app's CSP nonce; omitted when no nonce generator is configured %>
64
- <%= javascript_tag type: "module", nonce: true do %>
65
- if (!window.__dispatchWidgetLoaded) {
66
- window.__dispatchWidgetLoaded = true;
67
- import("/dispatch/widget.js").catch(() => {
68
- console.error("[dispatch-rails] failed to load widget.js make sure the engine assets are mounted");
69
- });
70
- }
71
- <% end %>
63
+ <%# Loaded as an EXTERNAL same-origin module (served by the engine's AssetMiddleware
64
+ at this exact path), so script-src 'self' allows it without a nonce or
65
+ 'unsafe-inline' — no inline loader to be blocked under script-src-elem. type=module
66
+ both resolves widget.js's `import "@hotwired/stimulus"` via the host importmap and
67
+ de-dupes the load across Turbo navigations via the module map. The nonce is
68
+ attached when the host generates one, for strict nonce-only / strict-dynamic CSPs. %>
69
+ <%= tag.script type: "module", src: "/dispatch/widget.js", nonce: content_security_policy_nonce %>
@@ -16,17 +16,23 @@ module Dispatch
16
16
  MAX_PARAMS_BYTES = 8_000
17
17
  SAFE_HEADERS = %w[User-Agent Referer Accept Content-Type Host X-Request-Id].freeze
18
18
 
19
- def self.call(exception, handled:, env: nil, user: nil, tags: {}, level: "error")
20
- new(exception, handled: handled, env: env, user: user, tags: tags, level: level).call
19
+ def self.call(exception, handled:, env: nil, user: nil, tags: {}, level: "error", request_url: nil)
20
+ new(exception, handled: handled, env: env, user: user, tags: tags, level: level,
21
+ request_url: request_url).call
21
22
  end
22
23
 
23
- def initialize(exception, handled:, env: nil, user: nil, tags: {}, level: "error")
24
+ def initialize(exception, handled:, env: nil, user: nil, tags: {}, level: "error", request_url: nil)
24
25
  @exception = exception
25
26
  @handled = handled
26
27
  @env = env
27
28
  @user = user
28
29
  @tags = tags || {}
29
30
  @level = level
31
+ # Override for the event's request URL. A browser report (CSP, NEL, …) is
32
+ # POSTed to /dispatch/reports, so the env URL is that endpoint, not the page
33
+ # that violated the policy — the caller passes the report's document-uri here
34
+ # so the dashboard attributes (and groups) the event to the real page.
35
+ @request_url_override = request_url
30
36
  @source_cache = {}
31
37
  @config = Dispatch::Rails.configuration
32
38
  end
@@ -221,6 +227,8 @@ module Dispatch
221
227
  end
222
228
 
223
229
  def request_url
230
+ return @request_url_override if @request_url_override.present?
231
+
224
232
  scheme = @env["rack.url_scheme"] || "http"
225
233
  host = @env["HTTP_HOST"] || @env["SERVER_NAME"]
226
234
  return nil if host.nil?
@@ -21,7 +21,8 @@ module Dispatch
21
21
  user = resolve_user(config, env, context)
22
22
  tags = merged_tags(config, env, context)
23
23
  event = EventBuilder.call(exception, handled: handled, env: env, user: user,
24
- tags: tags, level: level)
24
+ tags: tags, level: level,
25
+ request_url: context[:request_url])
25
26
  event = config.before_send.call(event) if config.before_send.respond_to?(:call)
26
27
  return if event.nil?
27
28
 
@@ -100,7 +100,13 @@ module Dispatch
100
100
  handled: true, env: env,
101
101
  # report-only disposition is monitoring, not a live block — keep it quieter.
102
102
  level: fields[:disposition].to_s == "report" ? "info" : "warning",
103
- context: { tags: { csp: "true", report_type: "csp-violation" }.merge(fields) }
103
+ context: {
104
+ # Attribute the violation to the page it happened on, not to this
105
+ # /dispatch/reports endpoint the browser POSTed the report to — otherwise
106
+ # every CSP violation across the app collapses onto one bogus URL.
107
+ request_url: fields[:document_uri],
108
+ tags: { csp: "true", report_type: "csp-violation" }.merge(fields)
109
+ }
104
110
  )
105
111
  end
106
112
 
@@ -1,5 +1,5 @@
1
1
  module Dispatch
2
2
  module Rails
3
- VERSION = "0.10.1".freeze
3
+ VERSION = "0.10.2".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dispatch-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dispatch Team