From 430c6dd269dad9759977675b2cb842f1ed9a9909 Mon Sep 17 00:00:00 2001 From: luelista Date: Thu, 9 Jul 2026 17:50:26 +0200 Subject: [PATCH] Use SafeStrings for plugin signals returning HTML that should be rendered (#6343) As a security precaution, we change the contract of some signals such that a SafeString needs to be returned if HTML should be rendered without further escaping. Before, the `{% signal ... %}` and `{% eventsignal ... %}` template tags called mark_safe themselves on all strings returned from signals. That could lead to unsafe coding practices, where untrusted values are interpolated into HTML format strings. However, such interpolations should usually be performed using helpers such Django's format_html, which automatically escapes inputs and returns a SafeString. Now, we call conditional_escape on signal results, so that any HTML not explicitly marked as safe gets escaped. Most plugins are not affected by this change as they return a SafeString as a result of Template.render already. --- src/pretix/base/models/event.py | 15 +++++------ src/pretix/base/signals.py | 5 ++-- src/pretix/base/templatetags/eventsignal.py | 5 ++-- src/pretix/control/signals.py | 16 +++++++++--- .../templates/pretixcontrol/event/live.html | 4 +-- src/pretix/presale/signals.py | 26 ++++++++++++------- 6 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index f8116bf57..e967e1560 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -1403,15 +1403,12 @@ class Event(EventMixin, LoggedModel): for mp in self.organizer.meta_properties.all(): if mp.required and not self.meta_data.get(mp.name): - issues.append( - ('' + gettext('You need to fill the meta parameter "{property}".') + '').format( - property=mp.name, - a_attr='href="%s#id_prop-%d-value"' % ( - reverse('control:event.settings', kwargs={'organizer': self.organizer.slug, 'event': self.slug}), - mp.pk - ) - ) - ) + issues.append(format_html( + '{text}', + text=gettext('You need to fill the meta parameter "{property}".').format(property=mp.name), + href=reverse('control:event.settings', kwargs={'organizer': self.organizer.slug, 'event': self.slug}), + href_hash=f'#id_prop-{mp.pk}-value', + )) responses = event_live_issues.send(self) for receiver, response in sorted(responses, key=lambda r: str(r[0])): diff --git a/src/pretix/base/signals.py b/src/pretix/base/signals.py index 108b24b0f..a9d1c3f6e 100644 --- a/src/pretix/base/signals.py +++ b/src/pretix/base/signals.py @@ -535,8 +535,9 @@ EventPluginRegistry = PluginAwareRegistry # for backwards compatibility event_live_issues = EventPluginSignal() """ This signal is sent out to determine whether an event can be taken live. If you want to -prevent the event from going live, return a string that will be displayed to the user -as the error message. If you don't, your receiver should return ``None``. +prevent the event from going live, return an error message to display to the user (either +as a SafeString containing HTML, or a string that will be HTML-escaped). If you don't, +your receiver should return ``None``. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ diff --git a/src/pretix/base/templatetags/eventsignal.py b/src/pretix/base/templatetags/eventsignal.py index 54f2da5df..f2e0ec2f4 100644 --- a/src/pretix/base/templatetags/eventsignal.py +++ b/src/pretix/base/templatetags/eventsignal.py @@ -22,6 +22,7 @@ import importlib from django import template +from django.utils.html import conditional_escape from django.utils.safestring import mark_safe from pretix.base.models import Event @@ -44,7 +45,7 @@ def eventsignal(event: Event, signame: str, **kwargs): _html = [] for receiver, response in signal.send(event, **kwargs): if response: - _html.append(response) + _html.append(conditional_escape(response)) return mark_safe("".join(_html)) @@ -63,5 +64,5 @@ def signal(signame: str, request, **kwargs): _html = [] for receiver, response in signal.send(request, **kwargs): if response: - _html.append(response) + _html.append(conditional_escape(response)) return mark_safe("".join(_html)) diff --git a/src/pretix/control/signals.py b/src/pretix/control/signals.py index 2a20f685e..f3b68cc85 100644 --- a/src/pretix/control/signals.py +++ b/src/pretix/control/signals.py @@ -39,7 +39,8 @@ from pretix.base.signals import ( html_page_start = GlobalSignal() """ This signal allows you to put code in the beginning of the main page for every -page in the backend. You are expected to return HTML. +page in the backend. You are expected to return a SafeString containing HTML, or +a string that will be HTML-escaped. The ``sender`` keyword argument will contain the request. """ @@ -129,7 +130,7 @@ event_dashboard_top = EventPluginSignal() Arguments: 'request' This signal is sent out to include custom HTML in the top part of the the event dashboard. -Receivers should return HTML. +Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. An additional keyword argument ``subevent`` *can* contain a sub-event. @@ -172,6 +173,7 @@ Arguments: 'form' This signal allows you to add additional HTML to the form that is used for modifying vouchers. You receive the form object in the ``form`` keyword argument. +Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. """ @@ -209,6 +211,7 @@ Arguments: 'quota' This signal allows you to append HTML to a Quota's detail view. You receive the quota as argument in the ``quota`` keyword argument. +Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. """ @@ -219,6 +222,7 @@ Arguments: 'subevent' This signal allows you to append HTML to a SubEvent's detail view. You receive the subevent as argument in the ``subevent`` keyword argument. +Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. """ @@ -265,7 +269,8 @@ order_info = EventPluginSignal() """ Arguments: ``order``, ``request`` -This signal is sent out to display additional information on the order detail page +This signal is sent out to display additional information on the order detail page. +Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. Additionally, the argument ``order`` and ``request`` are available. @@ -275,7 +280,8 @@ order_approve_info = EventPluginSignal() """ Arguments: ``order``, ``request`` -This signal is sent out to display additional information on the order approve page +This signal is sent out to display additional information on the order approve page. +Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. Additionally, the argument ``order`` and ``request`` are available. @@ -286,6 +292,7 @@ order_position_buttons = EventPluginSignal() Arguments: ``order``, ``position``, ``request`` This signal is sent out to display additional buttons for a single position of an order. +Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. Additionally, the argument ``order`` and ``request`` are available. @@ -315,6 +322,7 @@ Arguments: 'request' This signal is sent out to include template snippets on the settings page of an event that allows generating a pretix Widget code. +Receivers should return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. A second keyword argument ``request`` will contain the request object. diff --git a/src/pretix/control/templates/pretixcontrol/event/live.html b/src/pretix/control/templates/pretixcontrol/event/live.html index cd4d0e22e..70b0d3c7b 100644 --- a/src/pretix/control/templates/pretixcontrol/event/live.html +++ b/src/pretix/control/templates/pretixcontrol/event/live.html @@ -19,7 +19,7 @@

@@ -42,7 +42,7 @@

diff --git a/src/pretix/presale/signals.py b/src/pretix/presale/signals.py index 1111f00b2..007f9f459 100644 --- a/src/pretix/presale/signals.py +++ b/src/pretix/presale/signals.py @@ -161,7 +161,8 @@ voucher_redeem_info = EventPluginSignal() """ Arguments: ``voucher`` -This signal is sent out to display additional information on the "redeem a voucher" page +This signal is sent out to display additional information on the "redeem a voucher" page. +You are expected to return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. """ @@ -194,6 +195,7 @@ Arguments: ``request`` This signals allows you to add HTML content to the confirmation page that is presented at the end of the checkout process, just before the order is being created. +You are expected to return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. A ``request`` argument will contain the request object. @@ -276,7 +278,8 @@ order_info = EventPluginSignal() """ Arguments: ``order``, ``request`` -This signal is sent out to display additional information on the order detail page +This signal is sent out to display additional information on the order detail page. +Return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. """ @@ -285,7 +288,8 @@ position_info = EventPluginSignal() """ Arguments: ``order``, ``position``, ``request`` -This signal is sent out to display additional information on the position detail page +This signal is sent out to display additional information on the position detail page. +Return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. """ @@ -294,7 +298,8 @@ order_info_top = EventPluginSignal() """ Arguments: ``order``, ``request`` -This signal is sent out to display additional information on top of the order detail page +This signal is sent out to display additional information on top of the order detail page. +Return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. """ @@ -303,7 +308,8 @@ position_info_top = EventPluginSignal() """ Arguments: ``order``, ``position``, ``request`` -This signal is sent out to display additional information on top of the position detail page +This signal is sent out to display additional information on top of the position detail page. +Return a SafeString containing HTML, or a string that will be HTML-escaped. As with all event plugin signals, the ``sender`` keyword argument will contain the event. """ @@ -349,7 +355,7 @@ This signal is sent out to display additional information on the frontpage above of products and but below a custom frontpage text. As with all event plugin signals, the ``sender`` keyword argument will contain the event. The -receivers are expected to return HTML. +receivers are expected to return a SafeString containing HTML, or a string that will be HTML-escaped. """ render_seating_plan = EventPluginSignal() @@ -361,7 +367,7 @@ You will be passed the ``request`` as a keyword argument. If applicable, a ``sub ``voucher`` argument might be given. As with all event plugin signals, the ``sender`` keyword argument will contain the event. The -receivers are expected to return HTML. +receivers are expected to return a SafeString containing HTML, or a string that will be HTML-escaped. """ front_page_bottom = EventPluginSignal() @@ -372,7 +378,7 @@ This signal is sent out to display additional information on the frontpage below of products. As with all event plugin signals, the ``sender`` keyword argument will contain the event. The -receivers are expected to return HTML. +receivers are expected to return a SafeString containing HTML, or a string that will be HTML-escaped. """ front_page_bottom_widget = EventPluginSignal() @@ -383,7 +389,7 @@ This signal is sent out to display additional information on the frontpage below of products if the front page is shown in the widget. As with all event plugin signals, the ``sender`` keyword argument will contain the event. The -receivers are expected to return HTML. +receivers are expected to return a SafeString containing HTML, or a string that will be HTML-escaped. """ checkout_all_optional = EventPluginSignal() @@ -403,7 +409,7 @@ Arguments: ``item``, ``variation``, ``subevent`` This signal is sent out when the description of an item or variation is rendered and allows you to append additional text to the description. You are passed the ``item``, ``variation`` and ``subevent``. You are -expected to return HTML. +expected to return markdown. """ register_cookie_providers = EventPluginSignal()