fix: security + data-integrity fixes (P0/P1) #211

Merged
jmiller merged 2 commits from fix/security-integrity-p0p1 into main 2026-07-05 21:33:49 +00:00
24 changed files with 63 additions and 38 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ Joomla **package** with four sub-extensions:
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
- **Minification**: handled at build time (CI)
- **Wiki**: documentation lives in the Gitea wiki, not `docs/` files
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home)
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)
## Coding Standards
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation
# VERSION: 01.00.00
# VERSION: 02.56.07
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md
VERSION: 02.56.05
VERSION: 02.56.07
BRIEF: Security vulnerability reporting and handling policy
-->
@@ -24,7 +24,6 @@ class JsonapiView extends BaseApiView
'origin',
'backup_type',
'archivename',
'absolute_path',
'total_size',
'db_size',
'files_count',
@@ -7,7 +7,7 @@
-->
<extension type="component" method="upgrade">
<name>Component - MokoSuiteBackup</name>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,3 +1,4 @@
DROP TABLE IF EXISTS `#__mokosuitebackup_remotes`;
DROP TABLE IF EXISTS `#__mokosuitebackup_records`;
DROP TABLE IF EXISTS `#__mokosuitebackup_snapshots`;
DROP TABLE IF EXISTS `#__mokosuitebackup_profiles`;
@@ -0,0 +1 @@
/* 02.56.07 — no schema changes */
@@ -1201,6 +1201,7 @@ class AjaxController extends BaseController
private function maskSecrets(array $config, string $type): array
{
$secrets = [
'ftp' => ['password'],
'sftp' => ['password', 'passphrase', 'key_data'],
's3' => ['secret_key'],
'google_drive' => ['client_secret', 'refresh_token'],
@@ -1223,6 +1224,7 @@ class AjaxController extends BaseController
private function mergeExistingSecrets(int $id, array $config, string $type): array
{
$secrets = [
'ftp' => ['password'],
'sftp' => ['password', 'passphrase', 'key_data'],
's3' => ['secret_key'],
'google_drive' => ['client_secret', 'refresh_token'],
@@ -326,7 +326,7 @@ class DatabaseDumper
}
$createSql = str_replace('`' . $prefix, '`#__', $createRow[1]);
fwrite($fp, 'DROP TABLE IF EXISTS `' . $abstractName . "`;\\n");
fwrite($fp, 'DROP TABLE IF EXISTS `' . $abstractName . "`;\n");
fwrite($fp, $createSql . ";\n\n");
}
@@ -20,6 +20,13 @@ use Joomla\CMS\Factory;
class DatabaseImporter
{
/**
* Non-fatal per-statement errors collected during the last import().
*
* @var string[]
*/
private array $errors = [];
/**
* Import a SQL dump file into the database.
*
@@ -31,6 +38,8 @@ class DatabaseImporter
*/
public function import(string $sqlFile): int
{
$this->errors = [];
if (!is_file($sqlFile) || !is_readable($sqlFile)) {
throw new \RuntimeException('SQL file not readable: ' . $sqlFile);
}
@@ -97,8 +106,10 @@ class DatabaseImporter
} catch (\Exception $e) {
// Log but don't abort — some statements may fail on
// different MySQL versions (e.g. charset differences)
// but the overall restore should continue.
// but the overall restore should continue. Errors are
// collected so the caller can surface a warning status.
error_log('MokoSuiteBackup SQL import warning: ' . $e->getMessage());
$this->errors[] = $e->getMessage();
}
}
}
@@ -115,6 +126,7 @@ class DatabaseImporter
$statementsExecuted++;
} catch (\Exception $e) {
error_log('MokoSuiteBackup SQL import warning (final): ' . $e->getMessage());
$this->errors[] = $e->getMessage();
}
}
} finally {
@@ -123,4 +135,24 @@ class DatabaseImporter
return $statementsExecuted;
}
/**
* Non-fatal errors from the last import(), if any. A non-empty result
* means the restore completed with problems and should be treated as a
* warning rather than a clean success.
*
* @return string[]
*/
public function getErrors(): array
{
return $this->errors;
}
/**
* Whether the last import() had any non-fatal statement errors.
*/
public function hasErrors(): bool
{
return $this->errors !== [];
}
}
@@ -207,7 +207,7 @@ class SftpUploader implements RemoteUploaderInterface
*/
private function buildScpCommand(string $localPath, string $remoteTarget, ?string $keyFile): string
{
$parts = ['scp', '-o', 'StrictHostKeyChecking=no', '-o', 'BatchMode=yes'];
$parts = ['scp', '-o', 'StrictHostKeyChecking=accept-new', '-o', 'BatchMode=yes'];
if ($this->port !== 22) {
$parts[] = '-P';
@@ -235,7 +235,7 @@ class SftpUploader implements RemoteUploaderInterface
*/
private function buildSshCommand(string $remoteCmd, ?string $keyFile): string
{
$parts = ['ssh', '-o', 'StrictHostKeyChecking=no', '-o', 'BatchMode=yes'];
$parts = ['ssh', '-o', 'StrictHostKeyChecking=accept-new', '-o', 'BatchMode=yes'];
if ($this->port !== 22) {
$parts[] = '-p';
@@ -8,7 +8,7 @@
-->
<extension type="module" client="administrator" method="upgrade">
<name>Module - MokoSuiteBackup - cPanel</name>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-23</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="actionlog" method="upgrade">
<name>Action Log - MokoSuiteBackup</name>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="console" method="upgrade">
<name>Console - MokoSuiteBackup</name>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -227,11 +227,11 @@ class SnapshotCommand extends AbstractCommand
}
// Delete the snapshot file if it exists
if (!empty($record->file_path) && is_file($record->file_path)) {
if (!@unlink($record->file_path)) {
$io->warning('Could not delete snapshot file: ' . $record->file_path);
if (!empty($record->data_file) && is_file($record->data_file)) {
if (!@unlink($record->data_file)) {
$io->warning('Could not delete snapshot file: ' . $record->data_file);
} else {
$io->text('Deleted file: ' . $record->file_path);
$io->text('Deleted file: ' . $record->data_file);
}
}
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="content" method="upgrade">
<name>Content - MokoSuiteBackup</name>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -59,7 +59,7 @@
type="sql"
label="PLG_CONTENT_MOKOJOOMBACKUP_FIELD_PROFILE"
description="PLG_CONTENT_MOKOJOOMBACKUP_FIELD_PROFILE_DESC"
query="SELECT id AS value, title AS text FROM #__mokosuitebackup_profiles WHERE published = 1 ORDER BY ordering ASC"
query="SELECT id AS value, title AS text FROM #__mokosuitebackup_profiles WHERE published = 1 ORDER BY id ASC"
default="1"
>
<option value="1">Default Backup Profile</option>
@@ -24,9 +24,11 @@ final class MokoSuiteBackupContent extends CMSPlugin implements SubscriberInterf
public static function getSubscribedEvents(): array
{
// Pre-update backups are owned by plg_system_mokosuitebackup, which also
// subscribes to onExtensionBeforeUpdate. Only pre-install is handled here
// to avoid running the backup twice on a single extension update.
return [
'onExtensionBeforeInstall' => 'onExtensionBeforeInstall',
'onExtensionBeforeUpdate' => 'onExtensionBeforeUpdate',
];
}
@@ -42,18 +44,6 @@ final class MokoSuiteBackupContent extends CMSPlugin implements SubscriberInterf
$this->triggerAutoBackup('Pre-install backup');
}
/**
* Trigger a backup before an extension is updated.
*/
public function onExtensionBeforeUpdate(Event $event): void
{
if (!(int) $this->params->get('backup_before_update', 1)) {
return;
}
$this->triggerAutoBackup('Pre-update backup');
}
/**
* Run a backup using the configured profile.
*/
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="quickicon" method="upgrade">
<name>Quick Icon - MokoSuiteBackup</name>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuiteBackup</name>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -11,7 +11,7 @@
type="sql"
label="PLG_TASK_MOKOJOOMBACKUP_FIELD_PROFILE"
description="PLG_TASK_MOKOJOOMBACKUP_FIELD_PROFILE_DESC"
query="SELECT id AS value, title AS text FROM #__mokosuitebackup_profiles WHERE published = 1 ORDER BY ordering ASC"
query="SELECT id AS value, title AS text FROM #__mokosuitebackup_profiles WHERE published = 1 ORDER BY id ASC"
default="1"
required="true"
>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="task" method="upgrade">
<name>Task - MokoSuiteBackup</name>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -7,7 +7,7 @@
-->
<extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuiteBackup</name>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+1 -1
View File
@@ -8,7 +8,7 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuiteBackup</name>
<packagename>mokosuitebackup</packagename>
<version>02.56.05</version>
<version>02.56.07</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>