Apr 24, 2026·6 min read·19 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)
A critical logical security flaw exists in Auth.js (formerly NextAuth.js) where signed anti-CSRF check cookies (state, nonce, and PKCE code_verifier) are not bound to the specific Identity Provider that initiated the authorization flow. In multi-provider environments, this allows an attacker to replay valid, cryptographically signed cookies minted during a flow with one provider against a callback handling a different provider. This vulnerability can lead to session hijacking, identity theft, or unauthorized account linking.
A security vulnerability in the email normalization logic of NextAuth.js and Auth.js allows remote attackers to bypass email validation constraints and achieve Account Takeover (ATO) through Unicode homoglyph smuggling. Under standard conditions, Unicode compatibility characters represent visually similar symbols that are normalized downstream to ASCII equivalents, facilitating structural validation bypasses. This issue specifically affects passwordless email authentication flows.
Auth.js (formerly NextAuth.js) contains a denial of service vulnerability due to an uncaught URIError in the getToken() token parser when processing malformed percent-encoded sequences in bearer tokens. Additionally, the library was vulnerable to session state confusion and replay attacks because OAuth check cookies (state, nonce, and PKCE) were not properly bound to specific providers, permitting cross-provider token reuse.
CVE-2026-53467 is a heap information disclosure vulnerability in the Multiple-image Network Graphics (MNG) decoder of ImageMagick. The vulnerability arises from a failure to zero-initialize newly allocated pixel cache memory buffers. A remote attacker can exploit this by submitting a crafted sparse MNG image file to trigger uninitialized memory preservation. The resulting output contains residual heap bytes, potentially leaking sensitive process memory or assisting in ASLR bypass.
An untrusted deserialization vulnerability exists in the c3p0 JDBC connection pooling library before version 0.14.0. Standard JDBC getter methods conform to the JavaBean property getter pattern, allowing introspection libraries like Apache Commons BeanUtils to evaluate connection properties dynamically during deserialization, leading to arbitrary code execution when chained with a vulnerable database driver or JNDI sink.
A heap-based buffer overflow vulnerability exists in the native C extension of the Ruby json gem (versions 2.9.0 through 2.19.8) during IO-based streaming serialization. An incorrect buffer size calculation can lead to memory corruption and process termination when processing large strings.