From b77054b76999f1668ded5874893a2bccd068ef09 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 28 Jun 2026 13:54:53 -0500 Subject: [PATCH] fix: harden input handling and output safety (#79) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - canonical_url: sanitize via sanitizeUrl() (scheme allowlist) instead of bare trim() — closes stored-XSS via addHeadLink() on the public frontend - AI endpoint: replace die('Invalid Token') with a clean event result, and strip_tags + truncate article_title to 200 chars before use - SitemapBuilder: whitelist changefreq against the sitemap spec enum, intval() noindex IDs, strict in_array comparison - MokoOG: log a WARNING when sitemap.xml write fails instead of ignoring it --- .../src/Extension/MokoOGContent.php | 2 +- .../plg_system_mokoog/src/Extension/MokoOG.php | 12 +++++++++--- .../plg_system_mokoog/src/Helper/SitemapBuilder.php | 7 +++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php b/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php index dd7d709..6f55213 100644 --- a/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php +++ b/source/packages/plg_content_mokoog/src/Extension/MokoOGContent.php @@ -274,7 +274,7 @@ final class MokoOGContent extends CMSPlugin implements SubscriberInterface 'seo_title' => strip_tags(trim($ogData['seo_title'] ?? '')), 'meta_description' => strip_tags(trim($ogData['meta_description'] ?? '')), 'robots' => trim($robots), - 'canonical_url' => trim($ogData['canonical_url'] ?? ''), + 'canonical_url' => $this->sanitizeUrl($ogData['canonical_url'] ?? ''), 'published' => 1, 'modified' => Factory::getDate()->toSql(), ]; diff --git a/source/packages/plg_system_mokoog/src/Extension/MokoOG.php b/source/packages/plg_system_mokoog/src/Extension/MokoOG.php index bb59693..07c5596 100644 --- a/source/packages/plg_system_mokoog/src/Extension/MokoOG.php +++ b/source/packages/plg_system_mokoog/src/Extension/MokoOG.php @@ -832,7 +832,10 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface $changefreq = $this->params->get('sitemap_changefreq', 'weekly'); $xml = SitemapBuilder::generate($changefreq); - SitemapBuilder::writeToFile($xml); + + if (!SitemapBuilder::writeToFile($xml)) { + \Joomla\CMS\Log\Log::add('MokoOG: Failed to write sitemap.xml — check file permissions', \Joomla\CMS\Log\Log::WARNING, 'mokoog'); + } } /** @@ -850,7 +853,10 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface return; } - \Joomla\CMS\Session\Session::checkToken() or die('Invalid Token'); + if (!\Joomla\CMS\Session\Session::checkToken()) { + $event->setArgument('result', ['Invalid Token']); + return; + } if (!$this->params->get('ai_enabled', 0)) { $event->setArgument('result', ['AI generation is not enabled']); @@ -868,7 +874,7 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface $input = $app->getInput(); $field = $input->getString('field', 'title'); - $articleTitle = $input->getString('article_title', ''); + $articleTitle = mb_substr(strip_tags($input->getString('article_title', '')), 0, 200); $prompt = $field === 'title' ? "Generate a concise, engaging social media sharing title (max 60 characters) for an article titled: \"$articleTitle\". Return only the title text, no quotes or explanation." diff --git a/source/packages/plg_system_mokoog/src/Helper/SitemapBuilder.php b/source/packages/plg_system_mokoog/src/Helper/SitemapBuilder.php index 70068e7..af2149c 100644 --- a/source/packages/plg_system_mokoog/src/Helper/SitemapBuilder.php +++ b/source/packages/plg_system_mokoog/src/Helper/SitemapBuilder.php @@ -32,6 +32,9 @@ class SitemapBuilder */ public static function generate(string $changefreq = 'weekly'): string { + $allowed = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never']; + $changefreq = \in_array($changefreq, $allowed, true) ? $changefreq : 'weekly'; + $db = Factory::getDbo(); // Get all published articles @@ -51,7 +54,7 @@ class SitemapBuilder ->where($db->quoteName('robots') . ' LIKE ' . $db->quote('%noindex%')); $db->setQuery($noindexQuery); - $noindexIds = $db->loadColumn(); + $noindexIds = array_map('intval', $db->loadColumn()); $root = rtrim(Uri::root(), '/'); $xml = '' . "\n"; @@ -66,7 +69,7 @@ class SitemapBuilder foreach ($articles as $article) { // Skip noindexed - if (in_array((int) $article->id, $noindexIds)) { + if (\in_array((int) $article->id, $noindexIds, true)) { continue; }