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
@@ -118,7 +118,9 @@ class GlobalUsages
}
/**
* Every post (no revision, not trashed) that shows $libraryId, by ID – one query, no content.
* Every post (no revision, not trashed) that shows $libraryId, by ID – one query, no content. Like
* Divi's render, "globalModule":"0123" names entry 123 (hardening); the LIKE only narrows the
* rows the REGEXP looks at.
*
* @return list<array{id: int, status: string, type: string}>
*/
@@ -129,8 +131,9 @@ class GlobalUsages
$rows = $db->get_results(
$db->prepare(
'SELECT ID, post_status, post_type FROM ' . $db->posts . ' WHERE post_content LIKE %s AND '
. "post_type <> 'revision' AND " . self::LISTED . ' ORDER BY ID ASC',
self::like($db, $libraryId)
. "post_content REGEXP %s AND post_type <> 'revision' AND " . self::LISTED . ' ORDER BY ID ASC',
'%' . $db->esc_like('"globalModule":"') . '%',
'"globalModule":"0*' . $libraryId . '"'
),
'ARRAY_A'
);
@@ -164,11 +167,6 @@ class GlobalUsages
return current_user_can('edit_post', $row['id']);
}
private static function like(\wpdb $db, int $libraryId): string
{
return '%' . $db->esc_like('"globalModule":"' . $libraryId . '"') . '%';
}
private function db(): \wpdb
{
return $this->db ?? $GLOBALS['wpdb'];