diff --git a/packages/view/src/Attributes/ExpressionAttribute.php b/packages/view/src/Attributes/ExpressionAttribute.php index 0fd8209d8..67b9acc8d 100644 --- a/packages/view/src/Attributes/ExpressionAttribute.php +++ b/packages/view/src/Attributes/ExpressionAttribute.php @@ -70,7 +70,11 @@ public static function render(string $name, mixed $value): string return ''; } - $resolvedValue = self::resolveValue($value); + $resolvedValue = htmlspecialchars( + string: self::resolveValue($value), + flags: ENT_QUOTES | ENT_SUBSTITUTE, + encoding: 'UTF-8', + ); if ($resolvedValue === '') { return ''; diff --git a/tests/Integration/View/TempestViewRendererTest.php b/tests/Integration/View/TempestViewRendererTest.php index c0d5b6e5f..a162e8885 100644 --- a/tests/Integration/View/TempestViewRendererTest.php +++ b/tests/Integration/View/TempestViewRendererTest.php @@ -1000,4 +1000,17 @@ public function test_whitespace_with_blank_lines_between_inline_elements_is_pres Test

'), ); } + + public function test_expressions_attributes_are_escaped(): void + { + $html = $this->view->render(view( + 'click', + v: 'x" onmouseover="alert(document.cookie)', + )); + + $this->assertSnippetsMatch( + 'click', + $html, + ); + } }