fix(security): Divi-markup refusal sees content as storage will (ruling S52)

Control characters removed by kses (wp_kses_no_null: C0, \\0 sequences)
or ControlChars (DEL) could hide a divi/* delimiter or an et_pb shortcode
from the raw-input check. The shared DiviMarkup helper checks the raw
and the normalized form and now guards every raw-content path:
content-create/-update content and media-update description (attachment
post_content). MediaImporter logs import failures with scheme, host,
port and path only; uninstall.php lists the removed user meta.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 19:50:31 +02:00
co-authored by Claude Opus 5.5
parent b24d90d8b0
commit 99eda25452
10 changed files with 173 additions and 15 deletions
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Abilities;
defined('ABSPATH') || exit;
/**
* Divi markup in raw content (security audit M1, ruling S52): the single check for every path
* that stores client text as post_content without the Divi pipeline (lit/content-create and
* lit/content-update `content`, lit/media-update `description`). Such content would bypass the
* builder-only gates of the Divi tools (rulings S7/S26).
*
* Checked is the form storage will see: kses (wp_kses_no_null) removes C0 control characters and
* "\0" sequences only AFTER any check on the raw value, so a NUL inside "<!-- wp:divi/" would
* otherwise hide a real block. normalized() applies those removals plus DEL (like
* Divi\Compose\ControlChars::strip()); contains() refuses when either the raw or the normalized
* form holds a delimiter WordPress' block parser accepts for divi/* blocks ("<!--", whitespace,
* optional "/", lower-case "wp:divi/" – Divi's BlockParser extends it) or a Divi 4 shortcode.
* Other block markup stays allowed and goes through WordPress' kses. Pure PHP.
*/
final class DiviMarkup
{
private const PATTERN = '~<!--\s+/?wp:divi/|\[/?et_pb_~';
public const HINT = 'Divi-Inhalte über `divi` (lit-content-create) bzw. die lit-divi-*-Tools '
. '(lit-divi-insert, lit-divi-update, lit-divi-move, lit-divi-delete) ändern.';
public static function contains(string $content): bool
{
return preg_match(self::PATTERN, $content) === 1 || preg_match(self::PATTERN, self::normalized($content)) === 1;
}
/** $content without what kses (wp_kses_no_null) and ControlChars remove before it is stored. */
public static function normalized(string $content): string
{
$content = (string) preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $content);
return (string) preg_replace('/\\\\+0+/', '', $content);
}
}