fix: security & correctness batch (#99, #100, #101, #102, #106)
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 16s
#99 — AI AJAX endpoint hardening: - require core.edit/core.create on com_content before generating (was reachable by any authenticated back-end user → paid-credit abuse) - callAiApi: 20s timeout + HTTP status check (throw on non-200) instead of silently returning an empty string #100 — Sitemap information disclosure + robustness: - filter to public (guest) view levels so registered/special-access articles are never written into the public sitemap - atomic write (temp file + rename) so concurrent saves can't expose a half-written sitemap.xml - (throttling + SEF URLs remain follow-ups, noted on the issue) #101 — Expose newer columns in CSV + API: - og_video, event_data, recipe_data, custom_schema added to CSV export/import (appended, so existing CSVs still import) and to the REST API field whitelist - import validates JSON fields as arrays/objects and og_video as http(s) (prevents re-introducing the #97 scalar-JSON-LD crash via import) #102 — Forward-compat (complete): - Factory::getLanguage() -> getApplication()->getLanguage() (4 sites) - Joomla\CMS\Filesystem\File/Folder -> Joomla\Filesystem\* (ImageHelper, ImageGenerator) #106 — partial: loadArticle() now caches null misses (array_key_exists), getArticleDate() skips 0000-00-00 dates. Batch-JS halt deferred — the offset=0 design re-fetches failed rows, so the created>0 guard prevents an infinite loop; a safe fix needs cursor-based pagination in BatchController.
This commit is contained in:
@@ -60,6 +60,10 @@ class ImportExportController extends BaseController
|
||||
$db->quoteName('t.robots'),
|
||||
$db->quoteName('t.canonical_url'),
|
||||
$db->quoteName('t.language'),
|
||||
$db->quoteName('t.og_video'),
|
||||
$db->quoteName('t.event_data'),
|
||||
$db->quoteName('t.recipe_data'),
|
||||
$db->quoteName('t.custom_schema'),
|
||||
])
|
||||
->from($db->quoteName('#__mokoog_tags', 't'))
|
||||
->leftJoin(
|
||||
@@ -84,7 +88,7 @@ class ImportExportController extends BaseController
|
||||
'content_type', 'content_id', 'article_title',
|
||||
'og_title', 'og_description', 'og_image', 'og_type',
|
||||
'seo_title', 'meta_description', 'robots', 'canonical_url',
|
||||
'language',
|
||||
'language', 'og_video', 'event_data', 'recipe_data', 'custom_schema',
|
||||
]);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
@@ -187,6 +191,10 @@ class ImportExportController extends BaseController
|
||||
$robots = trim($row[9] ?? '');
|
||||
$canonicalUrl = trim($row[10] ?? '');
|
||||
$language = trim($row[11] ?? '*');
|
||||
$ogVideo = $this->sanitizeUrl($row[12] ?? '');
|
||||
$eventData = $this->validateJsonField($row[13] ?? '');
|
||||
$recipeData = $this->validateJsonField($row[14] ?? '');
|
||||
$customSchema = $this->validateJsonField($row[15] ?? '');
|
||||
|
||||
// Validate language tag format (e.g., 'en-GB', '*')
|
||||
if ($language !== '*' && !preg_match('/^[a-z]{2,3}-[A-Z]{2}$/', $language)) {
|
||||
@@ -229,6 +237,10 @@ class ImportExportController extends BaseController
|
||||
'robots' => $robots,
|
||||
'canonical_url' => $canonicalUrl,
|
||||
'language' => $language,
|
||||
'og_video' => $ogVideo,
|
||||
'event_data' => $eventData,
|
||||
'recipe_data' => $recipeData,
|
||||
'custom_schema' => $customSchema,
|
||||
'published' => 1,
|
||||
'modified' => $now,
|
||||
];
|
||||
@@ -252,4 +264,45 @@ class ImportExportController extends BaseController
|
||||
);
|
||||
$app->redirect('index.php?option=com_mokoog&view=tags');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a JSON field — returns trimmed JSON only if it is an object/array.
|
||||
*
|
||||
* Scalars and invalid JSON are dropped to '' so an import can never inject a
|
||||
* payload that crashes the frontend JSON-LD renderer.
|
||||
*
|
||||
* @param string $value Raw CSV cell value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function validateJsonField(string $value): string
|
||||
{
|
||||
$value = trim($value);
|
||||
|
||||
if ($value === '' || !\is_array(json_decode($value, true))) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a URL to only allow http/https schemes.
|
||||
*
|
||||
* @param string $url Raw CSV cell value
|
||||
*
|
||||
* @return string Sanitized URL or empty string
|
||||
*/
|
||||
private function sanitizeUrl(string $url): string
|
||||
{
|
||||
$url = trim($url);
|
||||
|
||||
if ($url === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME));
|
||||
|
||||
return \in_array($scheme, ['http', 'https'], true) ? $url : '';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user