mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-26 23:46:49 +09:00
Compute a page's script sources once per importmap and host
Every page parsed the importmap and joined each of its URLs against the request's base URL, about a millisecond of CPU a page, though the answer changes only with the importmap and the host. Keep the last answer, keyed by both. One entry, so a request with a made-up Host header can't grow it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8d4a5264cf
commit
c8f74a0b0e
2 files changed
+30
-1
No files matched your search
@@ -14,13 +14,32 @@ module InlineScriptHashes
|
||||
content_security_policy if: -> { request.content_security_policy } do |policy|
|
||||
if sources = policy.directives["script-src"]
|
||||
importmap = Rails.application.importmap.to_json(resolver: helpers)
|
||||
policy.directives["script-src"] = (sources - [ "'self'" ]) | script_locations(importmap) | script_hashes(importmap, ENTRY_POINT_SCRIPT)
|
||||
policy.directives["script-src"] = (sources - [ "'self'" ]) | script_sources(importmap)
|
||||
policy.worker_src "#{request.host_with_port}#{service_worker_path(format: :js)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mattr_accessor :last_script_sources
|
||||
|
||||
private
|
||||
# Parsing the importmap and resolving each of its URLs costs about a
|
||||
# millisecond a page, yet the answer changes only with the importmap and
|
||||
# the host the page is served from. Keep the last one: a single entry, so
|
||||
# a request with a made-up Host header can't grow it.
|
||||
def script_sources(importmap)
|
||||
key = [ importmap, request.base_url, request.host_with_port, config.relative_url_root ]
|
||||
cached_key, sources = InlineScriptHashes.last_script_sources
|
||||
|
||||
if cached_key == key
|
||||
sources
|
||||
else
|
||||
(script_locations(importmap) | script_hashes(importmap, ENTRY_POINT_SCRIPT)).freeze.tap do |sources|
|
||||
InlineScriptHashes.last_script_sources = [ key, sources ].freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The importmap is resolved by Rails' asset helpers, so its URLs already
|
||||
# carry any relative root and asset host, however the host is written:
|
||||
# with or without a scheme, protocol-relative, a %d set, or a proc. Each
|
||||
|
||||
@@ -103,6 +103,16 @@ class ContentSecurityPolicyTest < ActionDispatch::IntegrationTest
|
||||
assert_equal "www.example.com:8443/service-worker.js", policy[/worker-src ([^;]+)/, 1]
|
||||
end
|
||||
|
||||
test "each host's pages carry that host's sources, one after another" do
|
||||
[ "www.example.com", "www.example.com:8443", "www.example.com" ].each do |host|
|
||||
host! host
|
||||
get PAGE_PATH
|
||||
follow_redirect! while response.redirect?
|
||||
|
||||
assert_includes response.headers["Content-Security-Policy"][/script-src ([^;]+)/, 1].split, "#{host}/assets/"
|
||||
end
|
||||
end
|
||||
|
||||
test "uploads can't load as scripts" do
|
||||
get PAGE_PATH
|
||||
follow_redirect! while response.redirect?
|
||||
|
||||
Reference in new issue
Block a user