mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-23 14:26:47 +09:00
Enforce a script policy that allows the importmap by hash
script-src 'self', object-src 'none' and base-uri 'self', enforced. The two inline scripts the importmap helper emits are allowed by their SHA-256, and the origins its URLs point to (a configured asset host, in any form Rails accepts) are allowed by name. There is no per-request nonce, so cached pages stay valid. DISABLE_CONTENT_SECURITY_POLICY=true leaves the policy off, for an install whose proxy or customization injects scripts.
This commit is contained in:
1 parent
cad2d7211d
commit
562888251e
5 files changed
+140
-24
No files matched your search
@@ -1,4 +1,4 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
include AllowBrowser, Authentication, Authorization, BlockBannedRequests, SetCurrentRequest, SetPlatform, TrackedRoomVisit, VersionHeaders, CachedResponses
|
||||
include AllowBrowser, Authentication, Authorization, BlockBannedRequests, SetCurrentRequest, SetPlatform, TrackedRoomVisit, VersionHeaders, CachedResponses, InlineScriptHashes
|
||||
include Turbo::Streams::Broadcasts, Turbo::Streams::StreamName
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# Allows the scripts the importmap helper emits: the inline importmap and its
|
||||
# entry-point module by content hash, and the origins its URLs point to. The
|
||||
# hashes change only when the importmap does (a deploy), so cached HTML stays
|
||||
# valid, unlike a per-request nonce.
|
||||
module InlineScriptHashes
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ENTRY_POINT_SCRIPT = %(import "application")
|
||||
|
||||
included do
|
||||
content_security_policy do |policy|
|
||||
if sources = policy.directives["script-src"]
|
||||
importmap = Rails.application.importmap.to_json(resolver: helpers)
|
||||
policy.directives["script-src"] = sources | script_origins(importmap) | script_hashes(importmap, ENTRY_POINT_SCRIPT)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# The importmap is resolved by Rails' asset helpers, so its URLs already
|
||||
# carry any configured asset host, however it's written: with or without a
|
||||
# scheme, protocol-relative, a %d set, or a proc.
|
||||
def script_origins(importmap)
|
||||
JSON.parse(importmap)["imports"].values.filter_map do |url|
|
||||
origin = URI.join(request.base_url, url).origin
|
||||
origin unless origin == request.base_url
|
||||
end.uniq
|
||||
end
|
||||
|
||||
def script_hashes(*scripts)
|
||||
scripts.map { |script| "'sha256-#{Digest::SHA256.base64digest(script)}'" }
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user