From f9c1c6b18609750c0206dade1e92337d52b9f84a Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 12 Jul 2026 17:14:10 -0500 Subject: [PATCH 1/2] @ fix(review): open-redirect, btoa UTF-8 safety, honest duplicate-mode txn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-release review findings on dev -> main: - Return-URL open-redirect (MED): a base64 returnurl like "/\evil.com" passed the root-relative allow-list (raw[1] is "\", not "/"), and a browser normalises leading "/\" to "//", yielding a protocol-relative redirect off-site. Reject any backslash or control char in the decoded return URL outright — legitimate Joomla admin URLs never contain them. - installer-backup.js btoa() (LOW): btoa() throws on non-Latin1 input (e.g. a UTF-8 filter value in the list query string), and the throw was uncaught after preventDefault(), leaving the toolbar button dead. Build the redirect (UTF-8-safe base64) BEFORE swallowing the click; if it still fails, bail without preventing so Joomla proceeds normally. - Snapshot duplicate-mode transaction (HIGH): restore() wrapped duplicate mode in transactionStart()/Rollback(), but duplicate mode writes via Joomla Table API whose Nested tables (categories/tags) issue LOCK TABLES, which implicitly COMMITS the open transaction in MySQL — so the rollback promise was illusory and a mid-inject failure left a slave half-updated. Run only the raw-DB modes (replace/create/merge) inside the transaction; duplicate mode runs outside it, matching its existing per-item defensive design (bad item skipped + logged, never fatal). Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i @ --- .../src/Engine/SnapshotRestoreEngine.php | 38 ++++++++++++++----- .../tmpl/runbackup/default.php | 8 ++++ .../media/js/installer-backup.js | 23 ++++++++--- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php b/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php index 9e32577a..3b59ccf0 100644 --- a/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php +++ b/source/packages/com_mokosuitebackup/src/Engine/SnapshotRestoreEngine.php @@ -125,11 +125,23 @@ class SnapshotRestoreEngine $prefix = $db->getPrefix(); $totalRows = 0; - try { - $db->transactionStart(); + // Build list of tables to restore based on selected types + $tablesToRestore = $this->getTablesToRestore($restoreTypes); - // Build list of tables to restore based on selected types - $tablesToRestore = $this->getTablesToRestore($restoreTypes); + /* Duplicate mode inserts brand-new records through Joomla's Table API, + whose Nested tables (categories, tags) issue LOCK TABLES — which + implicitly COMMITS any open transaction in MySQL/InnoDB. Wrapping it + in transactionStart()/Rollback() would be a false atomicity promise: + the first category/tag store would commit and defeat the rollback. + Duplicate mode is therefore best-effort and per-item defensive (a bad + item is skipped and logged, never fatal) rather than all-or-nothing. + The raw-DB modes (replace/create/merge) remain fully transactional. */ + $useTransaction = ($mode !== 'duplicate'); + + try { + if ($useTransaction) { + $db->transactionStart(); + } if ($mode === 'duplicate') { $totalRows = $this->restoreDuplicate($data, $restoreTypes); @@ -156,7 +168,9 @@ class SnapshotRestoreEngine } } - $db->transactionCommit(); + if ($useTransaction) { + $db->transactionCommit(); + } $this->log('Restore complete: ' . $totalRows . ' total rows'); @@ -188,11 +202,15 @@ class SnapshotRestoreEngine 'log' => implode("\n", $this->log), ]; } catch (\Throwable $e) { - try { - $db->transactionRollback(); - $this->log('Transaction rolled back'); - } catch (\Exception $rollbackEx) { - $this->log('Rollback failed: ' . $rollbackEx->getMessage()); + if ($useTransaction) { + try { + $db->transactionRollback(); + $this->log('Transaction rolled back'); + } catch (\Exception $rollbackEx) { + $this->log('Rollback failed: ' . $rollbackEx->getMessage()); + } + } else { + $this->log('Duplicate mode is not transactional; records already inserted are left in place.'); } $this->log('FATAL: ' . $e->getMessage()); diff --git a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php index 5e148445..5f408e55 100644 --- a/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php +++ b/source/packages/com_mokosuitebackup/tmpl/runbackup/default.php @@ -34,6 +34,14 @@ if ($this->returnUrl !== '') { $decoded = base64_decode($this->returnUrl, true); $raw = ($decoded !== false && $decoded !== '') ? $decoded : $this->returnUrl; + /* Reject any backslash or control char outright: legitimate Joomla admin + return URLs never contain them, and a browser normalises a leading "/\" + to "//", turning a would-be root-relative path into a protocol-relative + open redirect (e.g. "/\evil.com" → "//evil.com"). */ + if (preg_match('/[\x00-\x1f\x7f\\\\]/', $raw)) { + $raw = ''; + } + $isAbsoluteHttp = (bool) preg_match('#^https?://#i', $raw); $isRootRelative = isset($raw[0]) && $raw[0] === '/' && (!isset($raw[1]) || $raw[1] !== '/'); diff --git a/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js b/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js index 6f80f850..477df90e 100644 --- a/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js +++ b/source/packages/plg_system_mokosuitebackup/media/js/installer-backup.js @@ -70,6 +70,22 @@ return; } + var ret = window.location.href; + ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1'; + + /* Build the redirect BEFORE we swallow the click. btoa() throws on + non-Latin1 input (e.g. a UTF-8 filter value in the query string), so + encode as UTF-8 first; if it still fails, bail WITHOUT preventing the + click so Joomla's own update/uninstall proceeds normally. */ + var target; + try { + target = cfg.runbackupUrl + + '&profile_id=' + encodeURIComponent(cfg.profileId) + + '&returnurl=' + encodeURIComponent(btoa(unescape(encodeURIComponent(ret)))); + } catch (x) { + return; + } + e.preventDefault(); e.stopImmediatePropagation(); @@ -77,12 +93,7 @@ sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ task: task, cids: cids })); } catch (x) {} - var ret = window.location.href; - ret += (ret.indexOf('?') === -1 ? '?' : '&') + 'msb_resume=1'; - - window.location.href = cfg.runbackupUrl - + '&profile_id=' + encodeURIComponent(cfg.profileId) - + '&returnurl=' + encodeURIComponent(btoa(ret)); + window.location.href = target; }, true); /* On return from the backup screen: restore the selection and re-fire. */ -- 2.52.0 From 498f62e9750a674333572e423c3e679f0cd20856 Mon Sep 17 00:00:00 2001 From: "mokogitea-actions[bot]" Date: Sun, 12 Jul 2026 22:14:30 +0000 Subject: [PATCH 2/2] chore(version): pre-release bump to 02.58.36-dev [skip ci] --- .mokogitea/workflows/issue-branch.yml | 2 +- SECURITY.md | 2 +- source/packages/com_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/com_mokosuitebackup/sql/updates/mysql/02.58.36.sql | 1 + .../mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml | 2 +- .../packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml | 2 +- .../packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml | 2 +- source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml | 2 +- .../plg_webservices_mokosuitebackup/mokosuitebackup.xml | 2 +- source/pkg_mokosuitebackup.xml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.36.sql diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index c58ebd05..934a65ce 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 02.58.35 +# VERSION: 02.58.36 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/SECURITY.md b/SECURITY.md index 345d2cb3..73a6d6e9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla INGROUP: Template-Joomla.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla PATH: /SECURITY.md -VERSION: 02.58.35 +VERSION: 02.58.36 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitebackup/mokosuitebackup.xml b/source/packages/com_mokosuitebackup/mokosuitebackup.xml index 187ea632..61d533d3 100644 --- a/source/packages/com_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/com_mokosuitebackup/mokosuitebackup.xml @@ -17,7 +17,7 @@ display label there. --> MokoSuiteBackup - 02.58.35 + 02.58.36 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.36.sql b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.36.sql new file mode 100644 index 00000000..bd262657 --- /dev/null +++ b/source/packages/com_mokosuitebackup/sql/updates/mysql/02.58.36.sql @@ -0,0 +1 @@ +/* 02.58.36 — no schema changes */ diff --git a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml index 4f635e5c..e9756fec 100644 --- a/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml +++ b/source/packages/mod_mokosuitebackup_cpanel/mod_mokosuitebackup_cpanel.xml @@ -8,7 +8,7 @@ --> Module - MokoSuiteBackup - cPanel - 02.58.35 + 02.58.36 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml index 3bc177fb..dd9ca3e9 100644 --- a/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_actionlog_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Action Log - MokoSuiteBackup - 02.58.35 + 02.58.36 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml index 45e20e18..951da50a 100644 --- a/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_console_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Console - MokoSuiteBackup - 02.58.35 + 02.58.36 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml index 578c792f..3d0f5112 100644 --- a/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_content_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Content - MokoSuiteBackup - 02.58.35 + 02.58.36 2026-06-04 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml index 53de7146..3ee21949 100644 --- a/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_quickicon_mokosuitebackup/mokosuitebackup.xml @@ -1,7 +1,7 @@ Quick Icon - MokoSuiteBackup - 02.58.35 + 02.58.36 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml index fb2f9f37..53e6e4fa 100644 --- a/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_system_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> System - MokoSuiteBackup - 02.58.35 + 02.58.36 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml index 46dd12db..670652f3 100644 --- a/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_task_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Task - MokoSuiteBackup - 02.58.35 + 02.58.36 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml index 8cdaf10f..86609f71 100644 --- a/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml +++ b/source/packages/plg_webservices_mokosuitebackup/mokosuitebackup.xml @@ -7,7 +7,7 @@ --> Web Services - MokoSuiteBackup - 02.58.35 + 02.58.36 2026-06-02 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitebackup.xml b/source/pkg_mokosuitebackup.xml index 9690b40c..b175f745 100644 --- a/source/pkg_mokosuitebackup.xml +++ b/source/pkg_mokosuitebackup.xml @@ -8,7 +8,7 @@ Package - MokoSuiteBackup mokosuitebackup - 02.58.35 + 02.58.36 2026-06-02 Moko Consulting hello@mokoconsulting.tech -- 2.52.0