fix: Task 3 review round 1 - strip control chars before the sanitizer (S32a), S34 minors

Control characters now go on the raw value in AttrWriter::cleanNode and
VariableRef::setting; after the sanitizer only rejection remains ('<' + VT/FF
no longer turns into a tag). VALUE_KEY anchored with D, exact column-width
fractions, nested rows in columns, specialty columns refused, fullwidth by
catalog category, preset ambiguity in DesignSystem::modulePresetId().

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 14:26:18 +02:00
co-authored by Claude Opus 5.5
parent 4b72c2bb42
commit 8688a02402
13 changed files with 278 additions and 104 deletions
@@ -93,6 +93,15 @@ final class VariableRefTest extends TestCase
self::content('post_title', '{"before":"' . $script . 'Hallo","after":""}'),
self::content('post_title', '{"before":"Hallo","after":""}'),
],
// S32a: controls go before text(), so "<" + VT/FF is a tag text() removes.
'VT-split tag in date format' => [
self::content('post_date', '{"custom_date_format":"<' . self::escape('000b') . 'img src=x onerror=alert(1)>d.m"}'),
self::content('post_date', '{"custom_date_format":"d.m"}'),
],
'FF-split script in date format' => [
self::content('post_date', '{"custom_date_format":"<' . self::escape('000c') . 'script>alert(1)</script>d.m"}'),
self::content('post_date', '{"custom_date_format":"d.m"}'),
],
'tags in date format' => [
self::content('post_date', '{"custom_date_format":"<b>d</b>.m"}'),
self::content('post_date', '{"custom_date_format":"d.m"}'),