Moderate severityOSV Advisory· Published Jan 12, 2026· Updated Jan 12, 2026
wlc may leak API keys due to an insecure API key configuration
CVE-2026-22251
Description
wlc is a Weblate command-line client using Weblate's REST API. Prior to 1.17.0, wlc supported providing unscoped API keys in the setting. This practice was discouraged for years, but the code was never removed. This might cause the API key to be leaked to different servers.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
wlcPyPI | < 1.17.0 | 1.17.0 |
Affected products
1- Range: 0.1, 0.10, 0.2, …
Patches
1aafdb507a9e6fix(config): avoid loading key from system-wide settings
5 files changed · +35 −34
wlc/config.py+20 −23 modified@@ -8,32 +8,30 @@ import os.path from configparser import NoOptionError, RawConfigParser -from typing import TYPE_CHECKING +from typing import cast -from xdg.BaseDirectory import load_config_paths # type: ignore[import-untyped] +from xdg.BaseDirectory import load_first_config # type: ignore[import-untyped] import wlc -if TYPE_CHECKING: - from collections.abc import Generator - __all__ = ["NoOptionError", "WeblateConfig"] class WeblateConfig(RawConfigParser): """Configuration parser wrapper with defaults.""" - def __init__(self, section="weblate") -> None: + def __init__(self, section: str = "weblate") -> None: """Construct WeblateConfig object.""" super().__init__(delimiters=("=",)) - self.section = section + self.section: str = section + self.cli_key: str | None = None + self.cli_url: str | None = None self.set_defaults() def set_defaults(self) -> None: """Set default values.""" self.add_section("keys") self.add_section(self.section) - self.set(self.section, "key", "") self.set(self.section, "url", wlc.API_URL) self.set(self.section, "retries", "0") self.set(self.section, "timeout", "300") @@ -44,23 +42,27 @@ def set_defaults(self) -> None: self.set(self.section, "backoff_factor", "0") @staticmethod - def find_configs() -> Generator[str]: + def find_config() -> str | None: # Handle Windows specifically for envname in ("APPDATA", "LOCALAPPDATA"): if path := os.environ.get(envname): win_path = os.path.join(path, "weblate.ini") if os.path.exists(win_path): - yield win_path + return win_path # Generic XDG paths - yield from load_config_paths("weblate") - yield from load_config_paths("weblate.ini") + for filename in ("weblate", "weblate.ini"): + if config := load_first_config(filename): + return config + + return None - def load(self, path=None) -> None: + def load(self, path: str | None = None) -> None: """Load configuration from XDG paths.""" if path is None: - path = list(self.find_configs()) - self.read(path) + path = self.find_config() + if path: + self.read(path) # Try reading from current dir cwd = os.path.abspath(".") @@ -74,15 +76,10 @@ def load(self, path=None) -> None: prev = cwd cwd = os.path.dirname(cwd) - def get_url_key(self): + def get_url_key(self) -> tuple[str, str]: """Get API URL and key.""" - url = self.get(self.section, "url") - key = self.get(self.section, "key") - if not key: - try: - key = self.get("keys", url) - except NoOptionError: - key = "" + url = self.cli_url or cast("str", self.get(self.section, "url")) + key = self.cli_key or cast("str", self.get("keys", url, fallback="")) return url, key def get_request_options(self):
wlc/main.py+4 −4 modified@@ -794,10 +794,10 @@ def parse_settings(args, settings): for section, key, value in settings: config.set(section, key, value) - for override in ("key", "url"): - value = getattr(args, override) - if value is not None: - config.set(args.config_section, override, value) + if args.key: + config.cli_key = args.key + if args.url: + config.cli_url = args.url return config
wlc/test_base.py+4 −2 modified@@ -21,7 +21,7 @@ class ResponseHandler: """responses response handler.""" - def __init__(self, body, filename, auth=False) -> None: + def __init__(self, body: bytes, filename: str, auth: bool = False) -> None: """Construct response handler object.""" self.body = body self.filename = filename @@ -111,7 +111,9 @@ def format_multipart_body(body, content_type): return digest.hexdigest() -def register_uri(path, domain="http://127.0.0.1:8000/api", auth=False) -> None: +def register_uri( + path: str, domain: str = "http://127.0.0.1:8000/api", auth: bool = False +) -> None: """Simplified URL registration.""" filename = os.path.join(DATA_TEST_BASE, path.replace("/", "-")) url = f"{domain}/{path}/"
wlc/test_data/weblate.ini+3 −1 modified@@ -1,3 +1,5 @@ [weblate] url = http://127.0.0.1:8000/api/ -key = KEY + +[keys] +http://127.0.0.1:8000/api/ = KEY
wlc/test_main.py+4 −4 modified@@ -91,15 +91,16 @@ def test_config_section(self) -> None: self.assertIn("Hello", output) def test_config_key(self) -> None: - """Configuration using custom config file section and key set.""" + """Configuration using custom config file section and key set is ignored.""" output = self.execute( ["--config", TEST_CONFIG, "--config-section", "withkey", "show", "acl"], settings=False, + expected=1, ) - self.assertIn("ACL", output) + self.assertIn("Error: You don't have permission to access this object", output) def test_config_appdata(self) -> None: - """Configuration using custom config file section and key set.""" + """Verify keys are loaded from the [keys] section in APPDATA-based config.""" output = self.execute(["show", "acl"], settings=False, expected=1) self.assertIn("You don't have permission to access this object", output) try: @@ -122,7 +123,6 @@ def test_config_cwd(self) -> None: def test_default_config_values(self) -> None: """Test default parser values.""" config = WeblateConfig() - self.assertEqual(config.get("weblate", "key"), "") self.assertEqual(config.get("weblate", "retries"), "0") self.assertEqual(config.get("weblate", "timeout"), "300") self.assertEqual(
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
5- github.com/advisories/GHSA-9rp8-h4g8-8766ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-22251ghsaADVISORY
- github.com/WeblateOrg/wlc/commit/aafdb507a9e66574ade1f68c50c4fe75dbe80797ghsax_refsource_MISCWEB
- github.com/WeblateOrg/wlc/pull/1098ghsax_refsource_MISCWEB
- github.com/WeblateOrg/wlc/security/advisories/GHSA-9rp8-h4g8-8766ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.