fix: Task 1 review round 1 - escaped )$ in reference settings, allow map, routing rulings

I-1: reject )$ in every decoded string setting and in the re-serialized
JSON. S21: string settings cleaned and re-serialized. S22: injectable
dynamic-content allow map (name => setting => field type), loop_* only via
the map. S23: unknown content.innerContent is rich text. S24: keys below
scalar kinds are unknown, ...Url path segments are URLs. S25: isVariable
removed. M-1: real \u escapes in tests, malicious suite as editor, fixture
html() raw for unfiltered_html. M-2: S7 gate at any depth plus
css.advanced.customCss.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-26 13:37:56 +02:00
co-authored by Claude Opus 5.5
parent 1ea2e16c49
commit 7180690611
7 changed files with 393 additions and 142 deletions
@@ -37,10 +37,14 @@ final class PlaceholdersTest extends TestCase
/** @dataProvider placeholders */
public function testResolvesToDiviVariables(string $input, string $expected): void
{
$resolved = ComposeFixture::placeholders()->resolve($input);
$placeholders = ComposeFixture::placeholders();
$resolved = $placeholders->resolve($input);
self::assertSame($expected, $resolved);
self::assertSame($expected !== $input, Placeholders::isVariable($resolved));
if ($expected !== $input) {
// What placeholders produce passes the strict checker unchanged (ruling S1/S25).
self::assertSame($resolved, $placeholders->references()->canonical($resolved, ComposeFixture::sanitizer()));
}
}
/** @return array<string, array{string, string}> */
@@ -131,6 +135,5 @@ final class PlaceholdersTest extends TestCase
['color' => '$variable({"type":"color","value":{"name":"gcid-primary-color","settings":{}}})$', 'size' => '20px'],
ComposeFixture::placeholders()->resolveDeep(['color' => '{{color:primary}}', 'size' => '20px'])
);
self::assertFalse(Placeholders::isVariable('$variable(kaputt)$'));
}
}