Package: release.debian.org Severity: normal Tags: bookworm X-Debbugs-Cc: [email protected] Control: affects -1 + src:php-league-commonmark User: [email protected] Usertags: pu
Hi, As agreed with the security team, I’d like to address the following three security issues via the next point release. * Fix XSS in AttributesExtension [CVE-2025-46734] * Fix DisallowedRawHtml bypass via newline/tab in tag names [CVE-2026-30838] * Fix DomainFilteringAdapter hostname boundary bypass [CVE-2026-33347] [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [x] the issue is verified as fixed in unstable Regards, taffit
diff -Nru php-league-commonmark-2.3.9/debian/changelog php-league-commonmark-2.3.9/debian/changelog --- php-league-commonmark-2.3.9/debian/changelog 2023-02-15 18:38:28.000000000 +0100 +++ php-league-commonmark-2.3.9/debian/changelog 2026-03-26 08:15:50.000000000 +0100 @@ -1,3 +1,12 @@ +php-league-commonmark (2.3.9-1+deb12u1) bookworm; urgency=medium + + * Track Bookworm branch + * Fix XSS in AttributesExtension [CVE-2025-46734] + * Fix DisallowedRawHtml bypass via newline/tab in tag names [CVE-2026-30838] + * Fix DomainFilteringAdapter hostname boundary bypass [CVE-2026-33347] + + -- David Prévot <[email protected]> Thu, 26 Mar 2026 08:15:50 +0100 + php-league-commonmark (2.3.9-1) unstable; urgency=medium [ Colin O'Dell ] diff -Nru php-league-commonmark-2.3.9/debian/control php-league-commonmark-2.3.9/debian/control --- php-league-commonmark-2.3.9/debian/control 2023-02-15 18:36:22.000000000 +0100 +++ php-league-commonmark-2.3.9/debian/control 2026-03-26 08:15:50.000000000 +0100 @@ -18,7 +18,7 @@ pkg-php-tools (>= 1.41~) Standards-Version: 4.6.2 Homepage: https://commonmark.thephpleague.com/ -Vcs-Git: https://salsa.debian.org/php-team/pear/php-league-commonmark.git -b debian/latest +Vcs-Git: https://salsa.debian.org/php-team/pear/php-league-commonmark.git -b debian/bookworm-security Vcs-Browser: https://salsa.debian.org/php-team/pear/php-league-commonmark Rules-Requires-Root: no diff -Nru php-league-commonmark-2.3.9/debian/gbp.conf php-league-commonmark-2.3.9/debian/gbp.conf --- php-league-commonmark-2.3.9/debian/gbp.conf 2022-01-25 19:18:49.000000000 +0100 +++ php-league-commonmark-2.3.9/debian/gbp.conf 2026-03-26 08:15:50.000000000 +0100 @@ -1,5 +1,5 @@ [DEFAULT] -debian-branch = debian/latest +debian-branch = debian/bookworm-security filter = [ '.gitattributes' ] pristine-tar = True upstream-vcs-tag = %(version%~%-)s diff -Nru php-league-commonmark-2.3.9/debian/patches/0002-Fix-XSS-in-AttributesExtension.patch php-league-commonmark-2.3.9/debian/patches/0002-Fix-XSS-in-AttributesExtension.patch --- php-league-commonmark-2.3.9/debian/patches/0002-Fix-XSS-in-AttributesExtension.patch 1970-01-01 01:00:00.000000000 +0100 +++ php-league-commonmark-2.3.9/debian/patches/0002-Fix-XSS-in-AttributesExtension.patch 2026-03-26 08:15:50.000000000 +0100 @@ -0,0 +1,296 @@ +From: Colin O'Dell <[email protected]> +Date: Mon, 5 May 2025 07:53:56 -0400 +Subject: Fix XSS in AttributesExtension + +Origin: backport, https://github.com/thephpleague/commonmark/commit/43207253ea5f14867c77c697cd3838c446cadcea +Bug: https://github.com/thephpleague/commonmark/security/advisories/GHSA-3527-qv2q-pfvx +Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2025-46734 +--- + src/Extension/Attributes/AttributesExtension.php | 18 +++++- + .../Attributes/Event/AttributesListener.php | 15 ++++- + src/Extension/Attributes/Util/AttributesHelper.php | 38 +++++++++++++ + .../Extension/Attributes/data/allowlist.html | 3 + + .../Extension/Attributes/data/allowlist.md | 12 ++++ + .../Attributes/data/js_event_attributes.html | 1 + + .../Attributes/data/js_event_attributes.md | 1 + + .../Attributes/data/unsafe_links_allowed.html | 1 + + .../Attributes/data/unsafe_links_allowed.md | 5 ++ + .../Attributes/data/unsafe_links_blocked.html | 1 + + .../Attributes/data/unsafe_links_blocked.md | 5 ++ + .../Attributes/Util/AttributesHelperTest.php | 66 ++++++++++++++++++++++ + 12 files changed, 162 insertions(+), 4 deletions(-) + create mode 100644 tests/functional/Extension/Attributes/data/allowlist.html + create mode 100644 tests/functional/Extension/Attributes/data/allowlist.md + create mode 100644 tests/functional/Extension/Attributes/data/js_event_attributes.html + create mode 100644 tests/functional/Extension/Attributes/data/js_event_attributes.md + create mode 100644 tests/functional/Extension/Attributes/data/unsafe_links_allowed.html + create mode 100644 tests/functional/Extension/Attributes/data/unsafe_links_allowed.md + create mode 100644 tests/functional/Extension/Attributes/data/unsafe_links_blocked.html + create mode 100644 tests/functional/Extension/Attributes/data/unsafe_links_blocked.md + +diff --git a/src/Extension/Attributes/AttributesExtension.php b/src/Extension/Attributes/AttributesExtension.php +index 2ef3d85..b29606d 100644 +--- a/src/Extension/Attributes/AttributesExtension.php ++++ b/src/Extension/Attributes/AttributesExtension.php +@@ -19,14 +19,26 @@ use League\CommonMark\Event\DocumentParsedEvent; + use League\CommonMark\Extension\Attributes\Event\AttributesListener; + use League\CommonMark\Extension\Attributes\Parser\AttributesBlockStartParser; + use League\CommonMark\Extension\Attributes\Parser\AttributesInlineParser; +-use League\CommonMark\Extension\ExtensionInterface; ++use League\CommonMark\Extension\ConfigurableExtensionInterface; ++use League\Config\ConfigurationBuilderInterface; ++use Nette\Schema\Expect; + +-final class AttributesExtension implements ExtensionInterface ++final class AttributesExtension implements ConfigurableExtensionInterface + { ++ public function configureSchema(ConfigurationBuilderInterface $builder): void ++ { ++ $builder->addSchema('attributes', Expect::structure([ ++ 'allow' => Expect::arrayOf('string')->default([]), ++ ])); ++ } ++ + public function register(EnvironmentBuilderInterface $environment): void + { ++ $allowList = $environment->getConfiguration()->get('attributes.allow'); ++ $allowUnsafeLinks = $environment->getConfiguration()->get('allow_unsafe_links'); ++ + $environment->addBlockStartParser(new AttributesBlockStartParser()); + $environment->addInlineParser(new AttributesInlineParser()); +- $environment->addEventListener(DocumentParsedEvent::class, [new AttributesListener(), 'processDocument']); ++ $environment->addEventListener(DocumentParsedEvent::class, [new AttributesListener($allowList, $allowUnsafeLinks), 'processDocument']); + } + } +diff --git a/src/Extension/Attributes/Event/AttributesListener.php b/src/Extension/Attributes/Event/AttributesListener.php +index feec8cc..fa068f4 100644 +--- a/src/Extension/Attributes/Event/AttributesListener.php ++++ b/src/Extension/Attributes/Event/AttributesListener.php +@@ -29,6 +29,19 @@ final class AttributesListener + private const DIRECTION_PREFIX = 'prefix'; + private const DIRECTION_SUFFIX = 'suffix'; + ++ /** @var list<string> */ ++ private array $allowList; ++ private bool $allowUnsafeLinks; ++ ++ /** ++ * @param list<string> $allowList ++ */ ++ public function __construct(array $allowList = [], bool $allowUnsafeLinks = true) ++ { ++ $this->allowList = $allowList; ++ $this->allowUnsafeLinks = $allowUnsafeLinks; ++ } ++ + public function processDocument(DocumentParsedEvent $event): void + { + foreach ($event->getDocument()->iterator() as $node) { +@@ -50,7 +63,7 @@ final class AttributesListener + $attributes = AttributesHelper::mergeAttributes($node->getAttributes(), $target); + } + +- $target->data->set('attributes', $attributes); ++ $target->data->set('attributes', AttributesHelper::filterAttributes($attributes, $this->allowList, $this->allowUnsafeLinks)); + } + + $node->detach(); +diff --git a/src/Extension/Attributes/Util/AttributesHelper.php b/src/Extension/Attributes/Util/AttributesHelper.php +index de5c111..33d3a0a 100644 +--- a/src/Extension/Attributes/Util/AttributesHelper.php ++++ b/src/Extension/Attributes/Util/AttributesHelper.php +@@ -134,4 +134,42 @@ final class AttributesHelper + + return $attributes; + } ++ ++ /** ++ * @param array<string, mixed> $attributes ++ * @param list<string> $allowList ++ * ++ * @return array<string, mixed> ++ */ ++ public static function filterAttributes(array $attributes, array $allowList, bool $allowUnsafeLinks): array ++ { ++ $allowList = \array_fill_keys($allowList, true); ++ ++ foreach ($attributes as $name => $value) { ++ $attrNameLower = \strtolower($name); ++ ++ // Remove any unsafe links ++ if (! $allowUnsafeLinks && ($attrNameLower === 'href' || $attrNameLower === 'src') && \is_string($value) && RegexHelper::isLinkPotentiallyUnsafe($value)) { ++ unset($attributes[$name]); ++ continue; ++ } ++ ++ // No allowlist? ++ if ($allowList === []) { ++ // Just remove JS event handlers ++ if (\str_starts_with($attrNameLower, 'on')) { ++ unset($attributes[$name]); ++ } ++ ++ continue; ++ } ++ ++ // Remove any attributes not in that allowlist (case-sensitive) ++ if (! isset($allowList[$name])) { ++ unset($attributes[$name]); ++ } ++ } ++ ++ return $attributes; ++ } + } +diff --git a/tests/functional/Extension/Attributes/data/allowlist.html b/tests/functional/Extension/Attributes/data/allowlist.html +new file mode 100644 +index 0000000..92ab9de +--- /dev/null ++++ b/tests/functional/Extension/Attributes/data/allowlist.html +@@ -0,0 +1,3 @@ ++<h2 id="header1">Header with attributes</h2> ++<p class="text">some text</p> ++<p><img align="left" src="/assets/image.jpg" alt="image" /></p> +diff --git a/tests/functional/Extension/Attributes/data/allowlist.md b/tests/functional/Extension/Attributes/data/allowlist.md +new file mode 100644 +index 0000000..996c647 +--- /dev/null ++++ b/tests/functional/Extension/Attributes/data/allowlist.md +@@ -0,0 +1,12 @@ ++--- ++attributes: ++ allow: [id, class, align] ++--- ++ ++Header with attributes {#header1} ++--------------------------------- ++ ++{class="text" hello="world"} ++some text ++ ++{align=left width=100px} +diff --git a/tests/functional/Extension/Attributes/data/js_event_attributes.html b/tests/functional/Extension/Attributes/data/js_event_attributes.html +new file mode 100644 +index 0000000..667c05e +--- /dev/null ++++ b/tests/functional/Extension/Attributes/data/js_event_attributes.html +@@ -0,0 +1 @@ ++<p><img class="blocked" src="" alt="this extension blocks js event attributes" /></p> +diff --git a/tests/functional/Extension/Attributes/data/js_event_attributes.md b/tests/functional/Extension/Attributes/data/js_event_attributes.md +new file mode 100644 +index 0000000..e0a0642 +--- /dev/null ++++ b/tests/functional/Extension/Attributes/data/js_event_attributes.md +@@ -0,0 +1 @@ ++![this extension blocks js event attributes](){onerror=alert(1) class=blocked} +diff --git a/tests/functional/Extension/Attributes/data/unsafe_links_allowed.html b/tests/functional/Extension/Attributes/data/unsafe_links_allowed.html +new file mode 100644 +index 0000000..3497e89 +--- /dev/null ++++ b/tests/functional/Extension/Attributes/data/unsafe_links_allowed.html +@@ -0,0 +1 @@ ++<p><a href="javascript:alert(1)">click me</a></p> +diff --git a/tests/functional/Extension/Attributes/data/unsafe_links_allowed.md b/tests/functional/Extension/Attributes/data/unsafe_links_allowed.md +new file mode 100644 +index 0000000..9a1d45e +--- /dev/null ++++ b/tests/functional/Extension/Attributes/data/unsafe_links_allowed.md +@@ -0,0 +1,5 @@ ++--- ++allow_unsafe_links: true ++--- ++ ++[click me](javascript:alert(1)){href="javascript:alert(1)"} +diff --git a/tests/functional/Extension/Attributes/data/unsafe_links_blocked.html b/tests/functional/Extension/Attributes/data/unsafe_links_blocked.html +new file mode 100644 +index 0000000..0205a6b +--- /dev/null ++++ b/tests/functional/Extension/Attributes/data/unsafe_links_blocked.html +@@ -0,0 +1 @@ ++<p><a>click me</a></p> +diff --git a/tests/functional/Extension/Attributes/data/unsafe_links_blocked.md b/tests/functional/Extension/Attributes/data/unsafe_links_blocked.md +new file mode 100644 +index 0000000..5c706a3 +--- /dev/null ++++ b/tests/functional/Extension/Attributes/data/unsafe_links_blocked.md +@@ -0,0 +1,5 @@ ++--- ++allow_unsafe_links: false ++--- ++ ++[click me](javascript:alert(1)){href="javascript:alert(1)"} +diff --git a/tests/unit/Extension/Attributes/Util/AttributesHelperTest.php b/tests/unit/Extension/Attributes/Util/AttributesHelperTest.php +index 17a4653..67767c5 100644 +--- a/tests/unit/Extension/Attributes/Util/AttributesHelperTest.php ++++ b/tests/unit/Extension/Attributes/Util/AttributesHelperTest.php +@@ -182,4 +182,70 @@ final class AttributesHelperTest extends TestCase + ['id' => 'block', 'class' => 'inline block'], + ]; + } ++ ++ /** ++ * @dataProvider dataForTestFilterAttributes ++ * ++ * @param array<string, mixed> $attributes ++ * @param list<string> $allowList ++ * @param array<string, mixed> $expected ++ */ ++ public function testFilterAttributes(array $attributes, array $allowList, bool $allowUnsafeLinks, array $expected): void ++ { ++ $this->assertEquals($expected, AttributesHelper::filterAttributes($attributes, $allowList, $allowUnsafeLinks)); ++ } ++ ++ /** ++ * @return iterable<array<mixed>> ++ */ ++ public static function dataForTestFilterAttributes(): iterable ++ { ++ // No allow list; unsafe links disallowed (default behavior) ++ yield [ ++ ['id' => 'foo', 'class' => 'bar', 'onclick' => 'alert("XSS")', 'href' => 'javascript:alert("XSS")'], ++ [], ++ false, ++ ['id' => 'foo', 'class' => 'bar'], ++ ]; ++ ++ // No allow list; unsafe links allowed ++ yield [ ++ ['id' => 'foo', 'class' => 'bar', 'onclick' => 'alert("XSS")', 'href' => 'javascript:alert("XSS")'], ++ [], ++ true, ++ ['id' => 'foo', 'class' => 'bar', 'href' => 'javascript:alert("XSS")'], ++ ]; ++ ++ // Allow list; unsafe links disallowed ++ yield [ ++ ['id' => 'foo', 'class' => 'bar', 'onclick' => 'alert("XSS")', 'href' => 'javascript:alert("XSS")'], ++ ['id', 'onclick', 'href'], ++ false, ++ ['id' => 'foo', 'onclick' => 'alert("XSS")'], ++ ]; ++ ++ // Allow list; unsafe links allowed ++ yield [ ++ ['id' => 'foo', 'class' => 'bar', 'onclick' => 'alert("XSS")', 'href' => 'javascript:alert("XSS")'], ++ ['id', 'onclick', 'href'], ++ true, ++ ['id' => 'foo', 'onclick' => 'alert("XSS")', 'href' => 'javascript:alert("XSS")'], ++ ]; ++ ++ // Allow list blocks all ++ yield [ ++ ['id' => 'foo', 'class' => '<script>alert("XSS")</script>'], ++ ['style'], ++ false, ++ [], ++ ]; ++ ++ // Can't use weird casing to bypass allowlist or 'on*' restriction ++ yield [ ++ ['ID' => 'foo', 'oNcLiCk' => 'alert("XSS")'], ++ ['id', 'class'], ++ false, ++ [], ++ ]; ++ } + } diff -Nru php-league-commonmark-2.3.9/debian/patches/0003-Add-regression-test.patch php-league-commonmark-2.3.9/debian/patches/0003-Add-regression-test.patch --- php-league-commonmark-2.3.9/debian/patches/0003-Add-regression-test.patch 1970-01-01 01:00:00.000000000 +0100 +++ php-league-commonmark-2.3.9/debian/patches/0003-Add-regression-test.patch 2026-03-26 08:15:50.000000000 +0100 @@ -0,0 +1,42 @@ +From: Colin O'Dell <[email protected]> +Date: Thu, 5 Mar 2026 07:22:58 -0500 +Subject: Add regression test + +Origin: upstream, https://github.com/thephpleague/commonmark/commit/f6e74434dd1a91f195f80cb0184b746a4187272a +--- + .../DisallowedRawHtml/DisallowedRawHtmlRendererTest.php | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/tests/unit/Extension/DisallowedRawHtml/DisallowedRawHtmlRendererTest.php b/tests/unit/Extension/DisallowedRawHtml/DisallowedRawHtmlRendererTest.php +index 968d780..af25950 100644 +--- a/tests/unit/Extension/DisallowedRawHtml/DisallowedRawHtmlRendererTest.php ++++ b/tests/unit/Extension/DisallowedRawHtml/DisallowedRawHtmlRendererTest.php +@@ -71,6 +71,16 @@ final class DisallowedRawHtmlRendererTest extends TestCase + yield ['<script>', '<script>']; + yield ['<plaintext>', '<plaintext>']; + ++ // Newline/whitespace bypass attempts (security fix) ++ yield ["<script >", "<script >"]; ++ yield ["<script\n>", "<script\n>"]; ++ yield ["<script\t>", "<script\t>"]; ++ yield ["<script\r\n>", "<script\r\n>"]; ++ yield ["<iframe\nwidth=\"560\">", "<iframe\nwidth=\"560\">"]; ++ ++ // Ensure non-disallowed tags with similar names are NOT filtered ++ yield ['<scriptfoo>', '<scriptfoo>']; ++ + // Tags not escaped by default + yield ['<strong>', '<strong>']; + } +@@ -107,6 +117,11 @@ final class DisallowedRawHtmlRendererTest extends TestCase + yield ['<strong/>', '<strong/>']; + yield ['<strong />', '<strong />']; + ++ // Newline bypass with custom config ++ yield ["<strong >", "<strong >"]; ++ yield ["<strong\n>", "<strong\n>"]; ++ yield ["<strong\t>", "<strong\t>"]; ++ + // Defaults that I didn't include in my custom config + yield ['<title>', '<title>']; + yield ['<textarea>', '<textarea>']; diff -Nru php-league-commonmark-2.3.9/debian/patches/0004-Fix-DisallowedRawHtml-bypass-via-newline-tab-in-tag-.patch php-league-commonmark-2.3.9/debian/patches/0004-Fix-DisallowedRawHtml-bypass-via-newline-tab-in-tag-.patch --- php-league-commonmark-2.3.9/debian/patches/0004-Fix-DisallowedRawHtml-bypass-via-newline-tab-in-tag-.patch 1970-01-01 01:00:00.000000000 +0100 +++ php-league-commonmark-2.3.9/debian/patches/0004-Fix-DisallowedRawHtml-bypass-via-newline-tab-in-tag-.patch 2026-03-26 08:15:50.000000000 +0100 @@ -0,0 +1,47 @@ +From: Colin O'Dell <[email protected]> +Date: Thu, 5 Mar 2026 07:43:00 -0500 +Subject: Fix DisallowedRawHtml bypass via newline/tab in tag names + +Origin: upstream, https://github.com/thephpleague/commonmark/commit/5c0c4c8fe5a31e8260be99e0afad7136a27c79e6 +Bug: https://github.com/thephpleague/commonmark/security/advisories/GHSA-4v6x-c7xx-hw9f +Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2026-30838 +--- + src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php | 2 +- + .../Extension/DisallowedRawHtml/DisallowedRawHtmlRendererTest.php | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php b/src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php +index 06252a3..2bcb89a 100644 +--- a/src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php ++++ b/src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php +@@ -45,7 +45,7 @@ final class DisallowedRawHtmlRenderer implements NodeRendererInterface, Configur + return $rendered; + } + +- $regex = \sprintf('/<(\/?(?:%s)[ \/>])/i', \implode('|', \array_map('preg_quote', $tags))); ++ $regex = \sprintf('/<(\/?(?:%s)[\s\/>])/i', \implode('|', \array_map('preg_quote', $tags))); + + // Match these types of tags: <title> </title> <title x="sdf"> <title/> <title /> + return \preg_replace($regex, '<$1', $rendered); +diff --git a/tests/unit/Extension/DisallowedRawHtml/DisallowedRawHtmlRendererTest.php b/tests/unit/Extension/DisallowedRawHtml/DisallowedRawHtmlRendererTest.php +index af25950..f8866e3 100644 +--- a/tests/unit/Extension/DisallowedRawHtml/DisallowedRawHtmlRendererTest.php ++++ b/tests/unit/Extension/DisallowedRawHtml/DisallowedRawHtmlRendererTest.php +@@ -72,7 +72,7 @@ final class DisallowedRawHtmlRendererTest extends TestCase + yield ['<plaintext>', '<plaintext>']; + + // Newline/whitespace bypass attempts (security fix) +- yield ["<script >", "<script >"]; ++ yield ['<script >', '<script >']; + yield ["<script\n>", "<script\n>"]; + yield ["<script\t>", "<script\t>"]; + yield ["<script\r\n>", "<script\r\n>"]; +@@ -118,7 +118,7 @@ final class DisallowedRawHtmlRendererTest extends TestCase + yield ['<strong />', '<strong />']; + + // Newline bypass with custom config +- yield ["<strong >", "<strong >"]; ++ yield ['<strong >', '<strong >']; + yield ["<strong\n>", "<strong\n>"]; + yield ["<strong\t>", "<strong\t>"]; + diff -Nru php-league-commonmark-2.3.9/debian/patches/0005-Fix-DomainFilteringAdapter-hostname-boundary-bypass.patch php-league-commonmark-2.3.9/debian/patches/0005-Fix-DomainFilteringAdapter-hostname-boundary-bypass.patch --- php-league-commonmark-2.3.9/debian/patches/0005-Fix-DomainFilteringAdapter-hostname-boundary-bypass.patch 1970-01-01 01:00:00.000000000 +0100 +++ php-league-commonmark-2.3.9/debian/patches/0005-Fix-DomainFilteringAdapter-hostname-boundary-bypass.patch 2026-03-26 08:15:50.000000000 +0100 @@ -0,0 +1,106 @@ +From: Colin O'Dell <[email protected]> +Date: Thu, 19 Mar 2026 09:01:30 -0400 +Subject: Fix DomainFilteringAdapter hostname boundary bypass + +Origin: backport, https://github.com/thephpleague/commonmark/commit/59fb075d2101740c337c7216e3f32b36c204218b +Bug: https://github.com/thephpleague/commonmark/security/advisories/GHSA-hh8v-hgvp-g3f5 +Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2026-33347 +--- + src/Extension/Embed/DomainFilteringAdapter.php | 38 ++++++++++++++-------- + .../Extension/Embed/DomainFilteringAdapterTest.php | 18 ++++++++-- + 2 files changed, 41 insertions(+), 15 deletions(-) + +diff --git a/src/Extension/Embed/DomainFilteringAdapter.php b/src/Extension/Embed/DomainFilteringAdapter.php +index 69dc096..b00b028 100644 +--- a/src/Extension/Embed/DomainFilteringAdapter.php ++++ b/src/Extension/Embed/DomainFilteringAdapter.php +@@ -17,15 +17,16 @@ class DomainFilteringAdapter implements EmbedAdapterInterface + { + private EmbedAdapterInterface $decorated; + +- private string $regex; ++ /** @var string[] */ ++ private array $allowedDomains; + + /** + * @param string[] $allowedDomains + */ + public function __construct(EmbedAdapterInterface $decorated, array $allowedDomains) + { +- $this->decorated = $decorated; +- $this->regex = self::createRegex($allowedDomains); ++ $this->decorated = $decorated; ++ $this->allowedDomains = \array_map('strtolower', $allowedDomains); + } + + /** +@@ -33,18 +34,29 @@ class DomainFilteringAdapter implements EmbedAdapterInterface + */ + public function updateEmbeds(array $embeds): void + { +- $this->decorated->updateEmbeds(\array_values(\array_filter($embeds, function (Embed $embed): bool { +- return \preg_match($this->regex, $embed->getUrl()) === 1; +- }))); ++ $this->decorated->updateEmbeds(\array_values(\array_filter($embeds, [$this, 'isAllowed']))); + } + +- /** +- * @param string[] $allowedDomains +- */ +- private static function createRegex(array $allowedDomains): string ++ private function isAllowed(Embed $embed): bool + { +- $allowedDomains = \array_map('preg_quote', $allowedDomains); +- +- return '/^(?:https?:\/\/)?(?:[^.]+\.)*(' . \implode('|', $allowedDomains) . ')/'; ++ $url = $embed->getUrl(); ++ $scheme = \parse_url($url, \PHP_URL_SCHEME); ++ if ($scheme === null || $scheme === false) { ++ // Bare domain (no scheme) - assume https:// so parse_url can extract the host ++ $url = 'https://' . $url; ++ } elseif (\strtolower($scheme) !== 'http' && \strtolower($scheme) !== 'https') { ++ return false; ++ } ++ ++ $host = \parse_url($url, \PHP_URL_HOST); ++ $host = \strtolower(\rtrim((string) $host, '.')); ++ ++ foreach ($this->allowedDomains as $domain) { ++ if ($host === $domain || \str_ends_with($host, '.' . $domain)) { ++ return true; ++ } ++ } ++ ++ return false; + } + } +diff --git a/tests/unit/Extension/Embed/DomainFilteringAdapterTest.php b/tests/unit/Extension/Embed/DomainFilteringAdapterTest.php +index 436e398..d320eb7 100644 +--- a/tests/unit/Extension/Embed/DomainFilteringAdapterTest.php ++++ b/tests/unit/Extension/Embed/DomainFilteringAdapterTest.php +@@ -28,9 +28,23 @@ final class DomainFilteringAdapterTest extends TestCase + $embed2 = new Embed('foo.example.com'), + new Embed('www.bar.com'), + new Embed('badexample.com'), +- $embed3 = new Embed('http://foo.bar.com'), +- $embed4 = new Embed('https://foo.bar.com/baz'), ++ $embed3 = new Embed('HTTP://foo.bar.com'), ++ $embed4 = new Embed('hTtPs://foo.bar.com/baz'), + new Embed('https://bar.com'), ++ new Embed('https://example.com.evil'), ++ new Embed('https://example.com.evil/path'), ++ new Embed('https://foo.bar.com.evil'), ++ new Embed('example.com.evil'), ++ new Embed('example.com.evil/path'), ++ new Embed('foo.bar.com.evil'), ++ new Embed('https://[email protected]'), ++ new Embed('https://user:[email protected]'), ++ new Embed('https://example.com:[email protected]/path'), ++ new Embed('javascript:alert(1)'), ++ new Embed('ftp://example.com'), ++ new Embed('file:///etc/passwd'), ++ new Embed('data:text/html,<script>alert(1)</script>'), ++ new Embed('//example.com/path'), + ]; + + $inner = $this->createMock(EmbedAdapterInterface::class); diff -Nru php-league-commonmark-2.3.9/debian/patches/series php-league-commonmark-2.3.9/debian/patches/series --- php-league-commonmark-2.3.9/debian/patches/series 2023-02-15 18:36:26.000000000 +0100 +++ php-league-commonmark-2.3.9/debian/patches/series 2026-03-26 08:15:50.000000000 +0100 @@ -1 +1,5 @@ 0001-Skip-tests-relying-on-packages-unavailable-in-Debian.patch +0002-Fix-XSS-in-AttributesExtension.patch +0003-Add-regression-test.patch +0004-Fix-DisallowedRawHtml-bypass-via-newline-tab-in-tag-.patch +0005-Fix-DomainFilteringAdapter-hostname-boundary-bypass.patch
signature.asc
Description: PGP signature

