What's the vulnerability?

Multi-translation download could write to an arbitrary location when instructed by a crafted server.

Root Cause Analysis

## Summary
A malicious Weblate server can abuse the `wlc download` command’s multi-component workflow to write files outside the caller’s chosen output directory. The client trusts component metadata returned by the server and uses the embedded project slug verbatim when constructing the zip filename, allowing absolute paths to be honored and causing arbitrary file writes on the filesystem running wlc < 1.17.2.

## Impact
- **Package:** Weblate `wlc` CLI
- **Affected versions:** All releases prior to 1.17.2
- **Risk:** High – a crafted server (or MITM) can force the client to overwrite arbitrary files (e.g., `/etc/cron.d/...`) during `wlc download --multi`, leading to privilege escalation or system compromise on any machine that runs the command.

## Root Cause
`wlc.main.Download.download_component()` builds the destination path as `Path(output_dir) / f"{component.project.slug}-{component.slug}.zip"`. The values `component.project.slug` and `component.slug` come directly from API responses without sanitization. When a malicious server returns an absolute path such as `/tmp/weblate-owned` for the project slug, `pathlib.Path` treats it as an absolute path and drops the intended base directory, writing the downloaded archive to that attacker-chosen location. The issue is fixed upstream in https://github.com/WeblateOrg/wlc/pull/1128 (released in 1.17.2) which normalizes and validates slugs before using them in filesystem paths.

## Reproduction Steps
1. Run `repro/reproduction_steps.sh`.
2. The script provisions a Python venv, installs `wlc==1.17.1`, starts a mock Weblate HTTP server that serves a component listing whose project slug is `/tmp/weblate-owned`, and then executes `wlc download --output repro-output` against it.
3. Successful reproduction is indicated by creation of `/tmp/weblate-owned-absolute-component.zip` (outside the requested output directory) and logs stored in `logs/`.

## Evidence
- Log artifacts: `logs/mock_server.log`, `logs/wlc_download.log`, `logs/evidence.txt`.
- Key excerpt (`logs/evidence.txt`):
  - `-rw-r--r-- 1 root root 58 ... /tmp/weblate-owned-absolute-component.zip`
  - Intended output directory `repro-output/` remains empty, proving the write escaped the sandbox.
- Environment: Python 3.11 virtualenv with `wlc==1.17.1` on Ubuntu (container) as provisioned by the script.

## Recommendations / Next Steps
- Upgrade `wlc` clients to 1.17.2 or later, which sanitizes slugs before constructing output paths.
- For additional defense-in-depth, consider enforcing a jail by resolving all download outputs relative to the configured directory and rejecting absolute inputs from the server.
- Add regression tests that simulate malicious slugs to ensure future changes preserve the validation.

## Additional Notes
- `repro/reproduction_steps.sh` is idempotent: running it twice in succession succeeds, recreating the evidence and overwriting the malicious file each time.
- The mock server only implements the minimal endpoints needed for this PoC; real-world instances may require authentication, but the vulnerability manifests regardless once the client processes attacker-controlled component metadata.
One Command

Verify with pruva-verify

Run the Pruva CLI to automatically fetch and execute the reproduction script.

pruva-verify REPRO-2026-00070
or pruva-verify GHSA-mmwx-79f6-67jg
or pruva-verify CVE-2026-23535
Install: curl -fsSL https://pruva.dev/install.sh | sh

Or Run Manually

1

Download the script

curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00070/artifacts/reproduction_steps.sh
2

Make executable

chmod +x reproduction_steps.sh
3

Run the script

./reproduction_steps.sh
Run in a VM, container, or disposable environment. This exploits a real vulnerability.

How Pruva Reproduced This

Watch the AI agent's step-by-step process.

Loading session...

Artifacts

No artifacts available