From 97096c698228034b429f5d311c9af5d01eb97467 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Wed, 2 Oct 2024 11:55:24 -0400 Subject: [PATCH] fix: sanitize email HTML templates (#5108) ## Which issue(s) this PR closes Closes https://github.com/grafana/oncall-private/issues/2760 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. --- engine/apps/alerts/paging.py | 5 +++++ engine/apps/alerts/tests/test_paging.py | 22 ++++++++++++++++++++++ engine/common/utils.py | 2 +- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/engine/apps/alerts/paging.py b/engine/apps/alerts/paging.py index b03f52d7..5121d017 100644 --- a/engine/apps/alerts/paging.py +++ b/engine/apps/alerts/paging.py @@ -16,6 +16,7 @@ from apps.alerts.tasks.notify_user import notify_user_task from apps.schedules.ical_utils import get_cached_oncall_users_for_multiple_schedules from apps.schedules.models import OnCallSchedule from apps.user_management.models import Organization, Team, User +from common.utils import escape_html UserNotifications = list[tuple[User, bool]] @@ -145,6 +146,10 @@ def direct_paging( if alert_group and alert_group.resolved: raise DirectPagingAlertGroupResolvedError + # https://github.com/grafana/oncall-private/issues/2760 + title = escape_html(title) + message = escape_html(message) + if title is None: title = _construct_title(from_user, team, users) diff --git a/engine/apps/alerts/tests/test_paging.py b/engine/apps/alerts/tests/test_paging.py index 0528782d..d6bad7a2 100644 --- a/engine/apps/alerts/tests/test_paging.py +++ b/engine/apps/alerts/tests/test_paging.py @@ -312,3 +312,25 @@ def test_construct_title(make_organization, make_team, make_user_for_organizatio assert _construct_title(from_user, team, multiple_users) == _title( f"{team.name}, {user1.username}, {user2.username} and {user3.username}" ) + + +@pytest.mark.django_db +def test_direct_paging_title_and_message_are_html_escaped(make_organization, make_user_for_organization): + dirty_input = "" + clean_input = "<script>alert('hacked');</script>" + + organization = make_organization() + from_user = make_user_for_organization(organization) + other_user = make_user_for_organization(organization) + + direct_paging(organization, from_user, dirty_input, dirty_input, users=[(other_user, False)]) + + # alert group created + alert_groups = AlertGroup.objects.all() + assert alert_groups.count() == 1 + ag = alert_groups.get() + alert = ag.alerts.get() + + assert ag.web_title_cache == clean_input + assert alert.title == clean_input + assert alert.message == clean_input diff --git a/engine/common/utils.py b/engine/common/utils.py index 395a1306..dc20b9ec 100644 --- a/engine/common/utils.py +++ b/engine/common/utils.py @@ -249,7 +249,7 @@ def clean_markup(text): def escape_html(text): - return html.escape(text, quote=False) + return html.escape(text, quote=False) if text else text def urlize_with_respect_to_a(html):