mirror of
https://github.com/discourse/discourse.git
synced 2026-08-04 10:39:43 +08:00
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.
32 lines
946 B
Ruby
Vendored
32 lines
946 B
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module Middleware
|
|
class CspScriptNonceInjector
|
|
NONCE_ENV = "discourse.csp_nonce"
|
|
PLACEHOLDER_HEADER = "Discourse-CSP-Nonce-Placeholder"
|
|
|
|
def initialize(app, settings = {})
|
|
@app = app
|
|
end
|
|
|
|
def call(env)
|
|
status, headers, response = @app.call(env)
|
|
|
|
if nonce_placeholder = headers.delete(PLACEHOLDER_HEADER)
|
|
nonce = env[NONCE_ENV] || SecureRandom.alphanumeric(25)
|
|
parts = response
|
|
if !env[NONCE_ENV]
|
|
parts = []
|
|
response.each { |part| parts << part.to_s.gsub(nonce_placeholder, nonce) }
|
|
end
|
|
%w[Content-Security-Policy Content-Security-Policy-Report-Only].each do |name|
|
|
next if headers[name].blank?
|
|
headers[name] = headers[name].sub("script-src ", "script-src 'nonce-#{nonce}' ")
|
|
end
|
|
[status, headers, parts]
|
|
else
|
|
[status, headers, response]
|
|
end
|
|
end
|
|
end
|
|
end
|