From 45c3481e420f2be9529f5a14b74d279a47e48bbc Mon Sep 17 00:00:00 2001
From: Eric Gardner <egardner@wikimedia.org>
Date: Wed, 22 Sep 2021 18:17:32 -0700
Subject: [PATCH] Close an XSS vulnerability in "did you mean" message

The "mediasearch-did-you-mean" message allowed HTML in order to
present a direct link to a suggested search query to the user.

Unfortunately this opened a backdoor for XSS attacks. If a malicious
user had entered a <script> tag into the search input field, some
"did you mean" links would include that script. Such a link could
then be shared via URL to others, exposing them to the attack.

We have no reason to believe that this has occurred; the vulnerability
was discovered in the process of fixing an unrelated bug in the same
codebase.

This change addresses the vulnerability through the following measures:

* Changes the "mediasearch-did-you-mean" message so it doesn't contain
  markup, just plain text
* Removes the "RawHtmlMessages" key from extension.json
* Removes "{{{" and v-html outputs for this message in Mustache and Vue
  respectively
* Reworks the mustache and vue.js templates to construct the link inside
  of their sandboxes like so:
    <a href="{{ didYouMeanLink }}">{{ didYouMeanMessage }}</a>
  This ensures that all values are escaped before anything is displayed
* Updates the message documentation and all message files to only allow
  a single parameter; no i18n files should contain markup any longer.

Bug: T291600
Change-Id: If64eb5842237c92290d07ebc3fe14710d9de3fc2
---
 extension.json                      | 3 ---
 i18n/ar.json                        | 2 +-
 i18n/bn.json                        | 2 +-
 i18n/ca.json                        | 2 +-
 i18n/de.json                        | 2 +-
 i18n/en.json                        | 2 +-
 i18n/es.json                        | 2 +-
 i18n/et.json                        | 2 +-
 i18n/fr.json                        | 2 +-
 i18n/he.json                        | 2 +-
 i18n/hr.json                        | 2 +-
 i18n/hsb.json                       | 2 +-
 i18n/hu.json                        | 2 +-
 i18n/it.json                        | 2 +-
 i18n/ja.json                        | 2 +-
 i18n/ko.json                        | 2 +-
 i18n/lmo.json                       | 2 +-
 i18n/lv.json                        | 2 +-
 i18n/mk.json                        | 2 +-
 i18n/nb.json                        | 2 +-
 i18n/nl.json                        | 2 +-
 i18n/om.json                        | 2 +-
 i18n/pl.json                        | 2 +-
 i18n/pt-br.json                     | 2 +-
 i18n/qqq.json                       | 2 +-
 i18n/ru.json                        | 2 +-
 i18n/sv.json                        | 2 +-
 i18n/tr.json                        | 2 +-
 i18n/uk.json                        | 2 +-
 i18n/vi.json                        | 2 +-
 i18n/zh-hans.json                   | 2 +-
 i18n/zh-hant.json                   | 2 +-
 resources/components/DidYouMean.vue | 6 +++---
 templates/SERPWidget.mustache       | 2 +-
 34 files changed, 35 insertions(+), 38 deletions(-)

diff --git a/extension.json b/extension.json
index 14c52b9..35cae1d 100644
--- a/extension.json
+++ b/extension.json
@@ -243,8 +243,5 @@
 	"MessagesDirs": {
 		"MediaSearch": [ "i18n" ]
 	},
-	"RawHtmlMessages": [
-		"mediasearch-did-you-mean"
-	],
 	"manifest_version": 2
 }
diff --git a/i18n/ar.json b/i18n/ar.json
index 6bc57fa..ebce0fa 100644
--- a/i18n/ar.json
+++ b/i18n/ar.json
@@ -68,7 +68,7 @@
 	"mediasearch-results-count": "$1 {{PLURAL:$1|نتيجة|نتائج}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|كلمة|كلمات}})",
 	"mediasearch-image-size": "($1)",
-	"mediasearch-did-you-mean": "هل تعني: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "هل تعني: $1",
 	"mediasearch-error-message": "بحث غير صالح",
 	"mediasearch-error-text": "أدخِل كلمات جديدة في الأعلى وابحث مجدداً",
 	"mediasearch-specialsearch-default": "أظهر نتائج البحث في خاص:واجهة البحث",
diff --git a/i18n/bn.json b/i18n/bn.json
index fd9a2be..558bcc1 100644
--- a/i18n/bn.json
+++ b/i18n/bn.json
@@ -65,7 +65,7 @@
 	"mediasearch-clear-filters": "ছাঁকনি পরিষ্কার করুন",
 	"mediasearch-results-count": "$1টি {{PLURAL:$1|ফলাফল}}",
 	"mediasearch-wordcount": "($1টি {{PLURAL:$1|শব্দ}})",
-	"mediasearch-did-you-mean": "আপনি কি বোঝাতে চেয়েছেন: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "আপনি কি বোঝাতে চেয়েছেন: $1",
 	"mediasearch-error-message": "অবৈধ অনুসন্ধান",
 	"mediasearch-error-text": "উপরে একটি নতুন অনুসন্ধান লিখুন এবং আবার চেষ্টা করুন",
 	"mediasearch-specialsearch-default": "বিশেষ:অনুসন্ধান ইন্টারফেসে অনুসন্ধানের ফলাফলগুলি দেখান",
diff --git a/i18n/ca.json b/i18n/ca.json
index ff15143..6ac336c 100644
--- a/i18n/ca.json
+++ b/i18n/ca.json
@@ -63,7 +63,7 @@
 	"mediasearch-results-count": "$1 {{PLURAL:$1|resultat|resultats}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|paraula|paraules}})",
 	"mediasearch-image-size": "($1)",
-	"mediasearch-did-you-mean": "Potser volíeu dir: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Potser volíeu dir: $1",
 	"mediasearch-error-message": "Recerca no vàlida",
 	"mediasearch-error-text": "Entreu a dalt una nova cerca i provar de nou",
 	"mediasearch-specialsearch-default": "Mostra els resultats de la cerca a la interfície de Special:Search",
diff --git a/i18n/de.json b/i18n/de.json
index 0cd3c19..ceba11b 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -66,7 +66,7 @@
 	"mediasearch-clear-filters": "Filter aufheben",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|Ergebnis|Ergebnisse}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|Wort|$1 Wörter}})",
-	"mediasearch-did-you-mean": "Meinst du: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Meinst du: $1",
 	"mediasearch-error-message": "Ungültige Suche",
 	"mediasearch-error-text": "Gib oben eine neue Suche ein und versuche es erneut",
 	"mediasearch-specialsearch-default": "Suchergebnisse in der Special:Search-Oberfläche anzeigen",
diff --git a/i18n/en.json b/i18n/en.json
index 663adea..ec14eb9 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -62,7 +62,7 @@
 	"mediasearch-results-count": "$1 {{PLURAL:$1|result|results}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|word|words}})",
 	"mediasearch-image-size": "($1)",
-	"mediasearch-did-you-mean": "Did you mean: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Did you mean: $1",
 	"mediasearch-error-message": "Invalid search",
 	"mediasearch-error-text": "Enter a new search above and try again",
 	"mediasearch-specialsearch-default": "Show search results in the Special:Search interface",
diff --git a/i18n/es.json b/i18n/es.json
index 1414d91..4b5fecd 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -70,7 +70,7 @@
 	"mediasearch-clear-filters": "Quitar filtros",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|resultado|resultados}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|palabra|palabras}})",
-	"mediasearch-did-you-mean": "Quizás quiso decir: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Quizás quiso decir: $1",
 	"mediasearch-error-message": "Búsqueda inválida",
 	"mediasearch-error-text": "Ingresa arriba una nueva búsqueda e intenta de nuevo",
 	"mediasearch-specialsearch-default": "Mostrar resultados de búsqueda en la interfaz de Special:Search",
diff --git a/i18n/et.json b/i18n/et.json
index 85c47d7..926ca7f 100644
--- a/i18n/et.json
+++ b/i18n/et.json
@@ -52,7 +52,7 @@
 	"mediasearch-copytextlayout-copy-success": "Kopeeritud lõikelauale.",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|tulemus|tulemust}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|sõna}})",
-	"mediasearch-did-you-mean": "Kas mõtlesid: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Kas mõtlesid: $1",
 	"mediasearch-specialsearch-default": "Näita otsitulemusi leheküljel Special:Search",
 	"mediasearch-specialsearch-default-help": "Selle valikuga näidatakse peamise otsiriba tulemusi vanema lehekülje Special:Search vahendusel, lehekülje Special:MediaSearch asemel.",
 	"mediasearch-filter-assessment-picture-of-the-day": "Päeva pilt",
diff --git a/i18n/fr.json b/i18n/fr.json
index 4009fed..152cd3e 100644
--- a/i18n/fr.json
+++ b/i18n/fr.json
@@ -74,7 +74,7 @@
 	"mediasearch-results-count": "$1 résultat{{PLURAL:$1||s}}",
 	"mediasearch-wordcount": "($1 mot{{PLURAL:$1||s}})",
 	"mediasearch-image-size": "($1)",
-	"mediasearch-did-you-mean": "Vouliez-vous dire : <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Vouliez-vous dire : $1",
 	"mediasearch-error-message": "Recherche incorrecte",
 	"mediasearch-error-text": "Entrez une nouvelle recherche ci-dessus et essayez à nouveau",
 	"mediasearch-specialsearch-default": "Afficher les résultats de recherche dans l’interface Spécial:Recherche",
diff --git a/i18n/he.json b/i18n/he.json
index 1ade79c..7deec86 100644
--- a/i18n/he.json
+++ b/i18n/he.json
@@ -63,7 +63,7 @@
 	"mediasearch-clear-filters": "ניקוי מסננים",
 	"mediasearch-results-count": "{{PLURAL:$1|תוצאה אחת|$1 תוצאות}}",
 	"mediasearch-wordcount": "({{PLURAL:$1|מילה אחת|$1 מילים}})",
-	"mediasearch-did-you-mean": "האם התכוונת לכתוב: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "האם התכוונת לכתוב: $1",
 	"mediasearch-error-message": "חיפוש בלתי־תקין",
 	"mediasearch-error-text": "נא להזין חיפוש חדש לעיל ולנסות שוב",
 	"mediasearch-specialsearch-default": "להציג את תוצאות החיפוש בממשק Special:Search",
diff --git a/i18n/hr.json b/i18n/hr.json
index d74ffee..ff188cc 100644
--- a/i18n/hr.json
+++ b/i18n/hr.json
@@ -60,7 +60,7 @@
 	"mediasearch-clear-filters": "Očisti filtar",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|rezultat|rezultata}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|riječ|riječi}})",
-	"mediasearch-did-you-mean": "Jeste li mislili: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Jeste li mislili: $1",
 	"mediasearch-error-message": "Nevaljala pretraga",
 	"mediasearch-error-text": "Unesite iznad novu pretragu i pokušajte ponovo",
 	"mediasearch-specialsearch-default": "Prikaži rezultate pretrage kao na sučelju Special:Search",
diff --git a/i18n/hsb.json b/i18n/hsb.json
index 34afb99..814ffe3 100644
--- a/i18n/hsb.json
+++ b/i18n/hsb.json
@@ -46,5 +46,5 @@
 	"mediasearch-results-count": "$1 {{PLURAL:$1|wuslědk|wuslědkaj|wuslědki|wuslědkow}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|słowo|słowje|słowa|słowow}})",
 	"mediasearch-image-size": "($1)",
-	"mediasearch-did-you-mean": "Měniš ty: <a href=\"$2\">$1</a>"
+	"mediasearch-did-you-mean": "Měniš ty: $1"
 }
diff --git a/i18n/hu.json b/i18n/hu.json
index 33c1f42..fe57f8f 100644
--- a/i18n/hu.json
+++ b/i18n/hu.json
@@ -55,7 +55,7 @@
 	"mediasearch-clear-filters": "Szűrők törlése",
 	"mediasearch-results-count": "$1 találat",
 	"mediasearch-wordcount": "($1 szó)",
-	"mediasearch-did-you-mean": "Erre gondoltál: <a href=\"$2\">$1</a> ?",
+	"mediasearch-did-you-mean": "Erre gondoltál: $1 ?",
 	"mediasearch-error-message": "Érvénytelen keresés",
 	"mediasearch-error-text": "Írj be egy új keresést és próbáld ismét"
 }
diff --git a/i18n/it.json b/i18n/it.json
index b0638d8..265a584 100644
--- a/i18n/it.json
+++ b/i18n/it.json
@@ -44,7 +44,7 @@
 	"mediasearch-copytextlayout-copy-success": "Copiato negli appunti.",
 	"mediasearch-clear-filters": "Cancella filtri",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|risultato|risultati}}",
-	"mediasearch-did-you-mean": "Forse cercavi: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Forse cercavi: $1",
 	"mediasearch-error-message": "Ricerca non valida",
 	"mediasearch-filter-assessment-picture-of-the-day": "Immagine del giorno",
 	"mediasearch-filter-assessment-picture-of-the-year": "Immagine dell'anno"
diff --git a/i18n/ja.json b/i18n/ja.json
index f36e5d2..bbafd82 100644
--- a/i18n/ja.json
+++ b/i18n/ja.json
@@ -56,7 +56,7 @@
 	"mediasearch-copytextlayout-copy-fail": "クリップボードにコピーできませんでした。",
 	"mediasearch-copytextlayout-copy-success": "クリップボードにコピーされました。",
 	"mediasearch-clear-filters": "フィルターをクリア",
-	"mediasearch-did-you-mean": "もしかして: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "もしかして: $1",
 	"mediasearch-error-message": "不正な検索",
 	"mediasearch-filter-assessment-picture-of-the-day": "今日の一枚",
 	"mediasearch-filter-assessment-picture-of-the-year": "今年の一枚",
diff --git a/i18n/ko.json b/i18n/ko.json
index bd96054..056c6de 100644
--- a/i18n/ko.json
+++ b/i18n/ko.json
@@ -63,7 +63,7 @@
 	"mediasearch-clear-filters": "필터 지우기",
 	"mediasearch-results-count": "$1개 {{PLURAL:$1|결과}}",
 	"mediasearch-wordcount": "($1{{PLURAL:$1|어절}})",
-	"mediasearch-did-you-mean": "이것을 찾으셨나요: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "이것을 찾으셨나요: $1",
 	"mediasearch-error-message": "잘못된 검색",
 	"mediasearch-error-text": "위에 새 검색을 입력하고 다시 시도하십시오",
 	"mediasearch-specialsearch-default": "검색 결과를 특수:검색 인터페이스에 표시",
diff --git a/i18n/lmo.json b/i18n/lmo.json
index b69fd88..f63a854 100644
--- a/i18n/lmo.json
+++ b/i18n/lmo.json
@@ -58,7 +58,7 @@
 	"mediasearch-clear-filters": "Scancela i filter",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|risultad}}",
 	"mediasearch-wordcount": "$1 ({{PLURAL:$1|parola|$1 parole}})",
-	"mediasearch-did-you-mean": "Te vorevet dì: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Te vorevet dì: $1",
 	"mediasearch-error-message": "Ricerca minga bona",
 	"mediasearch-error-text": "Met denter un altra ricerca e provegh anmò.",
 	"mediasearch-specialsearch-default": "Fà vedè i risultad de la ricerca in del SpecialːInterfacia de ricerca.",
diff --git a/i18n/lv.json b/i18n/lv.json
index ebf1efe..a41221c 100644
--- a/i18n/lv.json
+++ b/i18n/lv.json
@@ -30,5 +30,5 @@
 	"mediasearch-quickview-button-text": "Vairāk informācijas",
 	"mediasearch-copytextlayout-copy": "Kopēt",
 	"mediasearch-clear-filters": "Notīrīt filtrus",
-	"mediasearch-did-you-mean": "Vai tu domāji: <a href=\"$2\">$1</a>"
+	"mediasearch-did-you-mean": "Vai tu domāji: $1"
 }
diff --git a/i18n/mk.json b/i18n/mk.json
index cb62bac..422dee2 100644
--- a/i18n/mk.json
+++ b/i18n/mk.json
@@ -61,7 +61,7 @@
 	"mediasearch-clear-filters": "Исчисти филтри",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|ставка|ставки}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|збор|збора}})",
-	"mediasearch-did-you-mean": "Дали мислевте на: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Дали мислевте на: $1",
 	"mediasearch-error-message": "Неважечко пребарување",
 	"mediasearch-error-text": "Погоре внесете ново пребарување и обидете се пак",
 	"mediasearch-specialsearch-default": "Прикажувај исход од пребарување во посредникот Special:Search interface",
diff --git a/i18n/nb.json b/i18n/nb.json
index fb2ee7b..de91e20 100644
--- a/i18n/nb.json
+++ b/i18n/nb.json
@@ -61,7 +61,7 @@
 	"mediasearch-clear-filters": "Nullstill filtre",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|resultat|resultater}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|ord}})",
-	"mediasearch-did-you-mean": "Mente du: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Mente du: $1",
 	"mediasearch-error-message": "Ugyldig søk",
 	"mediasearch-error-text": "Skriv et nytt søk og prøv igjen",
 	"mediasearch-specialsearch-default": "Vis søkeresultater i Special:Search-grensesnittet",
diff --git a/i18n/nl.json b/i18n/nl.json
index 55337bd..a0472af 100644
--- a/i18n/nl.json
+++ b/i18n/nl.json
@@ -70,7 +70,7 @@
 	"mediasearch-results-count": "$1 {{PLURAL:$1|resulaat|resultaten}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|woord|woorden}})",
 	"mediasearch-image-size": "($1)",
-	"mediasearch-did-you-mean": "Bedoelde u: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Bedoelde u: $1",
 	"mediasearch-error-message": "Ongeldige zoekopdracht",
 	"mediasearch-error-text": "Voer hierboven een nieuwe zoekopdracht in en probeer het opnieuw",
 	"mediasearch-specialsearch-default": "Toon zoekresultaten in de Special:Search-interface",
diff --git a/i18n/om.json b/i18n/om.json
index 8ef87de..48057ed 100644
--- a/i18n/om.json
+++ b/i18n/om.json
@@ -14,6 +14,6 @@
 	"mediasearch-user-notice-title": "[[Special:Search]] ilaalaayirtaa?",
 	"mediasearch-user-notice-dismiss": "Yaadachiisa kana balleessi",
 	"mediasearch-switch-special-search": "Ka addaa bani:Barbaadi",
-	"mediasearch-did-you-mean": "<a href=\"$2\">$1</a>:Jechuu beektaa",
+	"mediasearch-did-you-mean": "$1:Jechuu beektaa",
 	"mediasearch-specialsearch-default": "Addatti bu'aa barbaachaa argisiisi:Qunnama barbaadi"
 }
diff --git a/i18n/pl.json b/i18n/pl.json
index 90e2d11..a147519 100644
--- a/i18n/pl.json
+++ b/i18n/pl.json
@@ -65,7 +65,7 @@
 	"mediasearch-clear-filters": "Wyczyść filtry",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|wynik|wyniki|wyników}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|słowo|słowa|słów}})",
-	"mediasearch-did-you-mean": "Czy masz na myśli: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Czy masz na myśli: $1",
 	"mediasearch-error-message": "Nieprawidłowe wyszukiwanie",
 	"mediasearch-error-text": "Wpisz nowe wyszukiwanie powyżej i spróbuj ponownie",
 	"mediasearch-specialsearch-default": "Pokaż wyniki wyszukiwania w interfejsie {{#special:Search}}",
diff --git a/i18n/pt-br.json b/i18n/pt-br.json
index f973c99..5a42b80 100644
--- a/i18n/pt-br.json
+++ b/i18n/pt-br.json
@@ -63,7 +63,7 @@
 	"mediasearch-clear-filters": "Limpar filtros",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|resultado|resultados}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|palavara|palavaras}})",
-	"mediasearch-did-you-mean": "Você quis dizer: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Você quis dizer: $1",
 	"mediasearch-error-message": "Pesquisa inválida",
 	"mediasearch-error-text": "Insira uma nova pesquisa acima e tente novamente",
 	"mediasearch-specialsearch-default": "Mostre os resultados da pesquisa na interface Especial:Pesquisa",
diff --git a/i18n/qqq.json b/i18n/qqq.json
index cf6017c..d0c9ee2 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -69,7 +69,7 @@
 	"mediasearch-results-count": "Total number of results for a search. Parameters:\n* $1 - Number of results (totalhits)",
 	"mediasearch-wordcount": "Wordcount of a page, wrapped in parentheses. Parameters:\n* $1 - Number of words (wordcount)",
 	"mediasearch-image-size": "{{optional}}\nSize of an image, wrapped in parentheses. Parameters:\n* $1 - Image size",
-	"mediasearch-did-you-mean": "Text prompting the user to search for an alternate search term if they entered a likely typo. Parameters:\n* $1 - Suggested term\n* $2 - Link to search results for suggested term",
+	"mediasearch-did-you-mean": "Text prompting the user to search for an alternate search term if they entered a likely typo. Parameters:\n* $1 - Suggested term",
 	"mediasearch-error-message": "Error message informing the user of an invalid search",
 	"mediasearch-error-text": "Text prompting the user to retry their search query",
 	"mediasearch-specialsearch-default": "Title of preference to use Special:Search as the default search (instead of Special:MediaSearch)",
diff --git a/i18n/ru.json b/i18n/ru.json
index 57b2ee1..3041fe3 100644
--- a/i18n/ru.json
+++ b/i18n/ru.json
@@ -74,7 +74,7 @@
 	"mediasearch-clear-filters": "Очистить фильтры",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|результат|результата|результатов}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|слово|слова|слов}})",
-	"mediasearch-did-you-mean": "Вы имели в виду: <a href=\"$2\">$1</а>",
+	"mediasearch-did-you-mean": "Вы имели в виду: $1",
 	"mediasearch-error-message": "Неверный поисковый запрос",
 	"mediasearch-error-text": "Введите новый запрос выше и повторите попытку",
 	"mediasearch-specialsearch-default": "Показать результаты поиска в обычном интерфейсе (Special:Search)",
diff --git a/i18n/sv.json b/i18n/sv.json
index 03fed11..67c32d4 100644
--- a/i18n/sv.json
+++ b/i18n/sv.json
@@ -62,7 +62,7 @@
 	"mediasearch-clear-filters": "Rensa filter",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|resultat}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|ord}})",
-	"mediasearch-did-you-mean": "Menade du: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Menade du: $1",
 	"mediasearch-error-message": "Ogiltig sökning",
 	"mediasearch-error-text": "Skriv en ny sökning och försök igen",
 	"mediasearch-specialsearch-default": "Visa sökresultat i gränssnittet Special:Search",
diff --git a/i18n/tr.json b/i18n/tr.json
index 42594ee..7f5fb7a 100644
--- a/i18n/tr.json
+++ b/i18n/tr.json
@@ -69,7 +69,7 @@
 	"mediasearch-clear-filters": "Filtreleri temizle",
 	"mediasearch-results-count": "$1 {{PLURAL:$1|sonuç|sonuç}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|kelime|kelime}})",
-	"mediasearch-did-you-mean": "<a href=\"$2\">$1</a> demek mi istediniz",
+	"mediasearch-did-you-mean": "$1 demek mi istediniz",
 	"mediasearch-error-message": "Geçersiz arama",
 	"mediasearch-error-text": "Yukarıya yeni bir arama girin ve tekrar deneyin",
 	"mediasearch-specialsearch-default": "Arama sonuçlarını Special:Search arayüzünde göster",
diff --git a/i18n/uk.json b/i18n/uk.json
index a09b79c..3302d00 100644
--- a/i18n/uk.json
+++ b/i18n/uk.json
@@ -69,7 +69,7 @@
 	"mediasearch-results-count": "$1 {{PLURAL:$1|результат|результати|результатів}}",
 	"mediasearch-wordcount": "($1 {{PLURAL:$1|слово|слова|слів}})",
 	"mediasearch-image-size": "($1)",
-	"mediasearch-did-you-mean": "Чи мали ви на увазі: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Чи мали ви на увазі: $1",
 	"mediasearch-error-message": "Недійсний пошук",
 	"mediasearch-error-text": "Введіть новий пошук вище та повторіть спробу",
 	"mediasearch-specialsearch-default": "Показувати результати пошуку в інтерфейсі Special:Search",
diff --git a/i18n/vi.json b/i18n/vi.json
index f4bd408..71b42a1 100644
--- a/i18n/vi.json
+++ b/i18n/vi.json
@@ -61,7 +61,7 @@
 	"mediasearch-clear-filters": "Đặt lại bộ lọc",
 	"mediasearch-results-count": "$1 kết quả",
 	"mediasearch-wordcount": "($1 từ)",
-	"mediasearch-did-you-mean": "Có phải bạn muốn: <a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "Có phải bạn muốn: $1",
 	"mediasearch-error-message": "Truy vấn không hợp lệ",
 	"mediasearch-error-text": "Hãy nhập truy vấn mới bên trên và thử lại",
 	"mediasearch-specialsearch-default": "Trình bày kết quả tìm kiếm trong giao diện Special:Search",
diff --git a/i18n/zh-hans.json b/i18n/zh-hans.json
index 1c6b39a..aec8463 100644
--- a/i18n/zh-hans.json
+++ b/i18n/zh-hans.json
@@ -82,7 +82,7 @@
 	"mediasearch-clear-filters": "清除过滤器",
 	"mediasearch-results-count": "$1个结果",
 	"mediasearch-wordcount": "（$1个字）",
-	"mediasearch-did-you-mean": "你的意思是不是：<a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "你的意思是不是：$1",
 	"mediasearch-error-message": "搜索无效",
 	"mediasearch-error-text": "在上方输入新搜索并重试",
 	"mediasearch-specialsearch-default": "在Special:Search界面中显示搜索结果",
diff --git a/i18n/zh-hant.json b/i18n/zh-hant.json
index 1c61dae..bce4472 100644
--- a/i18n/zh-hant.json
+++ b/i18n/zh-hant.json
@@ -67,7 +67,7 @@
 	"mediasearch-clear-filters": "清除篩選",
 	"mediasearch-results-count": "$1個結果",
 	"mediasearch-wordcount": "（$1個字）",
-	"mediasearch-did-you-mean": "您是不是指：<a href=\"$2\">$1</a>",
+	"mediasearch-did-you-mean": "您是不是指：$1",
 	"mediasearch-error-message": "無效搜尋",
 	"mediasearch-error-text": "在上方輸入新搜尋並重試",
 	"mediasearch-specialsearch-default": "顯示在 Special:Search 介面的搜尋結果",
diff --git a/resources/components/DidYouMean.vue b/resources/components/DidYouMean.vue
index e73a4f3..e9a4d22 100644
--- a/resources/components/DidYouMean.vue
+++ b/resources/components/DidYouMean.vue
@@ -1,8 +1,8 @@
 <template>
 	<!-- eslint-disable vue/no-v-html -->
 	<div v-if="didYouMean"
-		class="sdms-did-you-mean"
-		v-html="didYouMeanMessage">
+		class="sdms-did-you-mean">
+		<a :href="didYouMeanLink">{{ didYouMeanMessage }}</a>
 	</div>
 	<!-- eslint-enable vue/no-v-html -->
 </template>
@@ -45,7 +45,7 @@ module.exports = {
 		 */
 		didYouMeanMessage: function () {
 			return this.$i18n( 'mediasearch-did-you-mean' )
-				.params( [ this.didYouMean, this.didYouMeanLink ] )
+				.params( [ this.didYouMean ] )
 				.text();
 		}
 	} )
diff --git a/templates/SERPWidget.mustache b/templates/SERPWidget.mustache
index 73d7c35..758f746 100644
--- a/templates/SERPWidget.mustache
+++ b/templates/SERPWidget.mustache
@@ -122,7 +122,7 @@
 
 					{{#didYouMean}}
 					<div class="sdms-did-you-mean">
-						{{{ didYouMeanMessage }}}
+						<a href="{{ didYouMeanLink }}">{{ didYouMeanMessage }}</a>
 					</div>
 					{{/didYouMean}}
 
-- 
2.30.1 (Apple Git-130)

