CVE-2026-22250

Trust Issues: The 127.0.0.1 Prefix Bypass in Weblate CLI

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 12, 2026·5 min read

Executive Summary (TL;DR)

The Weblate CLI disabled SSL verification for any URL starting with '127.0.0.1'. Attackers registered domains like '127.0.0.1.attacker.com' to trick the client into trusting their malicious servers, exposing API tokens and translation data.

A critical logic flaw in the Weblate command-line client (wlc) allowed attackers to bypass SSL/TLS verification by crafting URLs that mimic local loopback addresses. By exploiting a naive string prefix check and case-sensitive protocol validation, malicious actors could perform Man-in-the-Middle (MitM) attacks against developers pushing translation updates.

The Hook: Developer Tooling is the Soft Underbelly

We often talk about hardening servers and sanitizing user input on the frontend, but we rarely look at the tools sitting in the developer's own terminal. The Weblate Command-Line Client (wlc) is one such tool—a utility used by translators and developers to push localization strings to a Weblate server. It holds the keys to the kingdom: API tokens that have write access to your application's language files.

In a perfect world, these tools would strictly enforce SSL/TLS to protect those tokens as they travel across the wire. But developers also love convenience. They run local instances for testing. They hate self-signed certificate warnings. So, wlc included a little helper function to automatically disable SSL verification if it thought you were talking to localhost. It was a kindness that unfortunately opened the door to a massive Man-in-the-Middle vulnerability.

The Flaw: Naive String Theory

The vulnerability lies in a Python method named _should_verify_ssl. Its job was simple: look at the URL and decide if requests should verify the certificate. The logic was intended to skip verification for local loopback addresses to avoid nagging developers about certificates on 127.0.0.1.

Here is where it went wrong: it used startswith. The code checked if the netloc (network location) of the URL started with the string "127.0.0.1". To a human, 127.0.0.1 means "home". To a computer executing a startswith check, 127.0.0.1.evil.com is just as valid a match as 127.0.0.1.

Furthermore, the code performed a case-sensitive check on the URL scheme. It strictly looked for "https". If you used "HTTPS://" (uppercase), the check failed. The function would return False (indicating "do not verify"), and the client would proceed to establish an insecure connection over an encrypted protocol, happily accepting whatever garbage certificate the server offered.

The Code: The Smoking Gun

Let's look at the logic that caused the headache. This is the vulnerable code found in wlc/__init__.py:

LOCALHOST_NETLOC = "127.0.0.1"
 
@staticmethod
def _should_verify_ssl(path):
    """Checks if it should verify ssl certificates."""
    url = urlparse(path)
    # FLAW 1: Prefix match allows 127.0.0.1.attacker.com
    is_localhost = url.netloc.startswith(LOCALHOST_NETLOC)
    
    # FLAW 2: Case-sensitive check fails on "HTTPS://"
    return url.scheme == "https" and (not is_localhost)

If is_localhost evaluates to True, the function returns False. If url.scheme is "HTTPS" (not "https"), the function returns False. In both cases, SSL verification is turned off.

The fix, implemented in January 2026, replaces this naive logic with a strict allowlist:

LOCALHOST_ADDRESSES = {"127.0.0.1", "localhost", "::1", "[::1]"}
 
@staticmethod
def should_verify_ssl(path: str) -> bool:
    url = urlparse(path)
    # FIX: Exact match against a set of known loopback addresses
    return url.hostname not in LOCALHOST_ADDRESSES

Note the switch from url.netloc to url.hostname. netloc can include port numbers (e.g., 127.0.0.1:8000), which complicates string matching. hostname isolates the domain, making exact matching robust.

The Exploit: Phishing the Developer

Exploiting this doesn't require a supercomputer; it requires $10 to buy a domain and a bit of social engineering. Here is the attack chain:

  1. Setup: The attacker registers 127.0.0.1.attacker.com and points the DNS A record to a server they control.
  2. The Trap: The attacker convinces a developer (or compromises a CI/CD config) to point their Weblate client to https://127.0.0.1.attacker.com/api/.
  3. The Connection: The developer runs wlc push. The tool parses the URL. It sees the netloc starts with 127.0.0.1. It decides, "Ah, a local test! No need for SSL verification."
  4. The Intercept: The tool connects to the attacker's server. The attacker presents a self-signed certificate. Because verify=False was passed to the underlying request library, wlc accepts it without complaint.
  5. The Loot: The attacker captures the Authorization: Token ... header sent by the client. They can now impersonate the developer against the real Weblate instance.

The Impact: Lost in Translation

The impact here is classic Man-in-the-Middle (MitM), but context is king. We aren't just stealing a session cookie for a cat video site. We are stealing API tokens for a localization platform.

With these credentials, an attacker could inject malicious translations into a project. Imagine changing the "Confirm Transfer" button text in a banking app to "Cancel" in a specific language, or injecting Cross-Site Scripting (XSS) payloads into string resources that get rendered unsanitized by the victim application. Since the wlc tool is often used in automated pipelines, this could compromise production builds silently.

The Fix: Strict Boundaries

The remediation is straightforward: stop guessing. Security checks should never rely on "fuzzy" matching unless absolutely necessary. The patch introduces a LOCALHOST_ADDRESSES set containing 127.0.0.1, localhost, ::1, and [::1].

By checking if url.hostname not in LOCALHOST_ADDRESSES, the developers ensure that only these exact strings trigger the insecurity exception. 127.0.0.1.attacker.com is not in that set, so SSL verification remains enabled. It's a boring fix, but boring is good in security.

Fix Analysis (1)

Technical Appendix

CVSS Score
7.5/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N

Affected Systems

Weblate Command-Line Client (wlc)CI/CD pipelines using wlcDeveloper workstations with wlc installed

Affected Versions Detail

Product
Affected Versions
Fixed Version
wlc
WeblateOrg
< 2026-01-07 (Commit a513864)Jan 2026 Release
AttributeDetail
CVE IDCVE-2026-22250
CVSS v3.17.5 (High)
CWECWE-295 (Improper Certificate Validation)
VectorAV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N
Attack VectorMan-in-the-Middle (MitM)
Affected Componentwlc/__init__.py (_should_verify_ssl)
CWE-295
Improper Certificate Validation

The software does not validate, or incorrectly validates, a certificate. This allows an attacker to spoof a trusted entity by presenting a certificate that the application accepts.

Vulnerability Timeline

Vulnerability identified and patch authored by Michal Čihař
2026-01-07
Pull Request #1097 opened
2026-01-07
CVE-2026-22250 Assigned
2026-01-10

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.