Trust Issues: The 127.0.0.1 Prefix Bypass in Weblate CLI
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_ADDRESSESNote 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:
- Setup: The attacker registers
127.0.0.1.attacker.comand points the DNS A record to a server they control. - 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/. - The Connection: The developer runs
wlc push. The tool parses the URL. It sees thenetlocstarts with127.0.0.1. It decides, "Ah, a local test! No need for SSL verification." - The Intercept: The tool connects to the attacker's server. The attacker presents a self-signed certificate. Because
verify=Falsewas passed to the underlying request library,wlcaccepts it without complaint. - 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:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
wlc WeblateOrg | < 2026-01-07 (Commit a513864) | Jan 2026 Release |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2026-22250 |
| CVSS v3.1 | 7.5 (High) |
| CWE | CWE-295 (Improper Certificate Validation) |
| Vector | AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N |
| Attack Vector | Man-in-the-Middle (MitM) |
| Affected Component | wlc/__init__.py (_should_verify_ssl) |
MITRE ATT&CK Mapping
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.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.