HexBrain

Weblate · Report #4022462

Uncaught RecursionError crash in translation-finder (deep directory tree DoS)

Validstatus
lowseverity
16upvotes
14 Oct 2026disclosed

How it was found

Type: Denial of Service (Availability) Severity: Low-Medium (CVSS 3.7-5.5 depending on access) Affected: WeblateOrg/translation-finder v3.4.1, commit f5f8bb5 (current main)

TL;DR

translation-finder scans repository content using recursion without any depth limit. A repository with ~1000 nested folders causes an uncaught RecursionError that crashes the entire discovery call. The error is not handled anywhere, so Weblate's import/update/hook processing fails repeatedly.

Tested environment

  • Python 3.13.9
  • charset-normalizer 3.5.1
  • ruamel.yaml 0.19.1
  • Linux x86_64

Where the bug is

translation_finder/finder.py, lines 157–177:

def list_files(self, root: PurePath, files: PathListType, dirs: PathListType) -> None:
    with scandir(root) as matches:
        for match in matches:
            if match.is_symlink():
                continue
            is_dir = match.is_dir()
            path = Path(match.path)
            if any(path.match(exclude) for exclude in EXCLUDES):
                continue
            if is_dir:
                dirs.append(self.process_path(path))
                try:
                    self.list_files(path, files, dirs)  # <-- line 176: unbounded recursion
                except OSError:
                    continue
            else:
                files.append(self.process_path(path))

The function calls itself recursively for every subdirectory. Only OSError is caught — RecursionError escapes and crashes discover() / Finder().

Why it matters

Git fully supports trees that are 1000+ levels deep. Any contributor can push such a tree to a repository. When Weblate scans that repo (import, update, webhook, PR), the discovery task crashes. Every retry also crashes, so the component's translation activity stops.

Steps to reproduce

  1. Run the PoC script:
    python3 poc_deepnest.py 1000
    
  2. The script creates a tree with 1000 nested directories plus two tiny translation files.
  3. It calls discover() on the tree.

Result (measured, repeatable)

DepthResult
960pass — no crash
985pass — no crash
990CRASH — RecursionError
1000, 1050, 1100CRASH

The exact boundary is ~960–1000 depending on Python version and stack. Below it works, above it always crashes.

Proof

Attachment: bug1_traceback.txt — actual crash traceback from a real run.

Key frames:

File "translation_finder/api.py", line 54, in discover
    finder = Finder(root, mock=mock)
File "translation_finder/finder.py", line 73, in __init__
    self.list_files(root, files, dirs)
File "translation_finder/finder.py", line 176, in list_files
    self.list_files(path, files, dirs)
[Previous line repeated 984 more times]
File "translation_finder/finder.py", line 154, in process_path
    relative = path.relative_to(self.root)
RecursionError: maximum recursion depth exceeded

Attachment: bug1_finder_frames.txt — same crash with library frames extracted for readability.

CVSS 3.1

  • AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H = 3.7 (Low) — admin action required for import
  • PR:N in webhook / PR-driven setups = 5.5 (Medium)

Weakness

  • CWE-674: Uncontrolled Recursion
  • CWE-400: Uncontrolled Resource Consumption (secondary)

Suggested fix

Best: Replace recursion with an iterative walk (os.walk()), or add a depth cap (~256).

Minimum: Catch RecursionError and skip that subtree:

try:
    self.list_files(path, files, dirs)
except (OSError, RecursionError):  # add RecursionError here
    continue

This is a one-line fix that turns the crash into a skip.

Attachments

FileWhat it is
poc_deepnest.pyPoC script — creates deep tree and crashes discover()
bug1_traceback.txtReal crash traceback (proof)
bug1_finder_frames.txtSame crash, library frames extracted (readable summary)

Impact

  • Availability: uncaught exception kills Weblate's discovery task
  • Repeated failures: every retry crashes again
  • Component stuck: import/update/hook activity stops for affected component
  • Aggregate CPU DoS: possible with many nested repos

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original