diff --git a/src/Whoops/Util/TemplateHelper.php b/src/Whoops/Util/TemplateHelper.php
index 5612c0b..b77b160 100644
--- a/src/Whoops/Util/TemplateHelper.php
+++ b/src/Whoops/Util/TemplateHelper.php
@@ -80,6 +80,9 @@ class TemplateHelper
* Escapes a string for output in an HTML document, but preserves
* URIs within it, and converts them to clickable anchor elements.
*
+ * Only http and https URIs are linkified; other schemes are left as
+ * plain escaped text so that a link cannot execute script when clicked.
+ *
* @param string $raw
* @return string
*/
@@ -87,7 +90,7 @@ class TemplateHelper
{
$escaped = $this->escape($raw);
return preg_replace(
- "@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@",
+ "@(https?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@i",
"$1",
$escaped
);
diff --git a/tests/Whoops/Util/TemplateHelperTest.php b/tests/Whoops/Util/TemplateHelperTest.php
index 086645a..100b463 100644
--- a/tests/Whoops/Util/TemplateHelperTest.php
+++ b/tests/Whoops/Util/TemplateHelperTest.php
@@ -63,6 +63,44 @@ class TemplateHelperTest extends TestCase
);
}
+ /**
+ * @covers Whoops\Util\TemplateHelper::escapeButPreserveUris
+ */
+ public function testEscapeButPreserveUrisRejectsDangerousSchemes()
+ {
+ $dangerous = [
+ 'javascript://x.co/?%0Aalert(1)',
+ 'vbscript://x.co/?%0Amsgbox(1)',
+ 'data://text/html,',
+ ];
+
+ foreach ($dangerous as $payload) {
+ $output = $this->helper->escapeButPreserveUris($payload);
+
+ $this->assertStringNotContainsString(
+ 'assertEquals(
+ "http://google.com",
+ $this->helper->escapeButPreserveUris('http://google.com')
+ );
+
+ $this->assertEquals(
+ "https://google.com",
+ $this->helper->escapeButPreserveUris('https://google.com')
+ );
+ }
+
/**
* @covers Whoops\Util\TemplateHelper::breakOnDelimiter
*/