0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-04 10:39:43 +08:00
discourse/lib/content_security_policy.rb
Alan Guo Xiang Tan 661ea0c2bd
PERF: Avoid replacing CSP nonces in non-cacheable responses (#42008)
CSP nonce placeholders are replaced after rendering by scanning and
copying the complete response body, even when the response could not
enter the anonymous cache.

Generate the final nonce before rendering non-cacheable requests so
templates can emit it directly. Requests eligible for anonymous caching
continue to use placeholders, preserving a unique nonce each time cached
HTML is served.

On a site with 11K categories for a logged in user, ~400ms was spent
executing `gsub` on the response body.
2026-07-24 09:49:01 +08:00

35 lines
1.2 KiB
Ruby
Vendored

# frozen_string_literal: true
require "content_security_policy/builder"
require "content_security_policy/extension"
class ContentSecurityPolicy
class << self
def policy(theme_id = nil, base_url: Discourse.base_url, path_info: "/", report_only: false)
new.build(theme_id, base_url: base_url, path_info: path_info, report_only: report_only)
end
def nonce_placeholder(response_headers, request_env: nil)
response_headers[
::Middleware::CspScriptNonceInjector::PLACEHOLDER_HEADER
] ||= if request_env.nil? || request_env[::Middleware::AnonymousCache::CACHEABLE_ENV]
"[[csp_nonce_placeholder_#{SecureRandom.hex}]]"
else
request_env[::Middleware::CspScriptNonceInjector::NONCE_ENV] ||= SecureRandom.alphanumeric(
25,
)
end
end
end
def build(theme_id, base_url:, path_info: "/", report_only: false)
builder = Builder.new(base_url: base_url, report_only: report_only)
Extension.theme_extensions(theme_id).each { |extension| builder << extension }
Extension.plugin_extensions.each { |extension| builder << extension }
builder << Extension.site_setting_extension
builder.build
end
end
CSP = ContentSecurityPolicy