fix: read globalModule like Divi's render in the guard and usage search (hardening)

StoredNodeGuard accepts a zero-padded library id (Divi intval()s it) but
refuses instances whose opener holds more than one or an escaped
globalModule, so the entry Divi renders is the one checked. GlobalUsages
matches "globalModule":"0*<id>" with REGEXP behind a cheap LIKE prefilter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 04:16:18 +02:00
co-authored by Claude Opus 5.5
parent 395049ec97
commit ce4dc776e5
4 changed files with 73 additions and 15 deletions
@@ -269,6 +269,31 @@ final class StoredNodeGuardTest extends TestCase
'nicht lesbar',
true,
],
// Hardening (audit I1): Divi renders the first textual "globalModule":"<digits>" of the opener.
'instance with two library ids (duplicate key)' => [
'<!-- wp:divi/global-layout {"globalModule":"5","blockName":"divi/text","globalModule":"7"} /-->',
'nicht lesbar',
true,
],
'instance with a second library id in its local attributes' => [
self::node('divi/global-layout', [
'globalModule' => '5',
'blockName' => 'divi/text',
'localAttrs' => ['module' => ['globalModule' => '7']],
]),
'nicht lesbar',
true,
],
'instance whose library id is escaped in the markup' => [
'<!-- wp:divi/global-layout {"globalModule":"' . chr(92) . 'u0035","blockName":"divi/text"} /-->',
'nicht lesbar',
true,
],
'instance with library id zero' => [
self::node('divi/global-layout', ['globalModule' => '000', 'blockName' => 'divi/text']),
'nicht lesbar',
true,
],
'control character' => [self::text('<p>a' . chr(1) . 'b</p>'), 'Steuerzeichen'],
// Stored $variable references: the same check as client references (VariableRef).
'script in before' => [
@@ -301,6 +326,14 @@ final class StoredNodeGuardTest extends TestCase
];
}
/** Divi renders "0123" as entry 123 (intval) – such an instance is a readable reference. */
public function testALibraryIdWithLeadingZerosIsAnInstance(): void
{
$block = self::block(self::node('divi/global-layout', ['globalModule' => '0123', 'blockName' => 'divi/text']));
self::assertSame([], self::guard()->problems($block));
}
/** @dataProvider refused */
public function testRefusesWhatTheUserCouldNotWriteForUsersWithoutUnfilteredHtml(
string $markup,