A loaded gun in a room almost nobody enters
CVE-2026-86035 is an argument-injection flaw in Weblate's Mercurial VCS backend. Versions 4.11.1 through 2026.7.1 pass repository-controlled filenames to hg cat without the -- option terminator, so a filename beginning with - is parsed as a Mercurial flag rather than a path. An authenticated user with project-scoped component.edit permission can exploit this to inject Mercurial configuration values via --config, replacing the expected hg cat call with an attacker-controlled shell command. The payload fires when Weblate next processes that component through the Update RESX files add-on — delivering full RCE as the Weblate service account. The bug is an incomplete remediation of CVE-2022-23915, which attempted the same -- fix four years ago but missed the get_file() code path.
The vendor rates this HIGH / 8.5 with AC:H and S:C, which is fair *on paper* — changed scope plus full CIA impact is a nasty CVSS profile. But CVSS does not capture the compounding deployment prerequisites: the attacker must hit a Weblate instance that (a) uses the Mercurial backend in an era where Git dominates >99% of VCS usage, (b) has the .NET RESX file format configured, and (c) has the Update RESX files add-on enabled on that Mercurial component. Each prerequisite slices the reachable population further. In practice, the set of Weblate instances simultaneously meeting all three conditions is vanishingly small. The vendor's 8.5 is an honest CVSS calculation, but it overstates real-world risk for defenders managing typical Weblate deployments.
5 steps from start to impact.
Obtain authenticated access with component.edit
component.edit permission. This is not the default role for translators — it is a privileged permission typically granted to project managers or integration accounts. The attacker either compromises such an account or registers one on an open-registration instance and is promoted.- Valid Weblate account
- Project-scoped
component.editpermission
- Default translator role does not carry
component.edit - Most production instances restrict registration or use SSO
- Promotion to component.edit requires admin action
component.edit permission grants; review user activity logs for unexpected component modifications.Identify or create a Mercurial-backed RESX component
component.edit permission. Creating a new component pointing to an attacker-controlled Mercurial repository makes this step self-service — if Mercurial is installed on the server at all.- Mercurial (
hg) binary installed on the Weblate server - Weblate configured to allow Mercurial VCS backend
- RESX file format in use or creatable
- Mercurial usage has collapsed industry-wide since Bitbucket dropped it in 2020 — <1% of new projects use it
- Most Weblate Docker deployments do not ship with
hginstalled - RESX is a .NET-specific format; most Weblate users localize web/mobile apps with PO, XLIFF, or JSON
hg is present on the Weblate host (which hg). Audit components for Mercurial VCS type.Enable the Update RESX files add-on
hg cat during the RESX update cycle, which is the vulnerable code path in HgRepository.get_file(). Without this add-on, the argument injection has no trigger.- Add-on installation permission (implied by
component.edit) - Update RESX files add-on available in the Weblate installation
- The add-on is niche — only relevant for .NET RESX workflows
- Enabling add-ons on existing components may trigger admin notifications or change-tracking alerts
Craft a malicious filename to inject hg arguments
-, such as --config=hooks.pre-cat=<shell command>. When Weblate calls hg cat <filename> without a -- separator, Mercurial interprets the filename as a --config flag and executes the injected hook. The payload runs as the Weblate service account (typically weblate or www-data).- Write access to the Mercurial repository backing the component
- Knowledge of Mercurial argument injection techniques (well-documented since CVE-2022-23915)
- Attacker must control or write to the upstream Mercurial repo — not just the Weblate UI
- Some Weblate deployments use read-only repo clones
-. EDR/auditd on the Weblate host for unexpected child processes of the Weblate worker.Trigger repository update to fire the payload
HgRepository.get_file() on the malicious filename, and the injected Mercurial config hook executes the shell command with the privileges of the Weblate service account. The attacker achieves RCE on the Weblate host.- Repository update cycle must run (automatic or manual trigger)
- If the Weblate worker is containerized with a read-only filesystem or restricted seccomp profile, post-exploitation is limited
- Network segmentation may limit lateral movement from the Weblate host
HgRepository.get_file() code path is never reached during repository processing. This is the single most effective workaround and can be applied in minutes via the Weblate admin UI. No mitigation SLA applies at MEDIUM — go straight to the 365-day remediation window, but this workaround is trivial enough to deploy immediately.hg) from the Weblate server — If the hg binary is not present, Weblate cannot use the Mercurial backend at all, eliminating the entire attack surface. Migrate any Mercurial-backed components to Git first. Most Weblate deployments already use Git exclusively.component.edit permission to trusted administrators only — Audit and tighten Weblate's role assignments. Remove component.edit from all non-admin users and service accounts. This prevents the attacker from creating or modifying components to set up the attack chain. Review via Weblate Admin → Groups → Roles.-- option terminator before filenames in all Mercurial command invocations, ensuring dash-prefixed filenames are treated as literal paths. Deploy within the 365-day noisgate remediation SLA for MEDIUM findings, though the fix is a straightforward version bump.- WAF rules — The injection occurs server-side during Mercurial command execution, not via HTTP request parameters. A WAF inspecting HTTP traffic will never see the malicious filename payload.
- Network segmentation / firewall — The attacker exploits the vulnerability through legitimate Weblate UI actions (editing components, pushing to repos). Blocking network ports does not prevent authenticated abuse of application functionality.
- Read-only container filesystem — While this limits *post-exploitation* actions, the initial command injection still fires. The attacker can still exfiltrate data or establish reverse shells via network I/O even without filesystem writes.
The supporting signals.
| In-the-Wild Exploitation | No evidence. Not listed on CISA KEV. No known campaigns or threat-actor usage as of 2026-10-01. Disclosed only 2 days ago. |
|---|---|
| Proof-of-Concept | No public PoC. Checked pocindex.io, GitHub, ExploitDB — no dedicated repos or exploit code for CVE-2026-86035. The technique is well-understood from CVE-2022-23915 and generic Mercurial argument injection research, lowering the bar for a skilled attacker to write one. Reporter: Shawky0-0 via HackerOne. |
| EPSS Score | Not yet scored. FIRST EPSS API returns empty data — expected for a CVE disclosed <72 hours ago. Expect a score in the next model update cycle. |
| KEV Status | Not listed. No CISA KEV entry as of 2026-10-01. |
| CVSS Vector | CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H — Network-accessible, high complexity, low privilege, no user interaction, changed scope. The S:C (changed scope) reflects that RCE on the Weblate host can affect systems beyond Weblate itself (e.g., connected repositories). |
| Affected Versions | Weblate ≥ 4.11.1 and < 2026.8 (approximately 4.5 years of releases). Docker images, pip installs, and distro packages all affected. |
| Fixed Version | Weblate 2026.8 (commit f60a975, PR #20768). NixOS backport: nixpkgs#568728. No other distro backports observed yet. |
| Internet Exposure | Weblate is used by ~2,500+ projects across 165 countries per vendor claims. No Shodan/Censys dork returns significant exposure data specific to Weblate. Most instances sit behind authentication and are not directly indexed. The subset using Mercurial+RESX is a fraction of a fraction. |
| Disclosure Timeline | Disclosed 2026-09-29 via GitHub Security Advisory GHSA-327h-qqgm-qv55. Fix released same day in Weblate 2026.8. |
| Researcher / Reporter | Shawky0-0 (GitHub) via HackerOne. Fix by nijel (Weblate maintainer). |
Sources.
Why this verdict
- Mercurial backend is near-extinct friction: Mercurial adoption has collapsed since Bitbucket dropped support in 2020. Well under 1% of new software projects use Mercurial, and Weblate's own documentation and community overwhelmingly reference Git workflows. The fraction of Weblate instances with a functioning Mercurial backend is likely in the low single-digit percentages, dramatically narrowing the vulnerable population.
- RESX add-on compounds the narrowing: Even among the rare Mercurial-backed instances, the attack requires the .NET RESX file format AND the Update RESX files add-on to be enabled. RESX is specific to .NET resource bundles — irrelevant to the web, mobile, and Python/JS projects that dominate Weblate's user base. This second prerequisite reduces the reachable set to a near-negligible fraction.
- Authentication + elevated permission required: The attacker needs
component.edit— a project-management-tier permission, not the default translator role. This rules out opportunistic unauthenticated exploitation entirely and restricts the threat to insiders or compromised privileged accounts. - AC:H already embedded in vendor score: The vendor's own CVSS vector sets Attack Complexity to High, acknowledging that exploitation requires specific conditions. The deployment-layer prerequisites (Mercurial + RESX + add-on) add further real-world complexity that CVSS cannot represent.
- Role multiplier: Weblate is a localization platform — it does NOT canonically occupy a high-value role (not an IdP, hypervisor, CI/CD engine, backup system, PAM, CA, or network edge appliance). When the chain succeeds, blast radius is host-level: the attacker gets a shell as the Weblate service account. Lateral pivot to connected repos is *possible* but requires the service account to hold write credentials to production repositories with no branch protections — an additional failure mode outside this CVE. Weblate does not meet the ≥1% high-value-role threshold that would floor the verdict at HIGH.
Why not higher?
The chain *does* end in RCE, and Weblate service accounts often hold repository credentials (SSH keys, tokens) that could enable lateral movement to source code repositories. If Weblate is deployed as part of a CI/CD pipeline with write access to production repos, the blast radius could extend to supply-chain impact. However, this extended scenario requires multiple additional failures beyond the CVE itself (write-capable creds, no branch protection, no code review), and Weblate is not canonically a CI/CD component. The RCE potential prevents dropping below MEDIUM.
Why not lower?
Despite the narrow prerequisite chain, the vulnerability is a real OS command injection that delivers full RCE when triggered. The technique is well-documented from CVE-2022-23915, meaning a motivated attacker with access to a qualifying instance could weaponize it quickly. The 4.5-year affected version window means many unpatched instances exist. Dropping to LOW would understate the impact for the (small) population of instances that do meet all prerequisites.
Crowdsourced verification payload.
Run on the Weblate host (or inside the Weblate container) as any user with read access to the Python environment. Example: bash check_cve_2026_86035.sh. No elevated privileges required.
#!/usr/bin/env bash
# check_cve_2026_86035.sh — Detect CVE-2026-86035 (Weblate Mercurial argument injection)
# Run on the Weblate host or inside its container.
# Exit codes: 0 = VULNERABLE, 1 = PATCHED, 2 = UNKNOWN
set -euo pipefail
VULN_FLOOR="4.11.1"
FIXED="2026.8"
# Attempt to get Weblate version
VERSION=""
if command -v weblate &>/dev/null; then
VERSION=$(weblate --version 2>/dev/null | grep -oP '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1)
elif python3 -c 'import weblate; print(weblate.VERSION_BASE)' 2>/dev/null; then
VERSION=$(python3 -c 'import weblate; print(weblate.VERSION_BASE)' 2>/dev/null)
elif pip3 show weblate 2>/dev/null | grep -q Version; then
VERSION=$(pip3 show weblate 2>/dev/null | grep '^Version:' | awk '{print $2}')
fi
if [ -z "$VERSION" ]; then
echo "UNKNOWN — Weblate not detected on this host."
exit 2
fi
echo "Detected Weblate version: $VERSION"
# Check if Mercurial is installed (attack prerequisite)
HG_PRESENT="no"
if command -v hg &>/dev/null; then
HG_PRESENT="yes"
echo "WARNING: Mercurial (hg) is installed — attack prerequisite met."
else
echo "INFO: Mercurial (hg) not installed — attack prerequisite NOT met."
fi
# Version comparison using sort -V
version_gte() {
[ "$(printf '%s\n' "$1" "$2" | sort -V | head -1)" = "$2" ]
}
version_lt() {
[ "$(printf '%s\n' "$1" "$2" | sort -V | head -1)" = "$1" ] && [ "$1" != "$2" ]
}
if version_lt "$VERSION" "$VULN_FLOOR"; then
echo "PATCHED — Version $VERSION predates the vulnerable range (4.11.1+)."
exit 1
elif version_gte "$VERSION" "$FIXED"; then
echo "PATCHED — Version $VERSION includes the fix (>= $FIXED)."
exit 1
else
echo "VULNERABLE — Version $VERSION is in the affected range ($VULN_FLOOR to <$FIXED)."
if [ "$HG_PRESENT" = "no" ]; then
echo "NOTE: Mercurial is not installed, so exploitation is not currently possible."
fi
exit 0
fi