Lightbox uses Stimulus target callbacks instead of data-action

Remove data-action from the sanitizer allowlist to disallow injection
of potentially malicious Stimulus actions in user-provided
content. The lightbox controller now uses imageTarget callbacks to
handle clicks on image links.

Also add the file name as a caption in the light box, and fix the
caption color for dark mode visibility.
This commit is contained in:
Mike Dalessio
2025-12-13 16:45:59 -05:00
parent 51df4af6ba
commit e9cb2956ee
6 changed files with 34 additions and 6 deletions
+1 -1
View File
@@ -67,7 +67,7 @@
}
.lightbox__caption {
color: var(--color-ink-inverted);
color: var(--color-white);
&:empty {
display: none;
@@ -3,9 +3,22 @@ import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "caption", "image", "dialog", "zoomedImage" ]
open(event) {
imageTargetConnected(element) {
element.addEventListener("click", this.#handleImageClick)
}
imageTargetDisconnected(element) {
element.removeEventListener("click", this.#handleImageClick)
}
#handleImageClick = (event) => {
event.preventDefault()
this.#open(event.currentTarget)
}
#open(link) {
this.dialogTarget.showModal()
this.#set(event.target.closest("a"))
this.#set(link)
}
// Wait for the transition to finish before resetting the image
@@ -15,7 +15,7 @@
<source src="<%= rails_blob_url(blob) %>" type="<%= blob.content_type %>">
</audio>
<% elsif blob.variable? %>
<%= link_to url_for(blob.variant(variant)), data: { action: "lightbox#open:prevent", lightbox_target: "image" } do %>
<%= link_to url_for(blob.variant(variant)), data: { lightbox_target: "image", lightbox_caption_value: blob.filename.to_s } do %>
<%= image_tag url_for(blob.variant(variant)), width: width, height: height %>
<% end %>
<% elsif blob.previewable? %>
@@ -1,5 +1,5 @@
<%= render "cards/display/common/background", card: card do %>
<%= link_to card.image.presence, class: "btn fill-transparent flex-item-justify-end", data: { controller: "tooltip", action: "lightbox#open:prevent", lightbox_target: "image" } do %>
<%= link_to card.image.presence, class: "btn fill-transparent flex-item-justify-end", data: { controller: "tooltip", lightbox_target: "image" } do %>
<%= icon_tag "picture-zoom" %>
<span class="for-screen-reader">Zoom background image</span>
<% end %>
+1 -1
View File
@@ -1,6 +1,6 @@
Rails.application.config.after_initialize do
Rails::HTML5::SafeListSanitizer.allowed_tags.merge(%w[ s table tr td th thead tbody details summary video source ])
Rails::HTML5::SafeListSanitizer.allowed_attributes.merge(%w[ data-turbo-frame controls type width data-action data-lightbox-target data-lightbox-url-value ])
Rails::HTML5::SafeListSanitizer.allowed_attributes.merge(%w[ data-turbo-frame data-lightbox-target data-lightbox-caption-value controls type width ])
# ugh, see https://github.com/rails/rails/issues/54478 which I need to fix upstream --mike
ActionText::ContentHelper.allowed_tags = Rails::HTML5::SafeListSanitizer.allowed_tags.to_a + [ ActionText::Attachment.tag_name, "figure", "figcaption" ] + ActionText::ContentHelper.allowed_tags.to_a
@@ -0,0 +1,15 @@
require "test_helper"
class ActionTextRenderingTest < ActionView::TestCase
test "data-action attributes in user content are stripped" do
malicious_html = <<~HTML
<p>Click here: <a href="#" data-action="dangerous#action">malicious link</a></p>
HTML
content = ActionText::Content.new(malicious_html)
rendered = content.to_s
assert_no_match(/data-action/, rendered)
assert_match(/<a href="#">malicious link<\/a>/, rendered)
end
end