CVEReports
CVEReports

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

Product

  • Home
  • 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-21889
7.50.04%

CVE-2026-21889: Weblate's Candid Camera – Leaking Screenshots via Static Misconfiguration

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 19, 2026·5 min read·3 visits

No Known Exploit

Executive Summary (TL;DR)

Weblate versions < 5.15.2 served screenshot uploads directly via the web server (Nginx/Apache), bypassing Django's permission checks. Unauthenticated users could view private project screenshots if they could guess the URL. Fixed by routing media requests through a protected Django view.

Weblate, the popular open-source localization tool, suffered from a classic architectural disconnect between application logic and web server configuration. By defaulting to serving the `/media/` directory directly via Nginx or Apache, Weblate inadvertently bypassed its own authentication middleware for uploaded screenshots. This allowed unauthenticated attackers to access sensitive visual context—potentially revealing internal dashboards, PII, or unreleased features—simply by knowing or guessing the file path.

The Hook: Lost in Translation (and Authorization)

Localization is hard. To make it easier, Weblate allows developers to upload screenshots of their application so translators can see exactly where a string like "Submit" appears—is it a button? A modal? A command to launch nuclear missiles?

This context is vital. However, in the world of software development, context is also sensitive. These screenshots often depict unreleased features, internal admin panels, or user interfaces populated with real (and hopefully test) data. You would assume that if a project is marked "Private" in Weblate, the screenshots attached to it are also private.

Well, prior to version 5.15.2, that assumption was dead wrong. It turns out that while Weblate locked the front door (the project dashboard), it left the side window (the media directory) wide open. This vulnerability isn't a complex buffer overflow; it's a fundamental misunderstanding of who is responsible for guarding the assets.

The Flaw: The Static File Trap

The root cause here is a classic "DevOps vs. Dev" conflict. In the Django world (which Weblate is built on), serving static files and user uploads through the Python application is considered slow and inefficient. The standard advice is to let a high-performance web server like Nginx or Apache handle the /media/ directory directly.

Weblate's official documentation and example configurations followed this advice to a fault. They provided Nginx configs that looked like this:

location /media/ {
    alias /home/weblate/data/media/;
    expires 30d;
}

Do you see the problem? When a request hits https://weblate.company.com/media/screenshots/secret_admin_panel.png, Nginx looks at the location block, sees it matches /media/, and happily serves the file from the disk. Nginx does not know what a "Django Session" is. It doesn't care about your Access Control Lists (ACLs) or whether User ID 5 has permission to view Project X. It just serves bytes.

By bypassing the application layer entirely, Weblate inadvertently created a massive IDOR (Insecure Direct Object Reference) scenario, or more accurately, a lack of access control on static assets. The application code checked permissions if you asked the application, but nobody was asking the application.

The Code: Patch Analysis

The fix required a two-pronged approach: changing the code to handle file serving securely, and screaming at administrators to update their web server configs. The code change was introduced in commit a6eb5fd0299780eca286be8ff187dc2d10feec47.

The developers introduced a new view specifically for serving these screenshots. Instead of a direct link to a file, the application now routes the request through a ScreenshotView class.

Here is the logic that was missing:

# Inside weblate/trans/views/files.py
 
class ScreenshotView(DetailView):
    # ... setup code ...
    
    def get_object(self, *args, **kwargs):
        obj = super().get_object(*args, **kwargs)
        # The Critical Fix:
        # Verify the user has access to the component this screenshot belongs to.
        self.request.user.check_access_component(obj.translation.component)
        return obj

Previously, this logic didn't exist because the request never reached Python. Now, the view explicitly checks check_access_component. If the user doesn't have rights to the component, they get a 403 Forbidden, not a PNG of your CEO's password hint.

The Exploit: Doomscrolling the Media Directory

Exploiting this is trivially easy if you know the file paths, and moderately annoying if you don't. Since directory listing is usually disabled in Nginx/Apache by default (hopefully), an attacker can't just browse the directory tree like an FTP server.

However, filenames in Weblate might be predictable or leaked via other channels. For example:

  1. Metadata Leakage: An attacker with access to a public component might query the API and see how screenshot URLs are structured. If they are sequential IDs (/screenshots/1001.png, /screenshots/1002.png), it's game over. A simple curl loop will scrape the entire database.
  2. Hash Prediction: If filenames are based on hashes of known values (like project names or timestamps), they can be brute-forced.
  3. Client-Side Leaks: Sometimes, a JavaScript bundle or a cached API response might reference a "private" screenshot URL that the frontend intends to hide but still knows about.

Once the attacker has the URL, they simply paste it into their browser. No cookies, no login, no exploit payloads needed. It's the digital equivalent of walking into a bank vault because the door was propped open with a chair.

The Fix: Closing the Blinds

Fixing CVE-2026-21889 is unique because pip install --upgrade weblate is not enough. If you upgrade the application code but leave your Nginx config pointing /media/ to the filesystem, you are still vulnerable.

Step 1: The Upgrade Update Weblate to version 5.15.2 or later. This installs the new ScreenshotView logic capable of verifying permissions.

Step 2: The Config Purge You must edit your web server configuration (Nginx, Apache, etc.). You need to remove any alias or root directive that serves the /media/ directory directly.

For Nginx, delete this block:

# DELETE THIS BLOCK
location /media/ {
    alias /var/lib/weblate/media/;
}

Instead, requests to /media/ should fall through to the default @uwsgi or @proxy block that hands traffic off to the Django application. This ensures the new Python code actually runs for every image request.

Official Patches

WeblateGitHub Commit Fix
WeblateOfficial Advisory

Fix Analysis (1)

Technical Appendix

CVSS Score
7.5/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
EPSS Probability
0.04%
Top 87% most exploited

Affected Systems

Weblate < 5.15.2

Affected Versions Detail

Product
Affected Versions
Fixed Version
Weblate
WeblateOrg
< 5.15.25.15.2
AttributeDetail
CWECWE-284 (Improper Access Control)
CVSS v3.17.5 (High)
Attack VectorNetwork
Privileges RequiredNone
Fix Version5.15.2
LikelihoodLow (Requires URL prediction)

MITRE ATT&CK Mapping

T1596Search Open Technical Databases
Reconnaissance
T1213Data from Information Repositories
Collection
CWE-284
Improper Access Control

Vulnerability Timeline

Fix committed to repository
2026-01-06
CVE Published
2026-01-14
Advisory Released
2026-01-14

References & Sources

  • [1]GHSA Advisory
  • [2]NVD CVE Record

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.