mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-09-25 17:54:55 +09:00
424219e485
Admin-authored custom styles were rendered inline into every page via custom_styles_tag, marked html_safe inside a <style> element. A stored payload containing </style><script>…</script> broke out of the style context and executed as HTML — a stored XSS reaching everyone who loads the app. Deliver the styles as an external stylesheet instead, following the account logo pattern: a new Accounts::CustomStyles#show renders the raw CSS with a text/css content type (ETag on the account, 5-minute public cache with stale-while-revalidate, fresh_custom_styles cache buster on account updates), and the layout links it with <link rel="stylesheet" data-turbo-track="reload">. In a CSS document, markup is inert text — the HTML-context breakout is closed by construction, and admins keep full custom-CSS capability.
30 lines
692 B
Ruby
30 lines
692 B
Ruby
class Accounts::CustomStylesController < ApplicationController
|
|
allow_unauthenticated_access only: :show
|
|
before_action :ensure_can_administer, :set_account, except: :show
|
|
|
|
def show
|
|
if stale?(etag: Current.account)
|
|
expires_in 5.minutes, public: true, stale_while_revalidate: 1.week
|
|
|
|
render plain: Current.account&.custom_styles.to_s, content_type: "text/css"
|
|
end
|
|
end
|
|
|
|
def edit
|
|
end
|
|
|
|
def update
|
|
@account.update!(account_params)
|
|
redirect_to edit_account_custom_styles_url, notice: "✓"
|
|
end
|
|
|
|
private
|
|
def set_account
|
|
@account = Current.account
|
|
end
|
|
|
|
def account_params
|
|
params.require(:account).permit(:custom_styles)
|
|
end
|
|
end
|