fix(security): raw content refuses Divi markup (audit M1)

lit/content-create and lit/content-update refuse content containing the
divi/* block delimiters WordPress' block parser accepts or Divi 4
shortcodes, so the builder-only gates (S7/S26) can't be bypassed. Other
block markup stays allowed (normal kses).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 19:13:58 +02:00
co-authored by Claude Opus 5.5
parent 0f23192873
commit 7d0837be6c
3 changed files with 65 additions and 1 deletions
@@ -75,6 +75,42 @@ final class PostWriterTest extends TestCase
return (string) self::errorFields($result)[$path];
}
/**
* Security M1: raw content must not introduce Divi layouts (the builder-only gates S7/S26 of
* the Divi tools would not apply). Refused are exactly the delimiters WordPress' block parser
* (and Divi's BlockParser, which extends it) accepts – "<!--" + whitespace + optional "/" +
* lower-case "wp:divi/" – plus Divi 4 shortcodes; other block markup stays allowed (kses).
*/
public function testContentWithDiviMarkupIsRefusedOnCreateAndUpdate(): void
{
$writer = $this->writerWithoutFields();
$refused = [
'<!-- wp:divi/section --><!-- /wp:divi/section -->',
"<p>x</p><!--\twp:divi/text {\"a\":1} /-->",
"<!--\n\n/wp:divi/row -->",
'[et_pb_section][/et_pb_section]',
];
foreach ($refused as $content) {
foreach ([true, false] as $isCreate) {
$result = $writer->prepare('page', ['content' => $content], $isCreate);
$payload = self::assertToolError($result, 'validation_failed', $content);
self::assertStringContainsString('Divi', self::errorFields($result)['content']);
self::assertStringContainsString('`divi`', (string) $payload['hint']);
self::assertStringContainsString('lit-divi-', (string) $payload['hint']);
}
}
$allowed = [
'<!-- wp:paragraph --><p>Divi ist toll</p><!-- /wp:paragraph -->',
'<!--wp:divi/text /-->',
'<!-- WP:DIVI/text /-->',
'<p>wp:divi/ im Text</p>',
];
foreach ($allowed as $content) {
self::assertIsArray($writer->prepare('page', ['content' => $content], true), $content);
}
}
public function testParentMustBeAReadableNonDescendantEntryOfAHierarchicalType(): void
{
$writer = $this->writerWithoutFields();