CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-GX2M-MCC2-R4P3
N/A

GHSA-GX2M-MCC2-R4P3: Cross-Site Scripting via Unescaped HTML Output in Weblate CLI

Alon Barad
Alon Barad
Software Engineer

Apr 24, 2026·6 min read·4 visits

PoC Available

Executive Summary (TL;DR)

A missing HTML entity encoding step in the wlc CLI tool permits attackers to execute arbitrary JavaScript if a victim generates and views an HTML-formatted translation report containing injected payloads.

The Weblate Command Line Interface (wlc) package contains a Cross-Site Scripting (XSS) vulnerability due to insufficient sanitization during HTML report generation. The `print_html` function fails to encode API-retrieved data before embedding it into HTML output, allowing malicious payloads to execute when the generated report is viewed in a web browser.

Vulnerability Overview

The wlc package serves as the primary Python-based Command Line Interface for Weblate, enabling developers and administrators to interact with Weblate APIs for localization management. A vulnerability exists within the application's reporting capabilities, specifically when outputting retrieved data in HTML format. This flaw is classified under CWE-79 (Improper Neutralization of Input During Web Page Generation).

When a user executes commands such as wlc list and specifies the --format html argument, the tool retrieves metadata and translations from the remote Weblate instance. It then parses this JSON or API structure and dynamically constructs an HTML table to present the data locally. The generated HTML is typically redirected to a file for subsequent review.

The vulnerability arises because the CLI application trusts the data returned by the Weblate API. It directly interpolates strings into HTML markup without applying any form of entity encoding or sanitization. Consequently, an attacker who controls the data within the Weblate instance can compromise the local environment of the user viewing the generated report.

Root Cause Analysis

The core issue resides in the print_html function located within wlc/main.py. This function is responsible for iterating over translation records and formatting them into structured output. It is invoked whenever the user explicitly requests HTML output from the command line.

Within this function, the application relies on the self.format_output_value(data) method to prepare values for display. While this method effectively converts Python objects into string representations suitable for standard console streams, it performs no HTML-specific sanitization. It treats all data as safe plaintext.

The resulting strings are placed directly between <th> and <td> HTML elements using Python f-strings. Because characters like < and > are not translated into &lt; and &gt;, a web browser parsing the resulting document will interpret injected HTML tags as structural document elements or executable scripts rather than text content.

This flaw acts as a hybrid of Stored and Reflected XSS. The malicious payload is persistently stored in the centralized Weblate database, but the execution context shifts to the client's local machine or internal network when the CLI dynamically reflects the data into an offline HTML document.

Code Analysis

The vulnerable implementation utilized direct f-string interpolation for both table headers and row items. The print_html function looped through the keys and items, passing them only through format_output_value(), which lacked context-aware escaping.

The patch implemented in commit 0f3e58f6d7457b05d48ef40f579a172c4c8b8469 resolves this by introducing a dedicated formatting wrapper specifically for HTML output. The fix imports Python's standard html library and adds a new class method to handle the encoding.

# wlc/main.py (Patched)
 
import html
 
# ...
 
    @classmethod
    def format_html_value(cls, value) -> str:
        """Format value for safe HTML rendering."""
        return html.escape(str(cls.format_value(value)), quote=True)

The format_html_value method leverages html.escape with the quote=True parameter. This ensures that ampersands, angle brackets, and both single and double quotation marks are reliably converted into safe HTML entities. The print_html method was subsequently updated to invoke this new method instead of the generic formatter.

# Iterating over the header and items
         for key in header:
-            self.println(f"      <th>{self.format_output_value(key)}</th>")
+            self.println(f"      <th>{self.format_html_value(key)}</th>")
         ...
         for item in value:
             for key in header:
                 self.println(
-                    f"      <td>{self.format_output_value(getattr(item, key))}</td>"
+                    f"      <td>{self.format_html_value(getattr(item, key))}</td>"
                 )

Exploitation and Attack Methodology

Exploiting this vulnerability requires the attacker to possess write access to a Weblate instance queried by the victim. The attacker injects standard cross-site scripting payloads, such as <script>alert(document.cookie)</script> or <img src=x onerror=alert('XSS')>, into controllable fields like translation strings, component names, or project descriptions.

The execution chain initiates when an administrator or developer utilizes the wlc command line tool to query the compromised data. If the user executes a command like wlc list --format html > output.html, the CLI retrieves the payload via the API and writes it unaltered into the local output.html file on the filesystem.

The final phase of exploitation occurs when the victim opens the generated HTML file in a web browser. The browser interprets the unsanitized tags, executing the attacker's JavaScript payload. The automated tests introduced alongside the patch verify that payloads like <svg onload=alert("value")> successfully bypass the vulnerable logic but are correctly neutralized by the fix.

Impact Assessment

The primary impact of this vulnerability is arbitrary JavaScript execution within the context of the user viewing the HTML report. The severity depends entirely on where and how the victim views the generated file.

If the report is viewed locally via the file:// protocol, the attacker gains execution within the local file context. Depending on specific browser sandbox constraints, this can permit the reading of adjacent local files or allow the attacker to make authenticated requests on behalf of the user to internal network services.

If the generated HTML report is hosted on an internal web server or shared portal for team access, the payload executes in the context of that hosting domain. This compromises the session of any user viewing the report, potentially allowing session hijacking, token theft, or unauthorized actions within that web application.

The vulnerability requires direct user interaction to trigger, specifically the manual generation of an HTML report and the subsequent opening of that report in a browser environment. This requisite interaction limits the overall scale of an attack but remains a highly viable vector against development teams and localization managers.

Remediation and Mitigation

The most effective remediation strategy is to upgrade the wlc package to a version containing the PR #1327 patch. Users should execute pip install --upgrade wlc in their relevant Python environments to ensure the html.escape logic is present.

For environments where immediate patching is not feasible, operators must avoid using the --format html argument when executing wlc commands. Generating reports in JSON (--format json), text, or CSV formats neutralizes the XSS vector entirely, as these formats are not subject to HTML rendering in a browser context.

If HTML reports must be generated using an outdated version of the tool, the raw output must be passed through a dedicated HTML sanitization utility before being opened in a web browser. This ensures any malicious script tags injected into the Weblate upstream data are stripped or neutralized.

Developers constructing similar command-line utilities should systematically utilize robust templating engines with automatic context-aware escaping (such as Jinja2) when generating HTML output. Manual string concatenation and f-string interpolation are highly prone to output encoding failures and should be strictly avoided for markup generation.

Official Patches

WeblateOrgOfficial patch commit resolving the XSS vulnerability
WeblateOrgPull Request #1327 containing the fix and test cases

Fix Analysis (1)

Technical Appendix

CVSS Score
N/A/ 10

Affected Systems

Weblate Command Line Interface (wlc)

Affected Versions Detail

Product
Affected Versions
Fixed Version
wlc (Weblate CLI)
WeblateOrg
All versions prior to the patch in PR #1327Latest
AttributeDetail
CWE IDCWE-79
Attack VectorLocal/Network (via file viewing)
Vulnerability TypeCross-Site Scripting (XSS)
Componentprint_html function in wlc/main.py
Exploit StatusProof of Concept available
CVSS SeverityNot explicitly rated

MITRE ATT&CK Mapping

T1189Drive-by Compromise
Initial Access
T1059.007Command and Scripting Interpreter: JavaScript
Execution
CWE-79
Cross-site Scripting

Improper Neutralization of Input During Web Page Generation (Cross-site Scripting)

Known Exploits & Detection

Advisory Context (Test Case PoC)Unit tests demonstrate execution utilizing `<svg onload=alert("value")>` and `<img src=x onerror=alert("value")>` payloads.

Vulnerability Timeline

Vulnerability discovered and patched via PR #1327 (Date inferred from available advisory data)
2024-01-01

References & Sources

  • [1]GitHub Security Advisory GHSA-GX2M-MCC2-R4P3
  • [2]WeblateOrg wlc PR #1327
  • [3]Fix Commit 0f3e58f6

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.