VYPR
High severityOSV Advisory· Published Jan 16, 2026· Updated Jan 16, 2026

wlc Path traversal: Unsanitized API slugs in download command

CVE-2026-23535

Description

wlc is a Weblate command-line client using Weblate's REST API. Prior to 1.17.2, the multi-translation download could write to an arbitrary location when instructed by a crafted server. This vulnerability is fixed in 1.17.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
wlcPyPI
< 1.17.21.17.2

Affected products

1

Patches

1
216e691c6e50

fix: sanitize server-provided paths

https://github.com/WeblateOrg/wlcMichal ČihařJan 15, 2026via ghsa
3 files changed · +45 7
  • wlc/main.py+7 7 modified
    @@ -21,6 +21,8 @@
     import wlc
     from wlc.config import NoOptionError, WeblateConfig, WLCConfigurationError
     
    +from .utils import sanitize_slug
    +
     COMMANDS: dict[str, type[Command]] = {}
     
     SORT_ORDER: list[str] = []
    @@ -655,13 +657,11 @@ def download_component(self, component) -> None:
                 raise CommandError("Output is needed for download!")
     
             directory = Path(self.args.output)
    -        file_path = directory.joinpath(f"{component.project.slug}-{component.slug}.zip")
    -
    -        if not directory.exists():
    -            directory.mkdir(exist_ok=True, parents=True)
    -
    -        with open(file_path, "wb") as file:
    -            file.write(content)
    +        file_path = directory / (
    +            f"{sanitize_slug(component.project.slug)}-{sanitize_slug(component.slug)}.zip"
    +        )
    +        directory.mkdir(exist_ok=True, parents=True)
    +        file_path.write_bytes(content)
     
         def download_components(self, iterable) -> None:
             for component in iterable:
    
  • wlc/test_utils.py+21 0 added
    @@ -0,0 +1,21 @@
    +# Copyright © Michal Čihař <michal@weblate.org>
    +#
    +# SPDX-License-Identifier: GPL-3.0-or-later
    +
    +"""Utils tests."""
    +
    +from unittest import TestCase
    +
    +from .utils import sanitize_slug
    +
    +
    +class UtilsTestCase(TestCase):
    +    """Utils tests."""
    +
    +    def test_sanitize_slug(self):
    +        self.assertEqual(sanitize_slug("slug"), "slug")
    +
    +    def test_sanitize_slug_dangerous(self):
    +        self.assertEqual(sanitize_slug("../\\slug"), "----slug")
    +        self.assertEqual(sanitize_slug("slug/other"), "slug-other")
    +        self.assertEqual(sanitize_slug("slug/"), "slug-")
    
  • wlc/utils.py+17 0 added
    @@ -0,0 +1,17 @@
    +# Copyright © Michal Čihař <michal@weblate.org>
    +#
    +# SPDX-License-Identifier: GPL-3.0-or-later
    +"""Utility helpers."""
    +
    +from __future__ import annotations
    +
    +import re
    +
    +# This matches Django's SlugField validation minus dash which is
    +# excluded by Weblate's validate_slug
    +NON_SLUG_RE = re.compile(r"[^a-zA-Z0-9_]")
    +
    +
    +def sanitize_slug(slug: str) -> str:
    +    """Sanitize slug for safe use as a filename component."""
    +    return NON_SLUG_RE.sub("-", slug)
    

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.