diff --git a/app/assets/javascripts/bootstrap-json/index.js b/app/assets/javascripts/bootstrap-json/index.js
index 6bc34b291e8..61a20ff817b 100644
--- a/app/assets/javascripts/bootstrap-json/index.js
+++ b/app/assets/javascripts/bootstrap-json/index.js
@@ -80,8 +80,23 @@ function updateScriptReferences({
entrypointName === "discourse" &&
element.tagName.toLowerCase() === "script"
) {
+ let nonce = "";
+ for (const [attr, value] of element.attributes) {
+ if (attr === "nonce") {
+ nonce = value;
+ break;
+ }
+ }
+
+ if (!nonce) {
+ // eslint-disable-next-line no-console
+ console.error(
+ "Expected to find a nonce= attribute on the main discourse script tag, but none was found. ember-cli-live-reload may not work correctly."
+ );
+ }
+
newElements.unshift(
- ``
+ ``
);
}
@@ -159,7 +174,7 @@ async function handleRequest(proxy, baseURL, req, res, outputPath) {
}
const csp = response.headers.get("content-security-policy");
- if (csp) {
+ if (csp && !csp.includes("'strict-dynamic'")) {
const emberCliAdditions = [
`http://${originalHost}${baseURL}assets/`,
`http://${originalHost}${baseURL}ember-cli-live-reload.js`,
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 7193ead23d0..105e5516cb4 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -65,10 +65,13 @@ module ApplicationHelper
google_universal_analytics_json
end
- def google_tag_manager_nonce_placeholder
- placeholder = "[[csp_nonce_placeholder_#{SecureRandom.hex}]]"
- response.headers["Discourse-GTM-Nonce-Placeholder"] = placeholder
- placeholder
+ def csp_nonce_placeholder
+ @csp_nonce_placeholder ||=
+ begin
+ placeholder = "[[csp_nonce_placeholder_#{SecureRandom.hex}]]"
+ response.headers["Discourse-CSP-Nonce-Placeholder"] = placeholder
+ placeholder
+ end
end
def shared_session_key
@@ -150,16 +153,17 @@ module ApplicationHelper
def preload_script_url(url, entrypoint: nil)
entrypoint_attribute = entrypoint ? "data-discourse-entrypoint=\"#{entrypoint}\"" : ""
+ nonce_attribute = "nonce=\"#{csp_nonce_placeholder}\""
add_resource_preload_list(url, "script")
if GlobalSetting.preload_link_header
<<~HTML.html_safe
-
+
HTML
else
<<~HTML.html_safe
-
-
+
+
HTML
end
end
@@ -586,6 +590,7 @@ module ApplicationHelper
mobile_view? ? :mobile : :desktop,
name,
skip_transformation: request.env[:skip_theme_ids_transformation].present?,
+ csp_nonce: csp_nonce_placeholder,
)
end
@@ -595,6 +600,7 @@ module ApplicationHelper
:translations,
I18n.locale,
skip_transformation: request.env[:skip_theme_ids_transformation].present?,
+ csp_nonce: csp_nonce_placeholder,
)
end
@@ -604,6 +610,7 @@ module ApplicationHelper
:extra_js,
nil,
skip_transformation: request.env[:skip_theme_ids_transformation].present?,
+ csp_nonce: csp_nonce_placeholder,
)
end
diff --git a/app/helpers/defer_script_helper.rb b/app/helpers/defer_script_helper.rb
deleted file mode 100644
index a33ba1d328d..00000000000
--- a/app/helpers/defer_script_helper.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-# Helper to render a no-op inline script tag to work around a safari bug
-# which causes `defer` scripts to be run before stylesheets are loaded.
-# https://bugs.webkit.org/show_bug.cgi?id=209261
-module DeferScriptHelper
- def self.safari_workaround_script
- <<~HTML.html_safe
-
- HTML
- end
-
- def self.fingerprint
- @fingerprint ||= calculate_fingerprint
- end
-
- private
-
- def self.raw_js
- "/* Workaround for https://bugs.webkit.org/show_bug.cgi?id=209261 */"
- end
-
- def self.calculate_fingerprint
- "sha256-#{Digest::SHA256.base64digest(raw_js)}"
- end
-end
diff --git a/app/helpers/splash_screen_helper.rb b/app/helpers/splash_screen_helper.rb
index 275c5452acd..edbc598cc25 100644
--- a/app/helpers/splash_screen_helper.rb
+++ b/app/helpers/splash_screen_helper.rb
@@ -1,18 +1,12 @@
# frozen_string_literal: true
module SplashScreenHelper
- def self.inline_splash_screen_script
- <<~HTML.html_safe
-
- HTML
- end
-
- def self.fingerprint
+ def self.raw_js
if Rails.env.development?
- calculate_fingerprint
+ load_js
else
- @fingerprint ||= calculate_fingerprint
- end
+ @loaded_js ||= load_js
+ end.html_safe
end
private
@@ -26,16 +20,4 @@ module SplashScreenHelper
Rails.logger.error("Unable to load splash screen JS") if Rails.env.production?
"console.log('Unable to load splash screen JS')"
end
-
- def self.raw_js
- if Rails.env.development?
- load_js
- else
- @loaded_js ||= load_js
- end
- end
-
- def self.calculate_fingerprint
- "sha256-#{Digest::SHA256.base64digest(raw_js)}"
- end
end
diff --git a/app/models/theme.rb b/app/models/theme.rb
index a771e7a2669..5a8b66fa3d4 100644
--- a/app/models/theme.rb
+++ b/app/models/theme.rb
@@ -6,7 +6,7 @@ require "json_schemer"
class Theme < ActiveRecord::Base
include GlobalPath
- BASE_COMPILER_VERSION = 78
+ BASE_COMPILER_VERSION = 80
class SettingsMigrationError < StandardError
end
@@ -356,11 +356,13 @@ class Theme < ActiveRecord::Base
end
end
- def self.lookup_field(theme_id, target, field, skip_transformation: false)
+ def self.lookup_field(theme_id, target, field, skip_transformation: false, csp_nonce: nil)
return "" if theme_id.blank?
theme_ids = !skip_transformation ? transform_ids(theme_id) : [theme_id]
- (resolve_baked_field(theme_ids, target.to_sym, field) || "").html_safe
+ resolved = (resolve_baked_field(theme_ids, target.to_sym, field) || "")
+ resolved = resolved.gsub(ThemeField::CSP_NONCE_PLACEHOLDER, csp_nonce) if csp_nonce
+ resolved.html_safe
end
def self.lookup_modifier(theme_ids, modifier_name)
@@ -469,8 +471,8 @@ class Theme < ActiveRecord::Base
.compact
caches.map { |c| <<~HTML.html_safe }.join("\n")
-
-
+
+
HTML
end
when :translations
diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb
index 37e1ffc44d6..61b164659c1 100644
--- a/app/models/theme_field.rb
+++ b/app/models/theme_field.rb
@@ -3,6 +3,9 @@
class ThemeField < ActiveRecord::Base
MIGRATION_NAME_PART_MAX_LENGTH = 150
+ # This string is not 'secret'. It's just randomized to avoid accidental clashes with genuine theme field content.
+ CSP_NONCE_PLACEHOLDER = "__CSP__NONCE__PLACEHOLDER__f72bff1b1768168a34ee092ce759f192__"
+
belongs_to :upload
has_one :javascript_cache, dependent: :destroy
has_one :upload_reference, as: :target, dependent: :destroy
@@ -168,12 +171,15 @@ class ThemeField < ActiveRecord::Base
doc
.css("script")
.each_with_index do |node, index|
- next unless inline_javascript?(node)
- js_compiler.append_raw_script(
- "_html/#{Theme.targets[self.target_id]}/#{name}_#{index + 1}.js",
- node.inner_html,
- )
- node.remove
+ if inline_javascript?(node)
+ js_compiler.append_raw_script(
+ "_html/#{Theme.targets[self.target_id]}/#{name}_#{index + 1}.js",
+ node.inner_html,
+ )
+ node.remove
+ else
+ node["nonce"] = CSP_NONCE_PLACEHOLDER
+ end
end
settings_hash = theme.build_settings_hash
@@ -185,9 +191,9 @@ class ThemeField < ActiveRecord::Base
javascript_cache.save!
doc.add_child(<<~HTML.html_safe) if javascript_cache.content.present?
-
-
- HTML
+
+
+ HTML
[doc.to_s, errors&.join("\n")]
end
@@ -294,8 +300,8 @@ class ThemeField < ActiveRecord::Base
javascript_cache.save!
doc = ""
doc = <<~HTML.html_safe if javascript_cache.content.present?
-
-
+
+
HTML
[doc, errors&.join("\n")]
end
diff --git a/app/views/common/_discourse_splash.html.erb b/app/views/common/_discourse_splash.html.erb
index 4ce41e78e8e..44013c98244 100644
--- a/app/views/common/_discourse_splash.html.erb
+++ b/app/views/common/_discourse_splash.html.erb
@@ -246,6 +246,8 @@
- <%= SplashScreenHelper.inline_splash_screen_script %>
+
<%- end %>
diff --git a/app/views/common/_google_tag_manager_head.html.erb b/app/views/common/_google_tag_manager_head.html.erb
index 56a77cfeed6..ba34edec767 100644
--- a/app/views/common/_google_tag_manager_head.html.erb
+++ b/app/views/common/_google_tag_manager_head.html.erb
@@ -1,6 +1,6 @@
<%= preload_script 'google-tag-manager' %>
diff --git a/app/views/common/_google_universal_analytics.html.erb b/app/views/common/_google_universal_analytics.html.erb
index 572c2817685..05527ae4ac5 100644
--- a/app/views/common/_google_universal_analytics.html.erb
+++ b/app/views/common/_google_universal_analytics.html.erb
@@ -7,6 +7,6 @@
<% if SiteSetting.ga_version == "v3_analytics" %>
<%= preload_script "google-universal-analytics-v3" %>
<% elsif SiteSetting.ga_version == "v4_gtag" %>
-
+
<%= preload_script "google-universal-analytics-v4" %>
<% end %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 66dc243e230..de9a6b711c9 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -27,8 +27,8 @@
<% add_resource_preload_list(script_asset_path("start-discourse"), "script") %>
<% add_resource_preload_list(script_asset_path("browser-update"), "script") %>
<%- else %>
- " as="script">
- " as="script">
+ " as="script" nonce="<%= csp_nonce_placeholder %>">
+ " as="script" nonce="<%= csp_nonce_placeholder %>">
<%- end %>
<%= preload_script 'browser-detect' %>
@@ -132,11 +132,11 @@
<% end %>
-
+
<%= yield :data %>
-
+
<%- unless customization_disabled? %>
<%= theme_lookup("body_tag") %>
@@ -146,6 +146,6 @@
<%= build_plugin_html 'server:before-body-close' %>
<%- end %>
- <%= DeferScriptHelper.safari_workaround_script %>
+