From 1925e42264633b6083698ef40a7a67e7124d032e Mon Sep 17 00:00:00 2001 From: Michael Josephson <30024+mikej@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:43:02 +0000 Subject: [PATCH 1/3] prevent potential XSS in flash messages flash messages were being assumed to be HTML-safe which feels too fragile as it relies on every developer remembering to escape any user input that's included in flash messages. fix by removing the `.html_safe` from the messages partial. the only place that seems to be relying on this behaviour is in `MeetingsController` where validation errors are joined into a single flash message using
tags. Updated this to just join the messages with commas instead. --- app/controllers/admin/meetings_controller.rb | 4 ++-- app/views/layouts/_messages.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/admin/meetings_controller.rb b/app/controllers/admin/meetings_controller.rb index 8a21c1715..c66aec006 100644 --- a/app/controllers/admin/meetings_controller.rb +++ b/app/controllers/admin/meetings_controller.rb @@ -13,7 +13,7 @@ def create if @meeting.save redirect_to [:admin, @meeting], notice: t('admin.messages.meeting.created') else - flash[:notice] = @meeting.errors.full_messages.join('
') + flash[:notice] = @meeting.errors.full_messages.join(', ') render :new end end @@ -33,7 +33,7 @@ def update if @meeting.update(meeting_params) redirect_to [:admin, @meeting], notice: t('admin.messages.meeting.updated') else - flash[:notice] = @meeting.errors.full_messages.join('
') + flash[:notice] = @meeting.errors.full_messages.join(', ') render 'edit' end end diff --git a/app/views/layouts/_messages.html.haml b/app/views/layouts/_messages.html.haml index 74cd2e569..9f0df4b93 100644 --- a/app/views/layouts/_messages.html.haml +++ b/app/views/layouts/_messages.html.haml @@ -3,11 +3,11 @@ - name = name.eql?('notice') ? 'info' : name - if msg.is_a?(String) .alert.alert-dismissible.fade.show.mb-0{ 'data-alert': '', class: "alert-#{name}", role: 'alert' } - = content_tag :div, msg.html_safe + = content_tag :div, msg %button.btn-close{ type: 'button', 'data-bs-dismiss': 'alert', 'aria-label': 'Close' } - elsif msg.is_a?(Array) - msg.each do |message| .alert.alert-dismissible.fade.show.mb-0{ 'data-alert': '', class: "alert-#{name}", role: 'alert' } - = content_tag :span, message.html_safe + = content_tag :span, message %button.btn-close{ type: 'button', 'data-bs-dismiss': 'alert', 'aria-label': 'Close' } From cbd78da441af449e2e63fa709765dea4d41a5396 Mon Sep 17 00:00:00 2001 From: Michael Josephson <30024+mikej@users.noreply.github.com> Date: Wed, 21 Jan 2026 14:49:02 +0000 Subject: [PATCH 2/3] prevent duplicate validation error when venue is blank venue gets validation as part of its belongs_to association which is required by default. this resulted in 2 validation error messages being shown: "Venue must be set" and "Venue can't be blank". remove the presence validator so we now get just "Venue must be set" --- app/models/meeting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/meeting.rb b/app/models/meeting.rb index 7c78ccdaf..ce0a3eae6 100644 --- a/app/models/meeting.rb +++ b/app/models/meeting.rb @@ -12,7 +12,7 @@ class Meeting < ApplicationRecord has_many :invitations, class_name: 'MeetingInvitation' has_and_belongs_to_many :chapters - validates :date_and_time, :ends_at, :venue, presence: true + validates :date_and_time, :ends_at, presence: true validates :slug, uniqueness: true, if: proc { |model| model.slug.present? } before_validation :set_date_and_time, :set_end_date_and_time From 81c79825240307bde52f3fb1d7dc46f5aa52fde1 Mon Sep 17 00:00:00 2001 From: Michael Josephson <30024+mikej@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:03:20 +0000 Subject: [PATCH 3/3] update failing specs for meeting validation update meeting specs to reflect removal of duplicate validation in cbd78da4 --- spec/features/admin/meeting_spec.rb | 2 +- spec/models/meeting_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/admin/meeting_spec.rb b/spec/features/admin/meeting_spec.rb index 3eb7ed243..a54caf611 100644 --- a/spec/features/admin/meeting_spec.rb +++ b/spec/features/admin/meeting_spec.rb @@ -31,7 +31,7 @@ click_on 'Save' - expect(page).to have_content('Venue can\'t be blank') + expect(page).to have_content('Venue must be set') end end diff --git a/spec/models/meeting_spec.rb b/spec/models/meeting_spec.rb index 00490e1af..b7895fcbb 100644 --- a/spec/models/meeting_spec.rb +++ b/spec/models/meeting_spec.rb @@ -6,7 +6,7 @@ subject(:meeting) { Fabricate(:meeting) } it { is_expected.to validate_presence_of(:date_and_time) } it { is_expected.to validate_presence_of(:ends_at) } - it { is_expected.to validate_presence_of(:venue) } + it { should belong_to(:venue) } context '#slug' do it 'fails when slug not present' do