diff --git a/frontend/src/scenes/surveys/SurveyAppearance.tsx b/frontend/src/scenes/surveys/SurveyAppearance.tsx index b964554d61360..5f90af5447c65 100644 --- a/frontend/src/scenes/surveys/SurveyAppearance.tsx +++ b/frontend/src/scenes/surveys/SurveyAppearance.tsx @@ -490,8 +490,10 @@ export function SurveyRatingAppearance({ )} -
{question}
- {description &&
{description}
} +
+ {description && ( +
+ )}
{ratingSurveyQuestion.display === 'emoji' && ( @@ -588,8 +590,10 @@ export function SurveyMultipleChoiceAppearance({
)} -
{question}
- {description &&
{description}
} +
+ {description && ( +
+ )}
{(multipleChoiceQuestion.choices || []).map((choice, idx) => (
diff --git a/posthog/api/survey.py b/posthog/api/survey.py index 81523782a86a2..1ef6dc1addc2d 100644 --- a/posthog/api/survey.py +++ b/posthog/api/survey.py @@ -98,11 +98,11 @@ def validate_appearance(self, value): thank_you_message = value.get("thankYouMessageHeader") if thank_you_message and nh3.is_html(thank_you_message): - value["thankYouMessageHeader"] = nh3.clean(thank_you_message) + value["thankYouMessageHeader"] = nh3_clean_with_whitelist(thank_you_message) thank_you_description = value.get("thankYouMessageDescription") if thank_you_description and nh3.is_html(thank_you_description): - value["thankYouMessageDescription"] = nh3.clean(thank_you_description) + value["thankYouMessageDescription"] = nh3_clean_with_whitelist(thank_you_description) return value @@ -128,9 +128,9 @@ def validate_questions(self, value): description = raw_question.get("description") if nh3.is_html(question_text): - cleaned_question["question"] = nh3.clean(question_text) + cleaned_question["question"] = nh3_clean_with_whitelist(question_text) if description and nh3.is_html(description): - cleaned_question["description"] = nh3.clean(description) + cleaned_question["description"] = nh3_clean_with_whitelist(description) cleaned_questions.append(cleaned_question) @@ -342,3 +342,142 @@ def surveys(request: Request): ).data return cors_response(request, JsonResponse({"surveys": surveys})) + + +def nh3_clean_with_whitelist(to_clean: str): + + return nh3.clean( + to_clean, + link_rel="noopener", + tags={ + "a", + "abbr", + "acronym", + "area", + "article", + "aside", + "b", + "bdi", + "bdo", + "blockquote", + "br", + "caption", + "center", + "cite", + "code", + "col", + "colgroup", + "data", + "dd", + "del", + "details", + "dfn", + "div", + "dl", + "dt", + "em", + "figcaption", + "figure", + "footer", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "header", + "hgroup", + "hr", + "i", + "img", + "ins", + "kbd", + "kbd", + "li", + "map", + "mark", + "nav", + "ol", + "p", + "pre", + "q", + "rp", + "rt", + "rtc", + "ruby", + "s", + "samp", + "small", + "span", + "strike", + "strong", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "th", + "thead", + "time", + "tr", + "tt", + "u", + "ul", + "var", + "wbr", + }, + attributes={ + "*": {"style", "lang", "title", "width", "height"}, + # below are mostly defaults to ammonia, but we need to add them explicitly + # because this python binding doesn't allow additive whitelisting + "a": {"href", "hreflang"}, + "bdo": {"dir"}, + "blockquote": {"cite"}, + "col": {"align", "char", "charoff", "span"}, + "colgroup": {"align", "char", "charoff", "span"}, + "del": {"cite", "datetime"}, + "hr": {"align", "size", "width"}, + "img": {"align", "alt", "height", "src", "width"}, + "ins": {"cite", "datetime"}, + "ol": {"start", "type"}, + "q": {"cite"}, + "table": {"align", "bgcolor", "border", "cellpadding", "cellspacing", "frame", "rules", "summary", "width"}, + "tbody": {"align", "char", "charoff", "valign"}, + "td": { + "abbr", + "align", + "axis", + "bgcolor", + "char", + "charoff", + "colspan", + "headers", + "height", + "nowrap", + "rowspan", + "scope", + "valign", + "width", + }, + "tfoot": {"align", "char", "charoff", "valign"}, + "th": { + "abbr", + "align", + "axis", + "bgcolor", + "char", + "charoff", + "colspan", + "headers", + "height", + "nowrap", + "rowspan", + "scope", + "valign", + "width", + }, + "thead": {"align", "char", "charoff", "valign"}, + "tr": {"align", "bgcolor", "char", "charoff", "valign"}, + }, + ) diff --git a/posthog/api/test/test_survey.py b/posthog/api/test/test_survey.py index 4225d33a27c16..e9bee0ecb1117 100644 --- a/posthog/api/test/test_survey.py +++ b/posthog/api/test/test_survey.py @@ -1,10 +1,12 @@ from datetime import datetime, timedelta from unittest.mock import ANY +import pytest from rest_framework import status from django.core.cache import cache from django.test.client import Client +from posthog.api.survey import nh3_clean_with_whitelist from posthog.models.feedback.survey import Survey from posthog.test.base import ( @@ -1069,3 +1071,33 @@ def test_responses_count_zero_responses(self): data = response.json() self.assertEqual(data, {}) + + +@pytest.mark.parametrize( + "test_input,expected", + [ + ( + """ +
+
+ Your Image +
+
+

Help us stay sharp.

+
+ """, + """ +
+
+ Your Image +
+
+

Help us stay sharp.

+
+
""", + ), + (""" """, """ """), + ], +) +def test_nh3_clean_configuration(test_input, expected): + assert nh3_clean_with_whitelist(test_input).replace(" ", "") == expected.replace(" ", "")