GitHub Security Advisory: GHSA-r7mc-x6x7-cqxx
Package: pyload-ng (PIP)
Severity: HIGH (CVSS 7.5)
CWE: CWE-269: Improper Privilege Management
Vulnerable versions: >= 0.4.0, <= 0.5.0b3.dev96
Patched in: unpatched

Summary: pyLoad SETTINGS Permission Users Can Achieve Remote Code Execution via Unrestricted Reconnect Script Configuration

Details:
## Summary

The `set_config_value()` API endpoint allows users with the non-admin `SETTINGS` permission to modify any configuration option without restriction. The `reconnect.script` config option controls a file path that is passed directly to `subprocess.run()` in the thread manager's reconnect logic. A SETTINGS user can set this to any executable file on the system, achieving Remote Code Execution. The only validation in `set_config_value()` is a hardcoded check for `general.storage_folder` — all other security-critical settings including `reconnect.script` are writable without any allowlist or path restriction.

## Details

The vulnerability chain spans two components:

**1. Unrestricted config write — `src/pyload/core/api/__init__.py:210-243`**

```python
@permission(Perms.SETTINGS)
@post
def set_config_value(self, category: str, option: str, value: Any, section: str = "core") -> None:
    self.pyload.addon_manager.dispatch_event(
        "config_changed", category, option, value, section
    )
    if section == "core":
        if category == "general" and option == "storage_folder":
            # Forbid setting the download folder inside dangerous locations
            # ... validation only for storage_folder ...
            return

        self.pyload.config.set(category, option, value)  # No validation for any other option
```

The `Perms.SETTINGS` permission (value 128) is a non-admin permission flag. The only hardcoded validation is for `general.storage_folder`. The `reconnect.script` option is written directly to config with no path validation, allowlist, or sanitization.

**2. Arbitrary script execution — `src/pyload/core/managers/thread_manager.py:157-199`**

```python
def try_reconnect(self):
    if not (
        self.pyload.config.get("reconnect", "enabled")
        and self.pyload.api.is_time_reconnect()
    ):
        return False

    # ... checks if active downloads want reconnect ...

    reconnect_script = self.pyload.config.get("reconnect", "script")
    if not os.path.isfile(reconnect_script):
        self.pyload.config.set("reconnect", "enabled", False)
        self.pyload.log.warning(self._("Reconnect script not found!"))
        return

    # ... reconnect logic ...

    try:
        subprocess.run(reconnect_script)  # Executes attacker-controlled path
    except Exception:
        # ...
```

The `reconnect_script` value comes directly from config. The only check is `os.path.isfile()` — the file must exist but there is no allowlist, no path restriction, and no signature verification.

**3. Attacker also controls timing via same SETTINGS permission**

The attacker can set `reconnect.enabled=True`, `reconnect.start_time`, and `reconnect.end_time` through the same `set_config_value()` endpoint to control when execution occurs. `toggle_reconnect()` at line 321 requires only `Perms.STATUS` — an even lower privilege.

**4. Additional privilege escalation via config access**

Beyond RCE, the same unrestricted config write allow