Files
once-campfire/app/controllers/concerns/inline_script_hashes.rb
T
Jeremy Daer c3616f81b4 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.

(cherry picked from commit 562888251e)

Backport: 1.5.2 has no response cache, so the cached-page test is left out.
2026-10-17 00:47:32 -07:00

34 lines
1.3 KiB
Ruby

# 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