Apr 24, 2026·6 min read·4 visits
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.
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.
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 < and >, 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.
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>"
)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.
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.
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.
| Product | Affected Versions | Fixed Version |
|---|---|---|
wlc (Weblate CLI) WeblateOrg | All versions prior to the patch in PR #1327 | Latest |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Local/Network (via file viewing) |
| Vulnerability Type | Cross-Site Scripting (XSS) |
| Component | print_html function in wlc/main.py |
| Exploit Status | Proof of Concept available |
| CVSS Severity | Not explicitly rated |
Improper Neutralization of Input During Web Page Generation (Cross-site Scripting)