GitHub Security Advisory: GHSA-gh9p-q46p-57g2
Package: thorsten/phpmyfaq (COMPOSER)
Severity: MODERATE (CVSS 6.5)
CWE: CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Vulnerable versions: <= 4.1.1
Patched in: 4.1.2

Summary: phpMyFAQ: Path Traversal in Client::deleteClientFolder enables arbitrary directory deletion by non-super-admin admins

Details:
## Summary

`Client::deleteClientFolder()` in `phpmyfaq/src/phpMyFAQ/Instance/Client.php:583` takes a URL from the caller, strips the `https://` prefix, and passes the remainder to `Filesystem::deleteDirectory()` relative to the multisite `clientFolder`. No path-traversal validation runs. An admin with the `INSTANCE_DELETE` permission (a role short of SUPER_ADMIN) submits `https://../../../<path>` as the client URL and the server recursively deletes arbitrary directories under the web user's rights. Same pattern and reachability as GHSA-38m8-xrfj-v38x, which the project accepted at High severity three weeks earlier.

## Details

`phpmyfaq/src/phpMyFAQ/Instance/Client.php:583-591`:

```php
public function deleteClientFolder(string $sourceUrl): bool
{
    if (!$this->isMultiSiteWriteable()) {
        return false;
    }

    $sourcePath = str_replace(search: 'https://', replace: '', subject: $sourceUrl);
    return $this->filesystem->deleteDirectory($this->clientFolder . $sourcePath);
}
```

`str_replace` strips the scheme but does nothing about `../` segments. The concatenation `$this->clientFolder . $sourcePath` directly feeds the filesystem call, which traverses above `clientFolder` without complaint.

Callers feed the URL from the HTTP request body:

`phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/InstanceController.php:184`:

```php
if (1 !== $instanceId && $client->deleteClientFolder($clientData->url) && $client->delete($instanceId)) {
```

`$clientData->url` comes from `json_decode($request->getContent())`. The route is `admin.api.instance.delete`, gated by `INSTANCE_DELETE`. The controller does not validate the URL against a scheme list or canonicalize the path before handing it to `deleteClientFolder()`.

`InstanceController.php:144` (edit path) and `Controller/Administration/InstanceController.php:151` (form path) both reach the same sink through different entry points.

### Precedent

GHSA-38m8-xrfj-v38x (2026-03-31) disclosed the identical bug class in `MediaBrowserController::index()`: an admin-gated API endpoint concatenates a user-supplied filename to a base directory without traversal validation. phpMyFAQ accepted that report at High severity. The present finding is the same root cause in a different controller; the project's INSTANCE_ADD / INSTANCE_DELETE permission is a granular admin right, not SUPER_ADMIN, so a lower-tier admin can reach the sink.

## Proof of Concept

Prerequisites: a phpMyFAQ 4.2.x instance with the multisite subsystem bootstrapped (there must be a non-primary instance present for the delete controller branch to fire). Alice is an admin with `INSTANCE_ADD` and `INSTANCE_DELETE` rights, no `SUPER_ADMIN` flag.

Step 1: Alice authenticates and retrieves the CSRF token for the instance admin page.

Step 2: Alice creates an instance whose `url` encodes a traversal payload. The create path at `InstanceController.php:144` already concatenates to the clientFolder through the same `deleteClientFolder(`'https://' .