Cross-site Scripting in Weblate
Description
Weblate is a copyleft software web-based continuous localization system. Versions prior to 4.11 do not properly neutralize user input used in user name and language fields. Due to this improper neutralization it is possible to perform cross-site scripting via these fields. The issues were fixed in the 4.11 release. Users unable to upgrade are advised to add their own neutralize logic.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
WeblatePyPI | < 4.11 | 4.11 |
Affected products
1- Range: < 4.11
Patches
3f6753a1a1c63translate: Add missing escaping to language name
1 file changed · +2 −1
weblate/trans/forms.py+2 −1 modified@@ -37,6 +37,7 @@ from django.template.loader import render_to_string from django.urls import reverse from django.utils import timezone +from django.utils.html import escape from django.utils.http import urlencode from django.utils.safestring import mark_safe from django.utils.translation import gettext @@ -318,7 +319,7 @@ def render(self, name, value, attrs=None, renderer=None, **kwargs): # Render textare textarea = super().render(fieldname, val, attrs, renderer, **kwargs) # Label for plural - label = str(unit.translation.language) + label = escape(unit.translation.language) if len(values) != 1: label = f"{label}, {plural.get_plural_label(idx)}" ret.append(
9e19a8414337js: Add missing escaping to username completion
1 file changed · +3 −1
weblate/static/loader-bootstrap.js+3 −1 modified@@ -1131,7 +1131,9 @@ $(function () { return ""; }, menuItemTemplate: function (item) { - return `<a>${item.string}</a>`; + let link = document.createElement("a"); + link.innerText = item.string; + return link.outerHTML; }, values: (text, callback) => { $.ajax({
22d577b1f1e8reports: Escape user names in generated reports
2 files changed · +29 −11
weblate/trans/tests/test_reports.py+21 −6 modified@@ -31,7 +31,7 @@ "count": 1, "count_edit": 0, "count_new": 1, - "name": "Weblate Test", + "name": "Weblate <b>Test</b>", "words": 2, "words_edit": 0, "words_new": 2, @@ -62,7 +62,9 @@ class BaseReportsTest(ViewTestCase): def setUp(self): super().setUp() self.user.is_superuser = True + self.user.full_name = "Weblate <b>Test</b>" self.user.save() + self.maxDiff = None def add_change(self): self.edit_unit("Hello, world!\n", "Nazdar svete!\n") @@ -87,7 +89,14 @@ def test_credits_one(self, expected_count=1): translation__component=self.component, ) self.assertEqual( - data, [{"Czech": [("weblate@example.org", "Weblate Test", expected_count)]}] + data, + [ + { + "Czech": [ + ("weblate@example.org", "Weblate <b>Test</b>", expected_count) + ] + } + ], ) def test_credits_more(self): @@ -126,15 +135,21 @@ def test_credits_view_json(self): self.assertEqual(response.status_code, 200) self.assertJSONEqual( response.content.decode(), - [{"Czech": [["weblate@example.org", "Weblate Test", 1]]}], + [{"Czech": [["weblate@example.org", "Weblate <b>Test</b>", 1]]}], ) def test_credits_view_rst(self): response = self.get_credits("rst") self.assertEqual(response.status_code, 200) self.assertEqual( response.content.decode(), - "\n\n* Czech\n\n * Weblate Test <weblate@example.org> (1)\n\n", + """ + +* Czech + + * Weblate <b>Test</b> <weblate@example.org> (1) + +""", ) def test_credits_view_html(self): @@ -145,7 +160,7 @@ def test_credits_view_html(self): "<table>\n" "<tr>\n<th>Czech</th>\n" '<td><ul><li><a href="mailto:weblate@example.org">' - "Weblate Test</a> (1)</li></ul></td>\n</tr>\n" + "Weblate <b>Test</b></a> (1)</li></ul></td>\n</tr>\n" "</table>", ) @@ -231,7 +246,7 @@ def test_counts_view_html(self): <th>Target chars edited</th> </tr> <tr> - <td>Weblate Test</td> + <td>Weblate <b>Test</b></td> <td>weblate@example.org</td> <td>1</td> <td>14</td>
weblate/trans/views/reports.py+8 −5 modified@@ -17,9 +17,9 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. # - from django.contrib.auth.decorators import login_required from django.http import HttpResponse, JsonResponse +from django.utils.html import escape from django.views.decorators.http import require_POST from weblate.lang.models import Language @@ -109,10 +109,13 @@ def get_credits(request, project=None, component=None): for language in data: name, translators = language.popitem() result.append(row_start) - result.append(language_format.format(name)) + result.append(language_format.format(escape(name))) result.append( translator_start - + "\n".join(translator_format.format(*t) for t in translators) + + "\n".join( + translator_format.format(escape(t[0]), escape(t[1]), t[2]) + for t in translators + ) + translator_end ) result.append(row_end) @@ -288,8 +291,8 @@ def get_counts(request, project=None, component=None): result.append( "".join( ( - cell_name.format(item["name"] or "Anonymous"), - cell_name.format(item["email"] or ""), + cell_name.format(escape(item["name"]) or "Anonymous"), + cell_name.format(escape(item["email"]) or ""), cell_count.format(item["count"]), cell_count.format(item["edits"]), cell_count.format(item["words"]),
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-6jp6-9rf9-gc66ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-24710ghsaADVISORY
- github.com/WeblateOrg/weblate/commit/22d577b1f1e88665a88b4569380148030e0f8389ghsax_refsource_MISCWEB
- github.com/WeblateOrg/weblate/commit/9e19a8414337692cc90da2a91c9af5420f2952f1ghsax_refsource_MISCWEB
- github.com/WeblateOrg/weblate/commit/f6753a1a1c63fade6ad418fbda827c6750ab0bdaghsax_refsource_MISCWEB
- github.com/WeblateOrg/weblate/security/advisories/GHSA-6jp6-9rf9-gc66ghsax_refsource_CONFIRMWEB
- github.com/pypa/advisory-database/tree/main/vulns/weblate/PYSEC-2022-35.yamlghsaWEB
News mentions
0No linked articles in our index yet.