From 3328d7cf1909978c4391496d257ca0a36aa29c57 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 21 Jun 2026 18:50:07 -0500 Subject: [PATCH 1/2] feat: backup type filter + path traversal protection (#68, #72) #68: Add backup type filter dropdown to backups list view - filter_backups.xml: full/database/files/differential options - BackupsModel: backup_type filter in getListQuery() - Language string: COM_MOKOJOOMBACKUP_FILTER_TYPE_ALL #72: Path traversal protection in RestoreEngine and MokoRestore - RestoreEngine::extractArchive(): validate ZIP entries before extractTo() - RestoreEngine::extractTarGz(): validate PharData entries before extractTo() - MokoRestore standalone script: same validation in generated PHP code - Rejects entries containing ../ or starting with / or \ Closes #68, closes #72 --- .../forms/filter_backups.xml | 12 +++++++++ .../language/en-GB/com_mokosuitebackup.ini | 1 + .../src/Engine/MokoRestore.php | 14 ++++++++++ .../src/Engine/RestoreEngine.php | 26 +++++++++++++++++++ .../src/Model/BackupsModel.php | 7 +++++ 5 files changed, 60 insertions(+) diff --git a/source/packages/com_mokosuitebackup/forms/filter_backups.xml b/source/packages/com_mokosuitebackup/forms/filter_backups.xml index 11af4cc..a44abd5 100644 --- a/source/packages/com_mokosuitebackup/forms/filter_backups.xml +++ b/source/packages/com_mokosuitebackup/forms/filter_backups.xml @@ -19,6 +19,18 @@ + + + + + + + diff --git a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini index 72366dd..bf9735b 100644 --- a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini +++ b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini @@ -167,6 +167,7 @@ COM_MOKOJOOMBACKUP_STATUS_PENDING="Pending" COM_MOKOJOOMBACKUP_FILTER_SEARCH="Search" COM_MOKOJOOMBACKUP_FILTER_STATUS="Status" COM_MOKOJOOMBACKUP_FILTER_STATUS_ALL="- Select Status -" +COM_MOKOJOOMBACKUP_FILTER_TYPE_ALL="- Select Type -" ; Tabs and fieldsets COM_MOKOJOOMBACKUP_TAB_GENERAL="General" diff --git a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php index 4c2f63f..06254ed 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php +++ b/source/packages/com_mokosuitebackup/src/Engine/MokoRestore.php @@ -303,6 +303,20 @@ function actionExtract(array $data): array $zip->setPassword($password); } + // Validate all entries before extraction (path traversal protection) + for ($i = 0; $i < $zip->numFiles; $i++) { + $entryName = $zip->getNameIndex($i); + + if ($entryName === false) { + continue; + } + + if (str_contains($entryName, '../') || str_contains($entryName, '..\\') || str_starts_with($entryName, '/') || str_starts_with($entryName, '\\')) { + $zip->close(); + throw new RuntimeException('Archive contains unsafe path: ' . $entryName); + } + } + if (!$zip->extractTo(RESTORE_DIR)) { $zip->close(); throw new RuntimeException( diff --git a/source/packages/com_mokosuitebackup/src/Engine/RestoreEngine.php b/source/packages/com_mokosuitebackup/src/Engine/RestoreEngine.php index c95ae67..7ffd2b6 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/RestoreEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/RestoreEngine.php @@ -191,6 +191,20 @@ class RestoreEngine $this->log('Decryption password set'); } + // Validate all entries before extraction (path traversal protection) + for ($i = 0; $i < $zip->numFiles; $i++) { + $entryName = $zip->getNameIndex($i); + + if ($entryName === false) { + continue; + } + + if (str_contains($entryName, '../') || str_contains($entryName, '..\\') || str_starts_with($entryName, '/') || str_starts_with($entryName, '\\')) { + $zip->close(); + throw new \RuntimeException('Archive contains unsafe path: ' . $entryName); + } + } + if (!$zip->extractTo($this->stagingDir)) { $zip->close(); @@ -210,6 +224,18 @@ class RestoreEngine private function extractTarGz(string $archivePath): void { $phar = new \PharData($archivePath); + + // Validate all entries before extraction (path traversal protection) + foreach (new \RecursiveIteratorIterator($phar) as $entry) { + $entryName = $entry->getPathname(); + // PharData paths are prefixed with phar:// — extract the relative part + $relative = substr($entryName, strlen('phar://' . $archivePath) + 1); + + if (str_contains($relative, '../') || str_contains($relative, '..\\') || str_starts_with($relative, '/') || str_starts_with($relative, '\\')) { + throw new \RuntimeException('Archive contains unsafe path: ' . $relative); + } + } + $phar->extractTo($this->stagingDir, null, true); $this->log('Extracted tar.gz archive'); } diff --git a/source/packages/com_mokosuitebackup/src/Model/BackupsModel.php b/source/packages/com_mokosuitebackup/src/Model/BackupsModel.php index c7466d3..d3290ad 100644 --- a/source/packages/com_mokosuitebackup/src/Model/BackupsModel.php +++ b/source/packages/com_mokosuitebackup/src/Model/BackupsModel.php @@ -61,6 +61,13 @@ class BackupsModel extends ListModel $query->where($db->quoteName('a.profile_id') . ' = ' . (int) $profileId); } + // Filter by backup type + $backupType = $this->getState('filter.backup_type'); + + if (!empty($backupType)) { + $query->where($db->quoteName('a.backup_type') . ' = ' . $db->quote($backupType)); + } + // Filter by search $search = $this->getState('filter.search'); -- 2.52.0 From 57bfb37be1fc2ac8f08bb29b858d94a1f8b70e5a Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 21 Jun 2026 18:54:45 -0500 Subject: [PATCH 2/2] fix: remove orphaned root-level webservices plugin files The root mokosuitebackup.xml and mokosuitebackup.php are stale copies from before the code was restructured into source/packages/. The webservices plugin lives at source/packages/plg_webservices_mokosuitebackup/ with its own manifest and src/ directory. The root manifest still referenced src but that directory was removed in PR #82, causing Joomla installer to fail with "File does not exist [ROOT][TMP]/.../mokosuitebackup/src". --- mokosuitebackup.php | 11 ----------- mokosuitebackup.xml | 31 ------------------------------- 2 files changed, 42 deletions(-) delete mode 100644 mokosuitebackup.php delete mode 100644 mokosuitebackup.xml diff --git a/mokosuitebackup.php b/mokosuitebackup.php deleted file mode 100644 index 2954662..0000000 --- a/mokosuitebackup.php +++ /dev/null @@ -1,11 +0,0 @@ - - * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. - * @license GNU General Public License version 3 or later; see LICENSE - */ - -defined('_JEXEC') or die; diff --git a/mokosuitebackup.xml b/mokosuitebackup.xml deleted file mode 100644 index 962b0f7..0000000 --- a/mokosuitebackup.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - Web Services - MokoSuiteBackup - 01.27.03 - 2026-06-02 - Moko Consulting - hello@mokoconsulting.tech - https://mokoconsulting.tech - Copyright (C) 2026 Moko Consulting. All rights reserved. - GPL-3.0-or-later - PLG_WEBSERVICES_MOKOJOOMBACKUP_DESCRIPTION - - Joomla\Plugin\WebServices\MokoSuiteBackup - - - mokosuitebackup.php - services - src - - - - language/en-GB/plg_webservices_mokosuitebackup.ini - language/en-GB/plg_webservices_mokosuitebackup.sys.ini - - -- 2.52.0