VYPR
Medium severity5.1NVD Advisory· Published May 8, 2026· Updated May 12, 2026

CVE-2026-42150

CVE-2026-42150

Description

wlc is a Weblate command-line client using Weblate's REST API. Prior to version 2.0.0, the HTML output format in wlc embeds API response data into HTML without escaping, allowing cross-site scripting when the output is rendered in a browser. This issue has been patched in version 2.0.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
wlcPyPI
< 2.0.02.0.0

Affected products

1
  • cpe:2.3:a:weblate:wlc:*:*:*:*:*:*:*:*
    Range: <2.0.0

Patches

1
0f3e58f6d745

fix(output): properly escape generated HTML (#1327)

https://github.com/WeblateOrg/wlcMichal ČihařApr 20, 2026via ghsa
2 files changed · +43 5
  • wlc/main.py+11 4 modified
    @@ -9,6 +9,7 @@
     from __future__ import annotations
     
     import csv
    +import html
     import json
     import sys
     from argparse import ArgumentParser
    @@ -224,6 +225,11 @@ def format_output_value(self, value):
             """Format a value for human-readable output."""
             return format_for_stream(self.format_value(value), self.stdout)
     
    +    @classmethod
    +    def format_html_value(cls, value) -> str:
    +        """Format value for safe HTML rendering."""
    +        return html.escape(str(cls.format_value(value)), quote=True)
    +
         def print_csv(self, value, header) -> None:
             """CSV print."""
             writer = csv.writer(self.stdout)
    @@ -247,7 +253,7 @@ def print_html(self, value, header) -> None:
                 self.println("  <thead>")
                 self.println("    <tr>")
                 for key in header:
    -                self.println(f"      <th>{self.format_output_value(key)}</th>")
    +                self.println(f"      <th>{self.format_html_value(key)}</th>")
                 self.println("    </tr>")
                 self.println("  </thead>")
                 self.println("  <tbody>")
    @@ -256,7 +262,7 @@ def print_html(self, value, header) -> None:
                     self.println("    <tr>")
                     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>"
                         )
                     self.println("    </tr>")
                 self.println("  </tbody>")
    @@ -266,8 +272,9 @@ def print_html(self, value, header) -> None:
                 for key, data in sorted_items(value):
                     self.println("  <tr>")
                     self.println(
    -                    f"    <th>{self.format_output_value(key)}</th>"
    -                    f"<td>{self.format_output_value(data)}</td>"
    +                    "    "
    +                    f"<th>{self.format_html_value(key)}</th>"
    +                    f"<td>{self.format_html_value(data)}</td>"
                     )
                     self.println("  </tr>")
                 self.println("</table>")
    
  • wlc/test_main.py+32 1 modified
    @@ -7,6 +7,7 @@
     from __future__ import annotations
     
     import csv
    +import html
     import json
     import os
     import sys
    @@ -55,7 +56,10 @@ class AttributeDict(dict):
     
         def __getattr__(self, key):
             """Provide attribute-style access."""
    -        return self[key]
    +        try:
    +            return self[key]
    +        except KeyError as exc:
    +            raise AttributeError(key) from exc
     
     
     class CLITestBase(APITest, ABC):
    @@ -403,6 +407,33 @@ def test_html_output_escapes_terminal_control_characters(self) -> None:
             self.assertIn(r"hello\x1b[31m\r\nworld", rendered)
             self.assertNotIn("\x1b", rendered)
     
    +    def test_html_escapes_list_output(self) -> None:
    +        """HTML list output escapes headers and values."""
    +        payload_key = '<script>alert("key")</script>'
    +        payload_value = '<img src=x onerror=alert("value")>'
    +        output = StringIO()
    +        cmd = self.create_command(output, "html")
    +
    +        cmd.print([AttributeDict({payload_key: payload_value})])
    +
    +        rendered = output.getvalue()
    +        self.assertIn(html.escape(payload_key), rendered)
    +        self.assertIn(html.escape(payload_value), rendered)
    +        self.assertNotIn(payload_key, rendered)
    +        self.assertNotIn(payload_value, rendered)
    +
    +    def test_html_escapes_detail_output(self) -> None:
    +        """HTML detail output escapes keys and values."""
    +        payload_value = '<svg onload=alert("value")>'
    +        output = StringIO()
    +        cmd = self.create_command(output, "html")
    +
    +        cmd.print({"name": payload_value})
    +
    +        rendered = output.getvalue()
    +        self.assertIn(html.escape(payload_value), rendered)
    +        self.assertNotIn(payload_value, rendered)
    +
         def test_json_encoder(self) -> None:
             """Test JSON encoder."""
             output = StringIO()
    

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

6

News mentions

0

No linked articles in our index yet.