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
@@ -267,4 +267,19 @@ final class MediaUpdateAbilityTest extends TestCase
self::assertSame(50, $ability->inputSchema()['properties']['items']['maxItems']);
self::assertStringContainsString('modified', $ability->description());
}
/** Ruling S52: description is the attachment's post_content – no Divi markup there either. */
public function testDescriptionRefusesDiviMarkupEvenWithControlCharacters(): void
{
foreach (["<!-- wp:divi/text /-->", "<!--\0 wp:divi/text /-->", "[et_\x01pb_text]x[/et_pb_text]"] as $description) {
$result = (new MediaUpdateAbility($this->log()))->execute(['items' => [['id' => 33, 'description' => $description]]]);
self::assertToolError($result, 'validation_failed', $description);
self::assertStringContainsString('Divi', $result->get_error_data()['failures'][0]['error']);
}
self::assertSame([], $this->postUpdates);
(new MediaUpdateAbility(FakeChangeLog::any(9)))->execute(['items' => [['id' => 33, 'description' => '<em>Team</em>']]]);
self::assertSame([['post_content' => '<em>Team</em>', 'ID' => 33]], $this->postUpdates);
}
}