4 Commits

Author SHA1 Message Date
gitea-actions[bot] dd597a3ecd chore(release): build 01.05.00-rc [skip ci]
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 14s
2026-06-07 14:18:39 +00:00
Jonathan Miller 41b481dbfe fix: address code review — Apache 2.4 htaccess, browseDir traversal, SQL cast
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 3s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 7s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 9s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 14s
Universal: Build & Release / Promote to RC (pull_request) Successful in 19s
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
- Update .htaccess content to support both Apache 2.4 (Require all denied)
  and Apache 2.2 (Order deny,allow) in all four locations
- Guard browseDir parent navigation to prevent escaping allowed boundaries
- Add explicit (int) cast on viewLog SQL query for defense-in-depth
2026-06-07 09:17:20 -05:00
Jonathan Miller e72a007041 fix: address PR review — error logging, ACL check, fetch error handling
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 5s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 10s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 10s
Universal: Build & Release / Promote to RC (pull_request) Successful in 14s
- Log failures in protectBackupDir() and protectWebAccessibleDir() instead
  of silently suppressing with @ (security-critical .htaccess writes)
- Add error_log() to empty catch blocks in boot() and syncMenuIcons()
- Add core.manage ACL check to checkDir() AJAX endpoint
- Surface opendir() failures in browseDir() with warning message
- Add HTTP status check (r.ok) to JS fetch calls before parsing JSON
- Log temp SQL file deletion failures in SteppedBackupEngine
2026-06-07 09:11:39 -05:00
Jonathan Miller 3edf635a4c docs: update changelog with all unreleased changes for v01.05.00
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 8s
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 8s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 10s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request_target) Failing after 13s
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
2026-06-07 09:03:39 -05:00
20 changed files with 87 additions and 65 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<display-name>Package - MokoJoomBackup</display-name>
<org>MokoConsulting</org>
<description>Full-site backup and restore for Joomla — database, files, and configuration</description>
<version>01.04.02-dev</version>
<version>01.05.00-dev</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokoplatform.Automation
# VERSION: 01.04.02
# VERSION: 01.05.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+20 -36
View File
@@ -1,8 +1,27 @@
# Changelog
## [Unreleased]
## [01.05.00] --- 2026-06-07
### Added
- Dashboard submenu entry as default landing page with `class:home` icon
- `[DEFAULT_DIR]` placeholder for portable backup directory configuration — resolves to `administrator/components/com_mokojoombackup/backups` at runtime
- Live AJAX directory validation on backup_dir field — checks existence, writability, and placeholder resolution as user types (debounced 400ms)
- `checkDir` AJAX endpoint for real-time directory permission checking
- Web-accessible warning badge on backup download buttons when archive is inside web root
- Inline security warning in FolderPicker when default directory is selected
- Auto `.htaccess` and `index.html` protection for web-accessible backup directories on profile save and at backup time
- Font Awesome 6 submenu icons via CSS injection in `MokoJoomBackupComponent::boot()`
- `syncMenuIcons()` installer postflight — syncs icon classes to `#__menu` on install and update
- `encryptionPassword` property on `SteppedSession` for upcoming stepped backup encryption support
### Changed
- Profile `backup_dir` default changed from literal path to `[DEFAULT_DIR]` placeholder
- Backup engine fallback directory changed from hardcoded path to `[DEFAULT_DIR]`
- `isUsingDefaultBackupDir()` now matches `[DEFAULT_DIR]` placeholder in addition to literal path and empty values
- Dashboard submenu language key added to `.sys.ini` files (en-GB, en-US)
## [01.04.00] --- 2026-06-07
@@ -67,38 +86,3 @@
- SQL update migration and error handling
- Removed orphaned scriptfile from component manifest
- Consolidated admin files into single files block
## 01.00 — 2026-06-02
### Added
- Initial package structure with component, system plugin, task plugin, and webservices plugin
- Joomla Scheduled Tasks integration (plg_task_mokojoombackup) — create multiple tasks, each running a different backup profile on its own schedule
- Individual form fields for all profile settings (no raw JSON)
- FTP/FTPS uploader with recursive directory creation, passive mode, SSL, and size verification
- Google Drive uploader using OAuth2 refresh tokens and resumable upload API
- S3-compatible remote storage: AWS S3, Wasabi, Backblaze B2, MinIO (#16)
- RemoteUploaderInterface for pluggable storage backends
- Remote upload integrated into BackupEngine with option to delete local copy after upload
- Restore engine with file restoration and database import
- MokoRestore standalone restore script — self-contained site restoration without Joomla
- "Include Restore Script" toggle per profile
- FileRestorer with protected file handling (preserves configuration.php, .htaccess)
- DatabaseImporter with streaming line-by-line SQL execution and error tolerance
- Admin dashboard quickicon widget — backup status at a glance with warnings (#18)
- Differential backups — only back up files changed since last full backup (#19)
- DifferentialScanner with file manifests stored in backup records
- JPA archive format import for Akeeba Backup migration (#20)
- AES-256 archive encryption with per-profile password (#17)
- SHA-256 checksum verification for backup integrity (#15)
- Email notifications on backup success/failure via Joomla mailer (#14)
- Akeeba Backup Pro importer — profiles, filters, remote storage, and backup history
- Auto-disables Akeeba plugins and scheduled tasks after successful import
- AJAX step-based backup engine for shared hosting (overcomes max_execution_time)
- Progress bar modal in admin UI with real-time phase/percentage updates
- Per-profile archive settings: format, compression level, split size, backup directory
- Backup engine with database dumper, file scanner, and ZIP archive builder
- Backup profiles with independent configurations
- Backup record management (list, download, delete)
- CLI script for cron/scheduled backups
- REST API compatible with MokoJoomBackup MCP server
- System plugin for automatic backup cleanup with configurable retention
+1 -1
View File
@@ -1,6 +1,6 @@
# MokoJoomBackup
<!-- VERSION: 01.04.02 -->
<!-- VERSION: 01.05.00 -->
Full-site backup and restore for Joomla — database, files, and configuration.
@@ -8,7 +8,7 @@
-->
<extension type="component" method="upgrade">
<name>com_mokojoombackup</name>
<version>01.04.02-dev</version>
<version>01.05.00-rc</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -109,6 +109,7 @@ class AjaxController extends BaseController
// that could contain a backup folder (e.g., /home/user/backups)
$dirs = [];
$handle = @opendir($path);
$warning = null;
if ($handle) {
while (($entry = readdir($handle)) !== false) {
@@ -127,18 +128,37 @@ class AjaxController extends BaseController
}
closedir($handle);
} else {
$warning = 'Cannot read directory contents (check permissions)';
}
usort($dirs, fn($a, $b) => strcasecmp($a['name'], $b['name']));
$parent = dirname($path);
$this->sendJson([
// Ensure parent is still within allowed boundaries
$parentAllowed = false;
if ($parent !== $path) {
if ($jRoot !== false && strpos($parent, $jRoot) === 0) {
$parentAllowed = true;
} elseif ($homeDir !== '' && strpos($parent, $homeDir) === 0) {
$parentAllowed = true;
}
}
$response = [
'error' => false,
'current' => $path,
'parent' => ($parent !== $path) ? $parent : null,
'parent' => $parentAllowed ? $parent : null,
'dirs' => $dirs,
]);
];
if ($warning !== null) {
$response['warning'] = $warning;
}
$this->sendJson($response);
}
/**
@@ -165,7 +185,7 @@ class AjaxController extends BaseController
$query = $db->getQuery(true)
->select($db->quoteName(['absolute_path', 'log']))
->from($db->quoteName('#__mokojoombackup_records'))
->where($db->quoteName('id') . ' = ' . $id);
->where($db->quoteName('id') . ' = ' . (int) $id);
$db->setQuery($query);
$record = $db->loadObject();
@@ -205,6 +225,12 @@ class AjaxController extends BaseController
return;
}
if (!$this->app->getIdentity()->authorise('core.manage', 'com_mokojoombackup')) {
$this->sendJson(['error' => true, 'message' => 'Access denied']);
return;
}
$rawPath = trim($this->input->getString('path', ''));
if ($rawPath === '') {
@@ -530,13 +530,17 @@ class BackupEngine
$htaccess = $dir . '/.htaccess';
if (!is_file($htaccess)) {
@file_put_contents($htaccess, "Order deny,allow\nDeny from all\n");
if (@file_put_contents($htaccess, "# Apache 2.4+\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n# Apache 2.2\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\n") === false) {
error_log('MokoJoomBackup: Could not create .htaccess in backup directory: ' . $dir);
}
}
$index = $dir . '/index.html';
if (!is_file($index)) {
@file_put_contents($index, '<!DOCTYPE html><title></title>');
if (@file_put_contents($index, '<!DOCTYPE html><title></title>') === false) {
error_log('MokoJoomBackup: Could not create index.html in backup directory: ' . $dir);
}
}
}
@@ -317,8 +317,8 @@ class SteppedBackupEngine
$zip->close();
// Clean up temp SQL file
if (is_file($sqlFile)) {
@unlink($sqlFile);
if (is_file($sqlFile) && !@unlink($sqlFile)) {
error_log('MokoJoomBackup: Could not delete temp SQL file: ' . $sqlFile);
}
$totalSize = file_exists($session->archivePath) ? filesize($session->archivePath) : 0;
@@ -572,13 +572,17 @@ class SteppedBackupEngine
$htaccess = $dir . '/.htaccess';
if (!is_file($htaccess)) {
@file_put_contents($htaccess, "Order deny,allow\nDeny from all\n");
if (@file_put_contents($htaccess, "# Apache 2.4+\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n# Apache 2.2\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\n") === false) {
error_log('MokoJoomBackup: Could not create .htaccess in backup directory: ' . $dir);
}
}
$index = $dir . '/index.html';
if (!is_file($index)) {
@file_put_contents($index, '<!DOCTYPE html><title></title>');
if (@file_put_contents($index, '<!DOCTYPE html><title></title>') === false) {
error_log('MokoJoomBackup: Could not create index.html in backup directory: ' . $dir);
}
}
}
@@ -39,7 +39,7 @@ class MokoJoomBackupComponent extends MVCComponent
. ' #menu a[href*="com_mokojoombackup"][href*="view=profiles"] .sidebar-item-title::before { content: "\f013"; }'
);
} catch (\Throwable $e) {
// Non-critical
error_log('MokoJoomBackup: boot() CSS injection failed: ' . $e->getMessage());
}
}
}
@@ -183,7 +183,7 @@ class FolderPickerField extends FormField
body: form,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function(r) { return r.json(); })
.then(function(r) { if (!r.ok) throw new Error('Server error (HTTP ' + r.status + ')'); return r.json(); })
.then(function(data) {
if (data.error) {
setStatus('text-danger', 'icon-unpublish', data.message || 'Error', null);
@@ -241,7 +241,7 @@ class FolderPickerField extends FormField
body: form,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function(r) { return r.json(); })
.then(function(r) { if (!r.ok) throw new Error('Server error (HTTP ' + r.status + ')'); return r.json(); })
.then(function(data) {
if (data.error) {
tree.textContent = data.message || 'Error loading directory';
@@ -65,13 +65,17 @@ class ProfileTable extends Table
$htaccess = $resolved . '/.htaccess';
if (!is_file($htaccess)) {
@file_put_contents($htaccess, "Order deny,allow\nDeny from all\n");
if (@file_put_contents($htaccess, "# Apache 2.4+\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n# Apache 2.2\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\n") === false) {
error_log('MokoJoomBackup: Could not create .htaccess in: ' . $resolved);
}
}
$index = $resolved . '/index.html';
if (!is_file($index)) {
@file_put_contents($index, '<!DOCTYPE html><title></title>');
if (@file_put_contents($index, '<!DOCTYPE html><title></title>') === false) {
error_log('MokoJoomBackup: Could not create index.html in: ' . $resolved);
}
}
}
}
@@ -8,7 +8,7 @@
-->
<extension type="plugin" group="actionlog" method="upgrade">
<name>plg_actionlog_mokojoombackup</name>
<version>01.04.02-dev</version>
<version>01.05.00-rc</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -8,7 +8,7 @@
-->
<extension type="plugin" group="console" method="upgrade">
<name>plg_console_mokojoombackup</name>
<version>01.04.02-dev</version>
<version>01.05.00-rc</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -8,7 +8,7 @@
-->
<extension type="plugin" group="content" method="upgrade">
<name>plg_content_mokojoombackup</name>
<version>01.04.02-dev</version>
<version>01.05.00-rc</version>
<creationDate>2026-06-04</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="quickicon" method="upgrade">
<name>plg_quickicon_mokojoombackup</name>
<version>01.04.02-dev</version>
<version>01.05.00-rc</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -8,7 +8,7 @@
-->
<extension type="plugin" group="system" method="upgrade">
<name>plg_system_mokojoombackup</name>
<version>01.04.02-dev</version>
<version>01.05.00-rc</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -8,7 +8,7 @@
-->
<extension type="plugin" group="task" method="upgrade">
<name>plg_task_mokojoombackup</name>
<version>01.04.02-dev</version>
<version>01.05.00-rc</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -8,7 +8,7 @@
-->
<extension type="plugin" group="webservices" method="upgrade">
<name>plg_webservices_mokojoombackup</name>
<version>01.04.02-dev</version>
<version>01.05.00-rc</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 - MokoJoomBackup</name>
<packagename>mokojoombackup</packagename>
<version>01.04.02-dev</version>
<version>01.05.00-rc</version>
<creationDate>2026-06-02</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
+2 -2
View File
@@ -198,7 +198,7 @@ class Pkg_MokoJoomBackupInstallerScript
mkdir($backupDir, 0755, true);
// Protect backup directory with .htaccess
file_put_contents($backupDir . '/.htaccess', "Order deny,allow\nDeny from all\n");
file_put_contents($backupDir . '/.htaccess', "# Apache 2.4+\n<IfModule mod_authz_core.c>\n Require all denied\n</IfModule>\n# Apache 2.2\n<IfModule !mod_authz_core.c>\n Order deny,allow\n Deny from all\n</IfModule>\n");
file_put_contents($backupDir . '/index.html', '<!DOCTYPE html><title></title>');
}
}
@@ -243,7 +243,7 @@ class Pkg_MokoJoomBackupInstallerScript
$db->setQuery($query);
$db->execute();
} catch (\Throwable $e) {
// Non-critical
error_log('MokoJoomBackup: syncMenuIcons() failed: ' . $e->getMessage());
}
}