mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-09-18 14:32:03 +09:00
Make CSP embed directives configurable per install
Campfire is self-hosted per customer: an admin may embed or connect to external hosts (video/embed providers, image CDNs, analytics, form or webhook endpoints) that vary per install and are unknown at build time. Hardcoding these directives at :self would break those integrations once the policy is enforced. Read per-install extras from ENV (CSP_EXTRA_SCRIPT_SRC, _STYLE_SRC, _IMG_SRC, _CONNECT_SRC, _FRAME_SRC, _FORM_ACTION), each a comma- or whitespace-separated host list, appended to the :self baseline. Unset by default, so the policy stays at :self only unless an admin opts in.
This commit is contained in:
@@ -47,7 +47,37 @@ class CspNonceTest < ActionDispatch::IntegrationTest
|
||||
assert_select "script[type='importmap'][nonce=?]", nonce
|
||||
end
|
||||
|
||||
test "a per-install ENV extra host is appended to its directive" do
|
||||
with_env "CSP_EXTRA_FRAME_SRC" => "https://player.vimeo.com https://www.youtube.com",
|
||||
"CSP_EXTRA_IMG_SRC" => "https://cdn.example.test" do
|
||||
header = ActionDispatch::ContentSecurityPolicy.new { |p| CSP.apply(p) }.build
|
||||
|
||||
assert_match %r{frame-src[^;]*\bhttps://player\.vimeo\.com\b}, header
|
||||
assert_match %r{frame-src[^;]*\bhttps://www\.youtube\.com\b}, header
|
||||
assert_match %r{img-src[^;]*\bhttps://cdn\.example\.test\b}, header
|
||||
# :self is preserved alongside the extras.
|
||||
assert_match %r{frame-src 'self'}, header
|
||||
end
|
||||
end
|
||||
|
||||
test "directives default to :self only when no ENV extras are set" do
|
||||
with_env "CSP_EXTRA_FRAME_SRC" => nil, "CSP_EXTRA_IMG_SRC" => nil do
|
||||
header = ActionDispatch::ContentSecurityPolicy.new { |p| CSP.apply(p) }.build
|
||||
|
||||
assert_match %r{frame-src 'self'(;|\z)}, header
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_env(vars)
|
||||
original = {}
|
||||
vars.each_key { |k| original[k] = ENV.key?(k) ? ENV[k] : :__unset__ }
|
||||
vars.each { |k, v| v.nil? ? ENV.delete(k) : ENV[k] = v }
|
||||
yield
|
||||
ensure
|
||||
original.each { |k, v| v == :__unset__ ? ENV.delete(k) : ENV[k] = v }
|
||||
end
|
||||
|
||||
def report_only_nonce
|
||||
response.headers["Content-Security-Policy-Report-Only"].to_s[/'nonce-([^']+)'/, 1]
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user