Skip to content

Commit e12cb05

Browse files
authored
Merge commit from fork
Escape error title and description in HtmlErrorRenderer
2 parents 402fcbc + b3c4462 commit e12cb05

3 files changed

Lines changed: 65 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,18 @@
1010

1111
### Removed
1212

13+
## 4.15.2 - 2026-05-22
14+
15+
### Fixed
16+
17+
- Escape HTML entities in HtmlErrorRenderer to prevent XSS attacks ([GHSA-53h4-8rc4-f539](https://github.com/slimphp/Slim/security/advisories/GHSA-53h4-8rc4-f539))
18+
- Fix static analysis suppression in RouteCollector::removeNamedRoute() (#3445)
19+
20+
**Full Changelog**: https://github.com/slimphp/Slim/compare/4.15.1...4.15.2
21+
1322
## 4.15.1 - 2025-11-21
1423

15-
## Fixed
24+
### Fixed
1625

1726
- Allow PHPUnit 10, 11 and 12 when testing Slim itself (#3411)
1827

Slim/Error/Renderers/HtmlErrorRenderer.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515

1616
use function get_class;
1717
use function htmlentities;
18+
use function htmlspecialchars;
1819
use function sprintf;
1920

21+
use const ENT_QUOTES;
22+
use const ENT_SUBSTITUTE;
23+
2024
/**
2125
* Default Slim application HTML Error Renderer
2226
*/
@@ -29,7 +33,12 @@ public function __invoke(Throwable $exception, bool $displayErrorDetails): strin
2933
$html .= '<h2>Details</h2>';
3034
$html .= $this->renderExceptionFragment($exception);
3135
} else {
32-
$html = "<p>{$this->getErrorDescription($exception)}</p>";
36+
$description = htmlspecialchars(
37+
$this->getErrorDescription($exception),
38+
ENT_QUOTES | ENT_SUBSTITUTE,
39+
'UTF-8'
40+
);
41+
$html = "<p>{$description}</p>";
3342
}
3443

3544
return $this->renderHtmlBody($this->getErrorTitle($exception), $html);
@@ -56,6 +65,8 @@ private function renderExceptionFragment(Throwable $exception): string
5665

5766
public function renderHtmlBody(string $title = '', string $html = ''): string
5867
{
68+
$title = htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
69+
5970
return sprintf(
6071
'<!doctype html>' .
6172
'<html lang="en">' .

tests/Error/AbstractErrorRendererTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@
2121
use Slim\Tests\TestCase;
2222
use stdClass;
2323

24+
use function htmlspecialchars;
2425
use function json_decode;
2526
use function json_encode;
2627
use function simplexml_load_string;
2728

29+
use const ENT_QUOTES;
30+
use const ENT_SUBSTITUTE;
31+
2832
class AbstractErrorRendererTest extends TestCase
2933
{
3034
public function testHTMLErrorRendererDisplaysErrorDetails()
@@ -94,6 +98,45 @@ public function testHTMLErrorRendererRenderHttpException()
9498
$this->assertStringContainsString($exceptionDescription, $output, 'Should contain http exception description');
9599
}
96100

101+
public function testHTMLErrorRendererEscapesHttpException()
102+
{
103+
$exceptionTitle = '<script>alert(1)</script>';
104+
$exceptionDescription = "<script>alert('xss')</script>";
105+
106+
$httpExceptionProphecy = $this->prophesize(HttpException::class);
107+
108+
$httpExceptionProphecy
109+
->getTitle()
110+
->willReturn($exceptionTitle)
111+
->shouldBeCalledOnce();
112+
113+
$httpExceptionProphecy
114+
->getDescription()
115+
->willReturn($exceptionDescription)
116+
->shouldBeCalledOnce();
117+
118+
$renderer = new HtmlErrorRenderer();
119+
$output = $renderer->__invoke($httpExceptionProphecy->reveal(), false);
120+
121+
$this->assertStringNotContainsString($exceptionTitle, $output, 'Title must not be rendered unescaped');
122+
$this->assertStringContainsString(
123+
htmlspecialchars($exceptionTitle, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
124+
$output,
125+
'Title must be HTML-escaped'
126+
);
127+
128+
$this->assertStringNotContainsString(
129+
$exceptionDescription,
130+
$output,
131+
'Description must not be rendered unescaped'
132+
);
133+
$this->assertStringContainsString(
134+
htmlspecialchars($exceptionDescription, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
135+
$output,
136+
'Description must be HTML-escaped'
137+
);
138+
}
139+
97140
public function testJSONErrorRendererDisplaysErrorDetails()
98141
{
99142
$exception = new Exception('Oops..');

0 commit comments

Comments
 (0)