discourse/lib/content_security_policy/middleware.rb
David Taylor b1f74ab59e
FEATURE: Add experimental option for strict-dynamic CSP ()
The strict-dynamic CSP directive is supported in all our target browsers, and makes for a much simpler configuration. Instead of allowlisting paths, we use a per-request nonce to authorize `<script>` tags, and then those scripts are allowed to load additional scripts (or add additional inline scripts) without restriction.

This becomes especially useful when admins want to add external scripts like Google Tag Manager, or advertising scripts, which then go on to load a ton of other scripts.

All script tags introduced via themes will automatically have the nonce attribute applied, so it should be zero-effort for theme developers. Plugins *may* need some changes if they are inserting their own script tags.

This commit introduces a strict-dynamic-based CSP behind an experimental `content_security_policy_strict_dynamic` site setting.
2024-02-16 11:16:54 +00:00

46 lines
1.2 KiB
Ruby

# frozen_string_literal: true
require "content_security_policy"
class ContentSecurityPolicy
class Middleware
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
_, headers, _ = response = @app.call(env)
return response if headers["Content-Security-Policy"].present?
return response unless html_response?(headers)
# The EnforceHostname middleware ensures request.host_with_port can be trusted
protocol = (SiteSetting.force_https || request.ssl?) ? "https://" : "http://"
base_url = protocol + request.host_with_port + Discourse.base_path
theme_id = env[:resolved_theme_id]
headers["Content-Security-Policy"] = policy(
theme_id,
base_url: base_url,
path_info: env["PATH_INFO"],
) if SiteSetting.content_security_policy
headers["Content-Security-Policy-Report-Only"] = policy(
theme_id,
base_url: base_url,
path_info: env["PATH_INFO"],
) if SiteSetting.content_security_policy_report_only
response
end
private
delegate :policy, to: :ContentSecurityPolicy
def html_response?(headers)
headers["Content-Type"] && headers["Content-Type"] =~ /html/
end
end
end