mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-20 13:16:30 +09:00
Keep the CSP nonce cookie off public cacheable responses
The nonce generator runs for every response that carries the policy, so the nonce_id cookie was set on QR codes, logos, avatars and disk-served blobs — all public, shared-cacheable. Those endpoints now opt out of the policy, so no cache can replay one client's identifier and nonce.
This commit is contained in:
1 parent
465b372935
commit
9c8f4cd272
6 files changed
+35
No files matched your search
@@ -2,6 +2,7 @@ class Accounts::LogosController < ApplicationController
|
||||
include ActiveStorage::Streaming, ActionView::Helpers::AssetUrlHelper
|
||||
|
||||
allow_unauthenticated_access only: :show
|
||||
content_security_policy false, only: :show
|
||||
before_action :ensure_can_administer, only: :destroy
|
||||
|
||||
def show
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class QrCodeController < ApplicationController
|
||||
allow_unauthenticated_access
|
||||
content_security_policy false
|
||||
|
||||
def show
|
||||
url = Base64.urlsafe_decode64(params[:id])
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Users::AvatarsController < ApplicationController
|
||||
include ActiveStorage::Streaming
|
||||
|
||||
content_security_policy false, only: :show
|
||||
|
||||
rescue_from(ActiveSupport::MessageVerifier::InvalidSignature) { head :not_found }
|
||||
|
||||
def show
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
ActiveSupport.on_load(:active_storage_blob) do
|
||||
ActiveStorage::DiskController.content_security_policy false, only: :show
|
||||
|
||||
ActiveStorage::DiskController.after_action only: :show do
|
||||
response.set_header("Cache-Control", "max-age=3600, public")
|
||||
end
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
# Campfire sets no per-session verification cookie, so the lightweight
|
||||
# nonce_id cookie — set on first visit, present for every session including
|
||||
# unauthenticated ones — is the sole identifier.
|
||||
#
|
||||
# The generator runs for every response that carries the policy, so the cookie
|
||||
# write would otherwise land on public, shared-cacheable responses (QR codes,
|
||||
# logos, avatars, disk-served blobs) where a cache could replay one client's
|
||||
# identifier and nonce to others. Those endpoints opt out of the policy with
|
||||
# `content_security_policy false`; the nonce is only meaningful on documents.
|
||||
module CSP
|
||||
module Nonce
|
||||
COOKIE = "campfire_csp_nonce_id"
|
||||
|
||||
@@ -81,6 +81,29 @@ class CspNonceTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
test "public cacheable responses carry neither the policy nor the nonce cookie" do
|
||||
sign_in :david
|
||||
blob = ActiveStorage::Blob.create_and_upload!(io: file_fixture("moon.jpg").open, filename: "moon.jpg", content_type: "image/jpeg")
|
||||
get rails_blob_path(blob)
|
||||
disk_blob_url = response.location
|
||||
|
||||
[
|
||||
qr_code_path(Base64.urlsafe_encode64("http://example.com")),
|
||||
account_logo_path,
|
||||
user_avatar_path(users(:kevin).avatar_token),
|
||||
disk_blob_url
|
||||
].each do |path|
|
||||
cookies.delete CSP::Nonce::COOKIE
|
||||
|
||||
get path
|
||||
|
||||
assert_response :success
|
||||
assert response.cache_control[:public], "#{path} is expected to be publicly cacheable"
|
||||
assert_nil response.headers["Content-Security-Policy-Report-Only"], "#{path} must not carry the policy"
|
||||
assert_nil cookies[CSP::Nonce::COOKIE], "#{path} must not set the nonce cookie"
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user