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
++
++![image](/assets/image.jpg){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>', '&lt;script>'];
+         yield ['<plaintext>', '&lt;plaintext>'];
+ 
++        // Newline/whitespace bypass attempts (security fix)
++        yield ["<script   >", "&lt;script   >"];
++        yield ["<script\n>", "&lt;script\n>"];
++        yield ["<script\t>", "&lt;script\t>"];
++        yield ["<script\r\n>", "&lt;script\r\n>"];
++        yield ["<iframe\nwidth=\"560\">", "&lt;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/>', '&lt;strong/>'];
+         yield ['<strong />', '&lt;strong />'];
+ 
++        // Newline bypass with custom config
++        yield ["<strong   >", "&lt;strong   >"];
++        yield ["<strong\n>", "&lt;strong\n>"];
++        yield ["<strong\t>", "&lt;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, '&lt;$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>', '&lt;plaintext>'];
+ 
+         // Newline/whitespace bypass attempts (security fix)
+-        yield ["<script   >", "&lt;script   >"];
++        yield ['<script   >', '&lt;script   >'];
+         yield ["<script\n>", "&lt;script\n>"];
+         yield ["<script\t>", "&lt;script\t>"];
+         yield ["<script\r\n>", "&lt;script\r\n>"];
+@@ -118,7 +118,7 @@ final class DisallowedRawHtmlRendererTest extends TestCase
+         yield ['<strong />', '&lt;strong />'];
+ 
+         // Newline bypass with custom config
+-        yield ["<strong   >", "&lt;strong   >"];
++        yield ['<strong   >', '&lt;strong   >'];
+         yield ["<strong\n>", "&lt;strong\n>"];
+         yield ["<strong\t>", "&lt;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

Attachment: signature.asc
Description: PGP signature

Reply via email to