Control: tags -1 + patch
Hi Release Team,
Update / ping on this issue: trixie is still vulnerable to all nine
CVEs, and the upload has not been made. Attached is an updated debdiff
against 3.10.2-1+deb13u1 (current version in trixie-proposed-updates).
The maintainer, Xavier Guimard, has reviewed and merged this into the
packaging repository:
https://salsa.debian.org/debian/cyrus-imapd/-/merge_requests/14
Changes since the debdiff Bastian posted on 2026-07-28: DEP-3 metadata
on all nine patches, and the upstream Cassandane regression tests that
the original omitted (it carried tests for only three of the nine
fixes).
* Risks
Low. All nine are upstream fixes cherry-picked from 3.10.3, each adding
a single missing ACL check (or a bounds check, for CVE-2026-47088). No
packaging, ABI or configuration changes. Diff is +1065/-0, confined to
debian/changelog, debian/patches/series and nine new patches.
* Tests
Built in a clean trixie chroot (sbuild) on amd64. No runtime testing was
done: Each fix ships its upstream Cassandane regression test, but those
are not exercised yet, because the autopkgtest pulls the archived
upstream cassandane repo rather than the in-tree suite (#1145447, being
fixed separately).
Fixed in unstable since 3.12.3-4.
Thank you,
Edmund
--
Edmund Lodewijks <[email protected]>
TZ: UTC+2 / GMT+2diff -Nru cyrus-imapd-3.10.2/debian/changelog
cyrus-imapd-3.10.2/debian/changelog
--- cyrus-imapd-3.10.2/debian/changelog 2026-07-12 21:29:05.000000000 +0200
+++ cyrus-imapd-3.10.2/debian/changelog 2026-08-02 12:00:00.000000000 +0200
@@ -1,3 +1,31 @@
+cyrus-imapd (3.10.2-1+deb13u2) trixie; urgency=high
+
+ * Backport security fixes from upstream 3.10.3 (Closes: #1142925)
+ (https://www.cyrusimap.org/imap/download/release-notes/3.10/x/3.10.3.html):
+ - CVE-2026-47084: LOCALDELETE bypassed ACL checks, allowing non-admin
+ users to delete mailboxes without permission.
+ - CVE-2026-47086: GENURLAUTH issued URLAUTH tokens without checking
+ ACL_READ on the target mailbox.
+ - CVE-2026-47087: URLAUTH tokens kept working after the authorizer's
+ access was revoked.
+ - CVE-2026-47081: XAPPLEPUSHSERVICE allowed probing for mailbox
+ existence and hijacking push notifications on other users' folders.
+ - CVE-2026-47089: LISTRIGHTS was not restricted to users with admin
+ access on the target mailbox.
+ - CVE-2026-47085: URLAUTH tokens could be forged via a predictable
+ empty mboxkey.
+ - CVE-2026-47083: MULTISEARCH/ESEARCH allowed a cross-user folder and
+ content enumeration oracle.
+ - CVE-2026-47088: heap out-of-bounds read when parsing nested MIME
+ comments in RFC 822 headers.
+ - CVE-2026-47082: vacation "fcc" delivery skipped the ACL check on the
+ destination mailbox.
+ * This revision adds DEP-3 metadata to the new patches and restores
+ upstream Cassandane regression tests that were missing from the initial
+ debdiff (thanks, Codin!) for several of the fixes.
+
+ -- Edmund Lodewijks <[email protected]> Sun, 02 Aug 2026 12:00:00 +0200
+
cyrus-imapd (3.10.2-1+deb13u1) trixie; urgency=medium
* http_jmap: allow JMAP EventSource without WebSocket/wslay; backport
diff -Nru
cyrus-imapd-3.10.2/debian/patches/0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch
cyrus-imapd-3.10.2/debian/patches/0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch
---
cyrus-imapd-3.10.2/debian/patches/0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch
1970-01-01 02:00:00.000000000 +0200
+++
cyrus-imapd-3.10.2/debian/patches/0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch
2026-08-02 12:00:00.000000000 +0200
@@ -0,0 +1,101 @@
+Description: lmtp_sieve.c: enforce ACL on vacation :fcc destination
(CVE-2026-47082)
+ An initial debdiff for this CVE batch was submitted by Codin
+ <[email protected]> and sponsored/reviewed by Bastian Germann
+ <[email protected]> in Debian bug #1142925. This revision adds DEP-3
+ metadata and, where missing from that debdiff, folds in the matching
+ upstream Cassandane regression test(s).
+ .
+ This is CYR-2892, CVE-2026-47082.
+ .
+ Enforce *some* ACL check when delivering the fcc for a vacation message!
+ Rather than pass aclcheck=0, pass ACL_INSERT: the script owner must be
+ able to write to the target mailbox.
+ .
+ This problem was reported by Michael Lynch (mtlynch.io).
+ .
+ Co-authored-by: Claude <[email protected]>
+Author: Ricardo Signes <[email protected]>
+Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/db6d646d00
+Bug: https://security-tracker.debian.org/tracker/CVE-2026-47082
+Bug-Debian: https://bugs.debian.org/1142925
+Bug-CVE: CVE-2026-47082
+Forwarded: not-needed
+Last-Update: 2026-07-25
+
+diff --git a/cassandane/tiny-tests/Sieve/vacation_with_fcc_otheruser_denied
b/cassandane/tiny-tests/Sieve/vacation_with_fcc_otheruser_denied
+new file mode 100644
+index 0000000..ed9ff9a
+--- /dev/null
++++ b/cassandane/tiny-tests/Sieve/vacation_with_fcc_otheruser_denied
+@@ -0,0 +1,58 @@
++#!perl
++use Cassandane::Tiny;
++
++sub test_vacation_with_fcc_otheruser_denied
++ :min_version_3_1
++ :NoAltNameSpace
++ :want_smtpdaemon
++{
++ my ($self) = @_;
++
++ my $target = "user.other.target";
++
++ xlog $self, "Create victim user 'other' and a target folder";
++ $self->{instance}->create_user('other');
++
++ my $admintalk = $self->{adminstore}->get_client();
++ $admintalk->create($target)
++ or die "Cannot create $target: $@";
++
++ xlog $self, "Give cassandane read-only access (no 'i'/'p' rights)";
++ $admintalk->setacl($target, 'cassandane' => 'lrs');
++ $self->assert_str_equals('ok',
++ $admintalk->get_last_completion_response());
++
++ xlog $self, "Confirm cassandane cannot APPEND directly to $target";
++ my $talk = $self->{store}->get_client();
++ $talk->append($target,
++ "From: nobody\@example.com\r\n"
++ . "Subject: direct-append\r\n\r\n"
++ . "denied\r\n");
++ $self->assert_str_equals('no',
++ $talk->get_last_completion_response());
++
++ xlog $self, "Install a vacation rule that :fcc's into the victim's
folder";
++ $self->{instance}->install_sieve_script(<<EOF
++require ["vacation", "fcc"];
++
++vacation :fcc "$target" :days 1 :addresses ["cassandane\@example.com"] text:
++I am out of the office today. I will answer your email as soon as I can.
++.
++;
++EOF
++ );
++
++ xlog $self, "Deliver a message to trigger the vacation auto-reply";
++ my $msg1 = $self->{gen}->generate(
++ subject => "Message 1",
++ to => Cassandane::Address->new(localpart => 'cassandane',
++ domain => 'example.com'),
++ );
++ $self->{instance}->deliver($msg1);
++
++ xlog $self, "Check that nothing was filed into the victim's folder";
++ $admintalk->select($target);
++ $self->assert_str_equals('ok',
++ $admintalk->get_last_completion_response());
++ $self->assert_num_equals(0, $admintalk->get_response_code('exists'));
++}
+diff --git a/imap/lmtp_sieve.c b/imap/lmtp_sieve.c
+index f13a266..b85bf1a 100644
+--- a/imap/lmtp_sieve.c
++++ b/imap/lmtp_sieve.c
+@@ -1931,7 +1931,7 @@ static void do_fcc(script_data_t *sdata,
sieve_fileinto_context_t *fcc,
+ }
+ if (!r) {
+ r = append_setup(&as, intname, userid, sdata->authstate,
+- 0, NULL, NULL, 0, EVENT_MESSAGE_APPEND);
++ ACL_INSERT, NULL, NULL, 0, EVENT_MESSAGE_APPEND);
+ }
+ if (!r) {
+ struct stagemsg *stage;
diff -Nru
cyrus-imapd-3.10.2/debian/patches/0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch
cyrus-imapd-3.10.2/debian/patches/0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch
---
cyrus-imapd-3.10.2/debian/patches/0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch
1970-01-01 02:00:00.000000000 +0200
+++
cyrus-imapd-3.10.2/debian/patches/0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch
2026-08-02 12:00:00.000000000 +0200
@@ -0,0 +1,145 @@
+Description: imap/message.c: fix heap exposure in nested MIME comment parsing
(CVE-2026-47088)
+ An initial debdiff for this CVE batch was submitted by Codin
+ <[email protected]> and sponsored/reviewed by Bastian Germann
+ <[email protected]> in Debian bug #1142925. This revision adds DEP-3
+ metadata and, where missing from that debdiff, folds in the matching
+ upstream Cassandane regression test(s).
+ .
+ This is CYR-2882, CVE-2026-47088.
+ .
+ Fix out-of-bounds read in message_parse_rfc822space() when parsing RFC
+ 822 comments that end with backslash. The backslash case would increment
+ p past a NUL terminator, and the subsequent loop increment would read
+ into adjacent heap memory.
+ .
+ Add bounds check after backslash handling to prevent reading past the
+ end of the buffer.
+ .
+ This problem was reported by Michael Lynch (mtlynch.io).
+ .
+ Co-Authored-By: Claude <[email protected]>
+Author: Ricardo Signes <[email protected]>
+Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/23495997b5
+Bug: https://security-tracker.debian.org/tracker/CVE-2026-47088
+Bug-Debian: https://bugs.debian.org/1142925
+Bug-CVE: CVE-2026-47088
+Forwarded: not-needed
+Last-Update: 2026-07-25
+
+diff --git a/cassandane/Cassandane/Cyrus/MIME.pm
b/cassandane/Cassandane/Cyrus/MIME.pm
+new file mode 100644
+index 0000000..58df5a2
+--- /dev/null
++++ b/cassandane/Cassandane/Cyrus/MIME.pm
+@@ -0,0 +1,40 @@
++# SPDX-License-Identifier: BSD-3-Clause-CMU
++# See COPYING file at the root of the distribution for more details.
++
++package Cassandane::Cyrus::MIME;
++use strict;
++use warnings;
++use Data::Dumper;
++
++use base qw(Cassandane::Cyrus::TestCase);
++use Cassandane::Util::Log;
++
++sub new
++{
++ my ($class, @args) = @_;
++
++ my $config = Cassandane::Config->default()->clone();
++
++ my $self = $class->SUPER::new({
++ config => $config,
++ services => [ 'imap' ]
++ }, @args);
++
++ return $self;
++}
++
++sub set_up
++{
++ my ($self) = @_;
++ $self->SUPER::set_up();
++}
++
++sub tear_down
++{
++ my ($self) = @_;
++ $self->SUPER::tear_down();
++}
++
++use Cassandane::Tiny::Loader 'tiny-tests/MIME';
++
++1;
+diff --git a/cassandane/tiny-tests/MIME/nested_comment_oob
b/cassandane/tiny-tests/MIME/nested_comment_oob
+new file mode 100644
+index 0000000..b47f6b1
+--- /dev/null
++++ b/cassandane/tiny-tests/MIME/nested_comment_oob
+@@ -0,0 +1,50 @@
++#!perl
++use Cassandane::Tiny;
++
++sub test_nested_comment_oob
++{
++ my ($self) = @_;
++
++ my $talk = $self->{store}->get_client();
++
++ # Create a multipart message where the vulnerability is in a nested part.
++ # Based on the PoC description, this reproduces the backslash handling bug
++ # in message_parse_rfc822space() when parsing nested Content-Type headers.
++ # The malformed header must be the last content to ensure backslash is
final byte.
++ my $msg = <<'EOF';
++From: [email protected]
++To: [email protected]
++Subject: Test heap disclosure
++Content-Type: multipart/mixed; boundary="BOUNDARY"
++
++--BOUNDARY
++Content-Type: text/plain
++
++This is a normal part.
++
++--BOUNDARY
++Content-Type: text/plain (\
++EOF
++
++ # Remove trailing newline so backslash is the final byte before NUL
++ chomp $msg;
++ $msg =~ s/\r?\n/\r\n/gs;
++
++ # Append the message
++ $talk->append('INBOX', $msg) || die $@;
++
++ # Select the mailbox
++ $talk->select('INBOX');
++ $self->assert_str_equals('ok', $talk->get_last_completion_response());
++
++ # Fetch BODYSTRUCTURE - this exercises the vulnerable MIME parsing code
++ # In vulnerable versions, this may crash or leak heap data as MIME
parameters
++ my $response = $talk->fetch('1', 'BODYSTRUCTURE');
++ $self->assert_str_equals('ok', $talk->get_last_completion_response());
++
++ # Log the bodystructure for manual inspection of any anomalies
++ # In a release build with heap spray, unexpected parameters may appear
++ if ($response && $response->{1} && $response->{1}->{bodystructure}) {
++ xlog $self, "BODYSTRUCTURE: " .
Data::Dumper::Dumper($response->{1}->{bodystructure});
++ }
++}
+diff --git a/imap/message.c b/imap/message.c
+index a5b633e..f18b1c6 100644
+--- a/imap/message.c
++++ b/imap/message.c
+@@ -1768,6 +1768,10 @@ static void message_parse_rfc822space(const char **s)
+
+ case '\\':
+ p++;
++ if (*p == '\0') {
++ *s = 0; /* backslash at end of string */
++ return;
++ }
+ break;
+
+ case '(':
diff -Nru
cyrus-imapd-3.10.2/debian/patches/0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch
cyrus-imapd-3.10.2/debian/patches/0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch
---
cyrus-imapd-3.10.2/debian/patches/0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch
1970-01-01 02:00:00.000000000 +0200
+++
cyrus-imapd-3.10.2/debian/patches/0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch
2026-08-02 12:00:00.000000000 +0200
@@ -0,0 +1,96 @@
+Description: imapd: MULTISEARCH must check ACL_READ on each mailbox
(CVE-2026-47083)
+ An initial debdiff for this CVE batch was submitted by Codin
+ <[email protected]> and sponsored/reviewed by Bastian Germann
+ <[email protected]> in Debian bug #1142925. This revision adds DEP-3
+ metadata and, where missing from that debdiff, folds in the matching
+ upstream Cassandane regression test(s).
+ .
+ This is CYR-2881, CVE-2026-47083.
+ .
+ multisearch_cb opened caller-supplied mailboxes from
+ MAILBOXES/SUBTREE/SUBTREE-ONE and ran index_search on them without
+ checking whether the caller could read the mailbox. SELECT has the
+ equivalent gate (imapd.c:5001); MULTISEARCH did not.
+ .
+ The effect is a cross-user read oracle: SUBTREE on another user's
+ account returns one `* ESEARCH ... MAILBOX "..."` line per folder
+ (folder enumeration for free), and BODY/SUBJECT predicates then probe
+ the contents one search at a time. An attacker only needs to be able
+ to name a target mailbox.
+ .
+ This problem was reported by Michael Lynch (mtlynch.io).
+ .
+ Co-Authored-By: Claude <[email protected]>
+Author: Ricardo Signes <[email protected]>
+Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/0534c4e30a
+Bug: https://security-tracker.debian.org/tracker/CVE-2026-47083
+Bug-Debian: https://bugs.debian.org/1142925
+Bug-CVE: CVE-2026-47083
+Forwarded: not-needed
+Last-Update: 2026-07-25
+
+diff --git a/cassandane/Cassandane/Cyrus/Search.pm
b/cassandane/Cassandane/Cyrus/Search.pm
+index 19e16fa..d9e5e9e 100644
+--- a/cassandane/Cassandane/Cyrus/Search.pm
++++ b/cassandane/Cassandane/Cyrus/Search.pm
+@@ -757,4 +757,44 @@ sub test_uidsearch_empty
+ $self->assert_str_equals('0', $results[0][3]);
+ }
+
++sub test_multisearch_cross_user
++ :NoAltNamespace
++{
++ my ($self) = @_;
++
++ my $admintalk = $self->{adminstore}->get_client;
++ my $talk = $self->{store}->get_client;
++
++ # victim has a private subfolder with a message. cassandane has no
++ # explicit grant on it, only whatever the "anyone p" default provides --
++ # i.e. ACL_POST, which is not enough to run a search.
++ $self->{instance}->create_user('victim');
++
++ $admintalk->create('user.victim.private')
++ || die "create: " . $admintalk->get_last_error;
++
++ $admintalk->append(
++ 'user.victim.private',
++ "From: t\@example.com\r\nSubject: secret\r\n\r\nconfidential body",
++ ) || die "append: " . $admintalk->get_last_error;
++
++ # Check our assumptions: default user has no access to victim's private
++ # folder.
++ $talk->select('user.victim.private');
++ $self->assert_str_equals('no', $talk->get_last_completion_response);
++
++ # The actual test: assert that cassandane user can't find anything by
++ # ESEARCHing victim's private mailbox.
++ my @esearch_responses;
++ $talk->_imap_cmd(
++ 'ESEARCH', 0,
++ { esearch => sub { push @esearch_responses, $_[1] } },
++ 'IN', '(MAILBOXES "user.victim.private")',
++ 'RETURN', '(ALL)', 'ALL',
++ );
++
++ $self->assert_str_equals('ok', $talk->get_last_completion_response);
++ $self->assert_deep_equals([], \@esearch_responses);
++}
++
+ 1;
+diff --git a/imap/imapd.c b/imap/imapd.c
+index f7be33e..4edd0bf 100644
+--- a/imap/imapd.c
++++ b/imap/imapd.c
+@@ -6076,6 +6076,11 @@ static int multisearch_cb(const mbentry_t *mbentry,
void *rock)
+ hash_lookup(mbentry->name, &mrock->mailboxes))
+ return 0;
+
++ /* Skip mailboxes the caller can't read. */
++ if (!imapd_userisadmin &&
++ !(cyrus_acl_myrights(imapd_authstate, mbentry->acl) & ACL_READ))
++ return 0;
++
+ switch (mrock->filter) {
+ case SEARCH_SOURCE_INBOXES:
+ /* Only allow user's INBOX or those postable by anonymous */
diff -Nru
cyrus-imapd-3.10.2/debian/patches/0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch
cyrus-imapd-3.10.2/debian/patches/0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch
---
cyrus-imapd-3.10.2/debian/patches/0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch
1970-01-01 02:00:00.000000000 +0200
+++
cyrus-imapd-3.10.2/debian/patches/0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch
2026-08-02 12:00:00.000000000 +0200
@@ -0,0 +1,121 @@
+Description: imapd: reject URLFETCH when no mboxkey exists for the mailbox
(CVE-2026-47085)
+ An initial debdiff for this CVE batch was submitted by Codin
+ <[email protected]> and sponsored/reviewed by Bastian Germann
+ <[email protected]> in Debian bug #1142925. This revision adds DEP-3
+ metadata and, where missing from that debdiff, folds in the matching
+ upstream Cassandane regression test(s).
+ .
+ This is CYR-2876, CVE-2026-47085.
+ .
+ Instead of treating "there's no mboxkey.db entry for this folder" as
+ indicating a zero-length key, treat a missing mboxkey entry as a
+ validation failure.
+ .
+ (Also close the mboxkey db on the previously-unclosed error paths.)
+ .
+ This problem reported by Matthew Horsfall.
+ .
+ Co-Authored-By: Claude <[email protected]>
+ .
+ Cherry-picked from upstream commits:
+ e5a1055614 cassandane: test for URLFETCH token forgery with empty mboxkey
+ 1d39d32c3b imapd: reject URLFETCH when no mboxkey exists for the mailbox
+Author: Ricardo Signes <[email protected]>
+Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/1d39d32c3b
+Bug: https://security-tracker.debian.org/tracker/CVE-2026-47085
+Bug-Debian: https://bugs.debian.org/1142925
+Bug-CVE: CVE-2026-47085
+Forwarded: not-needed
+Last-Update: 2026-07-25
+
+Index: cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm
+===================================================================
+--- cyrus-imapd.orig/cassandane/Cassandane/Cyrus/URLAuth.pm
++++ cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm
+@@ -44,6 +44,7 @@ use Cwd qw(abs_path);
+ use File::Path qw(mkpath);
+ use DateTime;
+ use Data::Dumper;
++use Digest::SHA qw(hmac_sha1_hex);
+
+ use lib '.';
+ use base qw(Cassandane::Cyrus::TestCase);
+@@ -161,4 +162,57 @@ sub test_urlfetch
+ $self->assert_str_equals($data, "ody");
+ }
+
++sub test_forged_empty_key
++ :UnixHierarchySep :AltNamespace
++{
++ my ($self) = @_;
++
++ # The victim has an INBOX and a second folder. The secret we don't want
++ # leaked lives in the second folder.
++ my $talk = $self->{store}->get_client;
++ $talk->create("Archive") or die "create Archive: " .
$talk->get_last_error;
++
++ $self->{store}->set_folder("INBOX");
++ $self->make_message("Public INBOX message");
++
++ $self->{store}->set_folder("Archive");
++ $self->make_message("Secret archived message");
++
++ # Cause the victim's mboxkey.db to be created, but with an entry only for
++ # the INBOX -- *not* for the Archive folder we're about to attack. An
++ # mboxkey.db appears in the wild whenever a user has ever issued a
++ # GENURLAUTH (or a RESETKEY for a specific mailbox).
++ my $seed;
++ $talk->_imap_cmd(
++ 'genurlauth', 0,
++ { genurlauth => sub { $seed = $_[1]->[0] } },
++ 'imap://[email protected]/INBOX/;uid=1;urlauth=user+cassandane',
++ 'INTERNAL',
++ );
++ $self->assert_not_null($seed, 'victim should be able to URLAUTH their own
INBOX');
++
++ # Now the attacker forges a token for the *Archive* folder. Because
++ # mboxkey_read() returns {key=NULL, keylen=0} for any mailbox it has never
++ # stored, cmd_urlfetch computes HMAC-SHA1 with an empty key -- which is
++ # entirely attacker-computable.
++ $self->{instance}->create_user('attacker');
++
++ my $rump =
'imap://[email protected]/Archive/;uid=1;urlauth=user+attacker';
++ my $token = '00' . hmac_sha1_hex($rump, '');
++ my $forged = "$rump:internal:$token";
++
++ my $svc = $self->{instance}->get_service('imap');
++ my $store = $svc->create_store(username => 'attacker');
++ my $attacker = $store->get_client();
++
++ my $stolen;
++ $attacker->_imap_cmd(
++ 'urlfetch', 0,
++ { urlfetch => sub { $stolen = $_[1]->[1] } },
++ $forged,
++ );
++
++ $self->assert_null($stolen);
++}
++
+ 1;
+Index: cyrus-imapd/imap/imapd.c
+===================================================================
+--- cyrus-imapd.orig/imap/imapd.c
++++ cyrus-imapd/imap/imapd.c
+@@ -14061,7 +14061,15 @@ static void cmd_urlfetch(char *tag)
+ if (r) break;
+
+ r = mboxkey_read(mboxkey_db, intname, &key, &keylen);
+- if (r) break;
++ if (!r && (!key || !keylen)) {
++ /* If there's no key, we can't possibly validate against
++ * it! */
++ r = IMAP_BADURL;
++ }
++ if (r) {
++ mboxkey_close(mboxkey_db);
++ break;
++ }
+
+ HMAC(EVP_sha1(), key, keylen, (unsigned char *) arg.s,
+ url.urlauth.rump_len, vtoken, &vtoken_len);
diff -Nru
cyrus-imapd-3.10.2/debian/patches/0024-imapd-LISTRIGHTS-requires-admin-rights.patch
cyrus-imapd-3.10.2/debian/patches/0024-imapd-LISTRIGHTS-requires-admin-rights.patch
---
cyrus-imapd-3.10.2/debian/patches/0024-imapd-LISTRIGHTS-requires-admin-rights.patch
1970-01-01 02:00:00.000000000 +0200
+++
cyrus-imapd-3.10.2/debian/patches/0024-imapd-LISTRIGHTS-requires-admin-rights.patch
2026-08-02 12:00:00.000000000 +0200
@@ -0,0 +1,85 @@
+Description: imapd: LISTRIGHTS requires admin rights (CVE-2026-47089)
+ An initial debdiff for this CVE batch was submitted by Codin
+ <[email protected]> and sponsored/reviewed by Bastian Germann
+ <[email protected]> in Debian bug #1142925. This revision adds DEP-3
+ metadata and, where missing from that debdiff, folds in the matching
+ upstream Cassandane regression test(s).
+ .
+ This is CYR-2872, CVE-2026-47089.
+ .
+ cmd_listrights was refusing only when the caller had *no* rights at
+ all on the target mailbox, when it should've been requiring admin
+ rights.
+ .
+ With this patch, we'll properly require ACL_ADMIN (owners and admins
+ still pass through), matching the check cmd_getacl already uses. When
+ the caller has ACL_LOOKUP but not ACL_ADMIN, return PERMISSION_DENIED;
+ otherwise hide the mailbox as NONEXISTENT.
+ .
+ This problem reported by Matthew Horsfall.
+ .
+ Co-Authored-By: Claude <[email protected]>
+ .
+ Cherry-picked from upstream commits:
+ 5f406195f7 cassandane: test that LISTRIGHTS requires admin rights
+ 5c97794e52 imapd: LISTRIGHTS requires admin rights
+Author: Ricardo Signes <[email protected]>
+Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/5c97794e52
+Bug: https://security-tracker.debian.org/tracker/CVE-2026-47089
+Bug-Debian: https://bugs.debian.org/1142925
+Bug-CVE: CVE-2026-47089
+Forwarded: not-needed
+Last-Update: 2026-07-25
+
+diff --git a/cassandane/Cassandane/Cyrus/ACL.pm
b/cassandane/Cassandane/Cyrus/ACL.pm
+index 62fef8e..64ca817 100644
+--- a/cassandane/Cassandane/Cyrus/ACL.pm
++++ b/cassandane/Cassandane/Cyrus/ACL.pm
+@@ -270,6 +270,30 @@ sub test_setacl_badrights
+ $self->assert_deep_equals($origacl, $newacl);
+ }
+
++sub test_listrights_other_user
++{
++ my ($self) = @_;
++
++ my $talk = $self->{store}->get_client;
++
++ # Create a victim user. We grant the attacker no explicit rights on the
++ # victim's mailbox: the default ACL "anyone p" is enough to expose the
++ # bug. (LISTRIGHTS gates only on "has any right at all", where it ought
++ # to require ACL_ADMIN; ACL_POST from the anyone grant is sufficient to
++ # slip past.)
++ $self->{instance}->create_user('victim');
++
++ my $seen;
++ $talk->_imap_cmd(
++ 'LISTRIGHTS', 0,
++ { listrights => sub { $seen = 1 } },
++ 'Other Users.victim', 'victim',
++ );
++
++ $self->assert_str_equals('no', $talk->get_last_completion_response);
++ $self->assert_null($seen);
++}
++
+ # see also LDAP.pm for groupid tests
+
+ 1;
+diff --git a/imap/imapd.c b/imap/imapd.c
+index 6aca6f4..703d835 100644
+--- a/imap/imapd.c
++++ b/imap/imapd.c
+@@ -8520,9 +8520,10 @@ static void cmd_listrights(char *tag, char *name, char
*identifier)
+ if (!r) {
+ rights = cyrus_acl_myrights(imapd_authstate, mbentry->acl);
+
+- if (!rights && !imapd_userisadmin &&
++ if (!(rights & ACL_ADMIN) && !imapd_userisadmin &&
+ !mboxname_userownsmailbox(imapd_userid, intname)) {
+- r = IMAP_MAILBOX_NONEXISTENT;
++ r = (rights & ACL_LOOKUP) ?
++ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
+ }
+ }
+
diff -Nru
cyrus-imapd-3.10.2/debian/patches/0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch
cyrus-imapd-3.10.2/debian/patches/0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch
---
cyrus-imapd-3.10.2/debian/patches/0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch
1970-01-01 02:00:00.000000000 +0200
+++
cyrus-imapd-3.10.2/debian/patches/0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch
2026-08-02 12:00:00.000000000 +0200
@@ -0,0 +1,130 @@
+Description: imapd: require read access for XAPPLEPUSHSERVICE mailboxes
(CVE-2026-47081)
+ An initial debdiff for this CVE batch was submitted by Codin
+ <[email protected]> and sponsored/reviewed by Bastian Germann
+ <[email protected]> in Debian bug #1142925. This revision adds DEP-3
+ metadata and, where missing from that debdiff, folds in the matching
+ upstream Cassandane regression test(s).
+ .
+ This is CYR-2871, CVE-2026-47081.
+ .
+ Only accept a mailbox if the caller is its owner, an admin, or holds
+ ACL_READ. Anything else is silently skipped (no echo, no event), which
+ also preserves the existing "missing" behaviour for non-existent names.
+ .
+ This problem reported by Matthew Horsfall.
+ .
+ Co-Authored-By: Claude <[email protected]>
+ .
+ Cherry-picked from upstream commits:
+ 1044c0b13b cassandane: test XAPPLEPUSHSERVICE leaks other users' folders
+ 50f77bad30 imapd: require read access for XAPPLEPUSHSERVICE mailboxes
+Author: Ricardo Signes <[email protected]>
+Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/50f77bad30
+Bug: https://security-tracker.debian.org/tracker/CVE-2026-47081
+Bug-Debian: https://bugs.debian.org/1142925
+Bug-CVE: CVE-2026-47081
+Forwarded: not-needed
+Last-Update: 2026-07-25
+
+diff --git a/cassandane/Cassandane/Cyrus/XApplePushService.pm
b/cassandane/Cassandane/Cyrus/XApplePushService.pm
+new file mode 100644
+index 0000000..497d505
+--- /dev/null
++++ b/cassandane/Cassandane/Cyrus/XApplePushService.pm
+@@ -0,0 +1,40 @@
++# SPDX-License-Identifier: BSD-3-Clause-CMU
++# See COPYING file at the root of the distribution for more details.
++
++package Cassandane::Cyrus::XApplePushService;
++use strict;
++use warnings;
++
++use base qw(Cassandane::Cyrus::TestCase);
++use Cassandane::Util::Log;
++
++sub new
++{
++ my ($class, @args) = @_;
++
++ my $config = Cassandane::Config->default()->clone();
++ $config->set(event_groups => 'applepushservice');
++ $config->set(aps_topic => 'mail');
++
++ return $class->SUPER::new({
++ adminstore => 1,
++ config => $config,
++ services => ['imap'],
++ }, @args);
++}
++
++sub set_up
++{
++ my ($self) = @_;
++ $self->SUPER::set_up();
++}
++
++sub tear_down
++{
++ my ($self) = @_;
++ $self->SUPER::tear_down();
++}
++
++use Cassandane::Tiny::Loader 'tiny-tests/XApplePushService';
++
++1;
+diff --git a/cassandane/tiny-tests/XApplePushService/leak_other_user
b/cassandane/tiny-tests/XApplePushService/leak_other_user
+new file mode 100644
+index 0000000..ec75872
+--- /dev/null
++++ b/cassandane/tiny-tests/XApplePushService/leak_other_user
+@@ -0,0 +1,34 @@
++#!perl
++use Cassandane::Tiny;
++
++sub test_leak_other_user
++{
++ my ($self) = @_;
++
++ my $admintalk = $self->{adminstore}->get_client;
++ my $talk = $self->{store}->get_client;
++
++ # Create a victim user and a subfolder on their account. The attacker
++ # holds nothing on it beyond the "anyone p" default ACL.
++ $self->{instance}->create_user('victim');
++ $admintalk->create('user.victim.bah') or die "create: " .
$admintalk->get_last_error;
++
++ my @responses;
++ $talk->_imap_cmd(
++ 'XAPPLEPUSHSERVICE', 0,
++ { xapplepushservice => sub { push @responses, [ @{$_[1]} ] } },
++ 'mailboxes', [ 'Other Users.victim.bah' ],
++ 'aps-version', '2',
++ 'aps-account-id', 'accountid',
++ 'aps-device-token', 'devtoken',
++ 'aps-subtopic', 'subtopic',
++ );
++
++ # Every invocation of XAPPLEPUSHSERVICE gets the trailing "aps-version"
++ # status response; that's fine. What must not appear is a
++ # * XAPPLEPUSHSERVICE "mailbox" "<name>" echo, which both confirms the
++ # mailbox exists and (via mboxevent_set_applepushservice) subscribes
++ # the attacker's APNS device to the victim's mail.
++ my @mailbox_echoes = grep { ($_->[0] // '') eq 'mailbox' } @responses;
++ $self->assert_deep_equals([], \@mailbox_echoes);
++}
+diff --git a/imap/imapd.c b/imap/imapd.c
+index 3b3d36c..6aca6f4 100644
+--- a/imap/imapd.c
++++ b/imap/imapd.c
+@@ -14794,7 +14794,10 @@ static void cmd_xapplepushservice(const char *tag,
+ char *intname =
+ mboxname_from_external(name, &imapd_namespace, imapd_userid);
+ r = mlookup(tag, name, intname, &mbentry);
+- if (!r && mbtype_isa(mbentry->mbtype) == MBTYPE_EMAIL) {
++ if (!r && mbtype_isa(mbentry->mbtype) == MBTYPE_EMAIL &&
++ (imapd_userisadmin ||
++ mboxname_userownsmailbox(imapd_userid, intname) ||
++ (cyrus_acl_myrights(imapd_authstate, mbentry->acl) & ACL_READ)))
{
+ strarray_push(¬if_mailboxes, name);
+ if (applepushserviceargs->aps_version >= 2) {
+ prot_puts(imapd_out, "* XAPPLEPUSHSERVICE \"mailbox\" ");
diff -Nru
cyrus-imapd-3.10.2/debian/patches/0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch
cyrus-imapd-3.10.2/debian/patches/0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch
---
cyrus-imapd-3.10.2/debian/patches/0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch
1970-01-01 02:00:00.000000000 +0200
+++
cyrus-imapd-3.10.2/debian/patches/0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch
2026-08-02 12:00:00.000000000 +0200
@@ -0,0 +1,127 @@
+Description: imapd.c: re-check URLAUTH authorizer access just in time
(CVE-2026-47087)
+ An initial debdiff for this CVE batch was submitted by Codin
+ <[email protected]> and sponsored/reviewed by Bastian Germann
+ <[email protected]> in Debian bug #1142925. This revision adds DEP-3
+ metadata and, where missing from that debdiff, folds in the matching
+ upstream Cassandane regression test(s).
+ .
+ This is CYR-2868, CVE-2026-47087.
+ .
+ cmd_urlfetch validated the HMAC token against the authorizer's mboxkey,
+ but skipped any ACL check whenever url.urlauth.access was set. Sure,
+ the rights existed right at that moment, but if access is revoked after
+ the URL is created, the URL needs to stop working. We need to just
+ check access JIT.
+ .
+ Co-Authored-By: Claude <[email protected]>
+ .
+ Cherry-picked from upstream commits:
+ be5eabb4df cassandane: tighten URLAuth ACL bypass test
+ fbb130b339 cassandane: fix indenting in cassandane/tiny-tests/URLAuth/perms
+ bdf709102c cassandane: URLAUTH must re-check authorizer ACLs at fetch time
+ 8380d0fb3d imapd.c: re-check URLAUTH authorizer access just in time
+Author: Ricardo Signes <[email protected]>
+Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/8380d0fb3d
+Bug: https://security-tracker.debian.org/tracker/CVE-2026-47087
+Bug-Debian: https://bugs.debian.org/1142925
+Bug-CVE: CVE-2026-47087
+Forwarded: not-needed
+Last-Update: 2026-07-25
+
+Index: cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm
+===================================================================
+--- cyrus-imapd.orig/cassandane/Cassandane/Cyrus/URLAuth.pm
++++ cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm
+@@ -215,4 +215,61 @@ sub test_forged_empty_key
+ $self->assert_null($stolen);
+ }
+
++sub test_revoked_access_invalidates_url
++ :UnixHierarchySep :AltNamespace
++{
++ my ($self) = @_;
++
++ my $admintalk = $self->{adminstore}->get_client;
++
++ $self->{instance}->create_user('other');
++
++ $self->make_message("Secret Message");
++
++ # Grant 'other' read+lookup so that genurlauth will mint a token.
++ $admintalk->setacl('user/cassandane', 'other', 'lrs')
++ or die "setacl: " . $admintalk->get_last_error;
++
++ my $svc = $self->{instance}->get_service('imap');
++ my $store = $svc->create_store(username => 'other');
++ my $other_talk = $store->get_client();
++
++ # 'other' mints a URLAUTH token while authorized.
++ my $url;
++ $other_talk->_imap_cmd(
++ 'genurlauth', 0,
++ { genurlauth => sub { $url = $_[1]->[0] } },
++
'imap://[email protected]/Other%20Users%2fcassandane/;uid=1;urlauth=user+other',
++ 'INTERNAL',
++ );
++ $self->assert_not_null($url, 'genurlauth should succeed while
authorized');
++
++ # Captured value is wrapped in quotes; strip them for urlfetch.
++ (my $bare_url = $url) =~ s/\A"(.*)"\z/$1/;
++
++ # Sanity check: the token works while access is in place.
++ my $data;
++ $other_talk->_imap_cmd(
++ 'urlfetch', 0,
++ { urlfetch => sub { $data = $_[1]->[1] } },
++ $bare_url,
++ );
++ $self->assert_not_null($data, 'URL should fetch while authorizer is
authorized');
++
++ # Revoke 'other's access entirely.
++ $admintalk->setacl('user/cassandane', 'other', '')
++ or die "setacl: " . $admintalk->get_last_error;
++
++ # The token is cryptographically intact, but the authorizer's right to
++ # delegate the message is gone. cmd_urlfetch must re-check ACLs at fetch
++ # time and refuse, instead of trusting the token alone.
++ my $stolen;
++ $other_talk->_imap_cmd(
++ 'urlfetch', 0,
++ { urlfetch => sub { $stolen = $_[1]->[1] } },
++ $bare_url,
++ );
++ $self->assert_null($stolen, 'URL must not fetch after authorizer access
revoked');
++}
++
+ 1;
+Index: cyrus-imapd/imap/imapd.c
+===================================================================
+--- cyrus-imapd.orig/imap/imapd.c
++++ cyrus-imapd/imap/imapd.c
+@@ -14108,6 +14108,26 @@ static void cmd_urlfetch(char *tag)
+ }
+ if (r) goto err;
+
++ /* For URLAUTH-protected URLs, re-check the authorizer's ACL. The
++ * HMAC asserts that authorization DID exist, but may have since been
++ * revoked. We can't read the ACL via state->mailbox here: when the
++ * URL targets the currently-selected mailbox we reuse imapd_index,
++ * whose mailbox handle is closed between commands. */
++ if (url.urlauth.access) {
++ mbentry_t *authz_mbentry = NULL;
++ r = mlookup(NULL, NULL, intname, &authz_mbentry);
++ if (!r) {
++ struct auth_state *authzstate = auth_newstate(url.user);
++ int authz_rights =
++ cyrus_acl_myrights(authzstate, authz_mbentry->acl);
++ auth_freestate(authzstate);
++ if (!(authz_rights & ACL_READ))
++ r = IMAP_BADURL;
++ }
++ mboxlist_entry_free(&authz_mbentry);
++ if (r) goto err;
++ }
++
+ if (url.uidvalidity &&
+ (state->mailbox->i.uidvalidity != url.uidvalidity)) {
+ r = IMAP_BADURL;
diff -Nru
cyrus-imapd-3.10.2/debian/patches/0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch
cyrus-imapd-3.10.2/debian/patches/0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch
---
cyrus-imapd-3.10.2/debian/patches/0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch
1970-01-01 02:00:00.000000000 +0200
+++
cyrus-imapd-3.10.2/debian/patches/0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch
2026-08-02 12:00:00.000000000 +0200
@@ -0,0 +1,107 @@
+Description: imapd: require read access on mailbox in GENURLAUTH
(CVE-2026-47086)
+ An initial debdiff for this CVE batch was submitted by Codin
+ <[email protected]> and sponsored/reviewed by Bastian Germann
+ <[email protected]> in Debian bug #1142925. This revision adds DEP-3
+ metadata and, where missing from that debdiff, folds in the matching
+ upstream Cassandane regression test(s).
+ .
+ This is CYR-2868, CVE-2026-47086.
+ .
+ You can't grant access to things you can't access! Without this check,
+ any authenticated user could mint a URLAUTH token for any mailbox they
+ could name, because cmd_urlfetch skipped the ACL check whenever
+ urlauth.access is set -- it trusts that the presence of a valid HMAC
+ means the authorizing user was entitled to delegate.
+ .
+ Require the authorizer to have at least ACL_READ on the mailbox before
+ issuing a token.
+ .
+ This problem reported by Matthew Horsfall.
+ .
+ Co-Authored-By: Claude <[email protected]>
+ .
+ Cherry-picked from upstream commits:
+ 88d9bf6e7e cassandane: test for ACL bypass in URLAuth
+ e7a28ccf9b imapd: require read access on mailbox in GENURLAUTH
+Author: Ricardo Signes <[email protected]>
+Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/e7a28ccf9b
+Bug: https://security-tracker.debian.org/tracker/CVE-2026-47086
+Bug-Debian: https://bugs.debian.org/1142925
+Bug-CVE: CVE-2026-47086
+Forwarded: not-needed
+Last-Update: 2026-07-25
+
+Index: cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm
+===================================================================
+--- cyrus-imapd.orig/cassandane/Cassandane/Cyrus/URLAuth.pm
++++ cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm
+@@ -272,4 +272,50 @@ sub test_revoked_access_invalidates_url
+ $self->assert_null($stolen, 'URL must not fetch after authorizer access
revoked');
+ }
+
++sub test_perms
++ :UnixHierarchySep :AltNamespace
++{
++ my ($self) = @_;
++
++ $self->{instance}->create_user('other');
++
++ my $msg = $self->make_message("Message A");
++
++ my $url;
++ my %handlers = (
++ genurlauth => sub {
++ my ($cmd, $params) = @_;
++ $url = $params->[0];
++ }
++ );
++
++ my $svc = $self->{instance}->get_service('imap');
++ my $store = $svc->create_store(username => 'other');
++ my $talk = $store->get_client();
++
++ $talk->_imap_cmd(
++ 'genurlauth', 0, \%handlers,
++
"imap://other\@127.0.0.1/Other%20Users%2fcassandane/;uid=1;urlauth=user+other",
++ "INTERNAL",
++ );
++
++ # This should fail in some way, commented out to show we really do fetch the
++ # email
++# $self->assert_null($url);
++
++ my $email;
++ %handlers = (
++ urlfetch => sub {
++ my ($cmd, $params) = @_;
++ $email = ${$params}[1];
++ },
++ );
++
++ my $res = $talk->_imap_cmd('urlfetch', 0, \%handlers, $url);
++
++ warn $email if $email;
++
++ $self->assert_null($email);
++}
++
+ 1;
+Index: cyrus-imapd/imap/imapd.c
+===================================================================
+--- cyrus-imapd.orig/imap/imapd.c
++++ cyrus-imapd/imap/imapd.c
+@@ -14261,6 +14261,14 @@ static void cmd_genurlauth(char *tag)
+ continue;
+ }
+
++ /* You can't give permission you don't have! */
++ if (!imapd_userisadmin &&
++ !(cyrus_acl_myrights(imapd_authstate, mbentry->acl) & ACL_READ)) {
++ mboxlist_entry_free(&mbentry);
++ r = IMAP_BADURL;
++ goto err;
++ }
++
+ mboxlist_entry_free(&mbentry);
+
+ /* lookup key */
diff -Nru
cyrus-imapd-3.10.2/debian/patches/0028-imapd-fix-cmd_delete-force-handling.patch
cyrus-imapd-3.10.2/debian/patches/0028-imapd-fix-cmd_delete-force-handling.patch
---
cyrus-imapd-3.10.2/debian/patches/0028-imapd-fix-cmd_delete-force-handling.patch
1970-01-01 02:00:00.000000000 +0200
+++
cyrus-imapd-3.10.2/debian/patches/0028-imapd-fix-cmd_delete-force-handling.patch
2026-08-02 12:00:00.000000000 +0200
@@ -0,0 +1,116 @@
+Description: imapd: fix cmd_delete 'force' handling (LOCALDELETE ACL bypass)
(CVE-2026-47084)
+ An initial debdiff for this CVE batch was submitted by Codin
+ <[email protected]> and sponsored/reviewed by Bastian Germann
+ <[email protected]> in Debian bug #1142925. This revision adds DEP-3
+ metadata and, where missing from that debdiff, folds in the matching
+ upstream Cassandane regression test(s).
+ .
+ This is CYR-2867, CVE-2026-47084.
+ .
+ Non-admin users could invoke the admin-only LOCALDELETE command
+ and delete mailboxes without permission, because MBOXLIST_DELETE_FORCE
+ wasn't being plumbed through to mboxlist_deletemailbox.
+ .
+ Reported by Michael Lynch (mtlynch.io).
+ .
+ Cherry-picked from upstream commits:
+ 2c871deac5 Delete: test localdelete of other user's mailbox
+ 304255f5be imapd: fix cmd_delete 'force' handling
+ 7dfc43c681 MurderIMAP: localdelete requires admin privs
+Author: ellie timoney <[email protected]>
+Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/304255f5be
+Bug: https://security-tracker.debian.org/tracker/CVE-2026-47084
+Bug-Debian: https://bugs.debian.org/1142925
+Bug-CVE: CVE-2026-47084
+Forwarded: not-needed
+Last-Update: 2026-07-25
+
+diff --git a/cassandane/Cassandane/Cyrus/Delete.pm
b/cassandane/Cassandane/Cyrus/Delete.pm
+index 10f9fb6..fc778af 100644
+--- a/cassandane/Cassandane/Cyrus/Delete.pm
++++ b/cassandane/Cassandane/Cyrus/Delete.pm
+@@ -1085,4 +1085,30 @@ sub test_cyr_expire_delete_noexpire
+ $self->check_syslog($self->{instance});
+ }
+
++sub test_localdelete_other
++ :UnixHierarchySep :AltNamespace
++{
++ my ($self) = @_;
++
++ $self->{instance}->create_user("other");
++
++ my $admintalk = $self->{adminstore}->get_client();
++ $admintalk->create('user/other/foo');
++ $self->assert_str_equals('ok',
$admintalk->get_last_completion_response());
++ $admintalk->setacl('user/other/foo', 'cassandane' => 'l');
++ $self->assert_str_equals('ok',
$admintalk->get_last_completion_response());
++
++ my $imaptalk = $self->{store}->get_client();
++
++ # shouldn't be able to delete another user's mailbox
++ $imaptalk->delete('Other Users/other/foo');
++ $self->assert_str_equals('no', $imaptalk->get_last_completion_response());
++ $self->assert_matches(qr{Permission denied}, $imaptalk->get_last_error());
++
++ # shouldn't be able to localdelete it either!
++ $imaptalk->localdelete('Other Users/other/foo');
++ $self->assert_str_equals('no', $imaptalk->get_last_completion_response());
++ $self->assert_matches(qr{Permission denied}, $imaptalk->get_last_error());
++}
++
+ 1;
+diff --git a/cassandane/Cassandane/Cyrus/MurderIMAP.pm
b/cassandane/Cassandane/Cyrus/MurderIMAP.pm
+index fbf631b..0a83897 100644
+--- a/cassandane/Cassandane/Cyrus/MurderIMAP.pm
++++ b/cassandane/Cassandane/Cyrus/MurderIMAP.pm
+@@ -294,14 +294,18 @@ sub test_move_to_backend_nonexistent
+
+ my $frontend = $self->{frontend_store}->get_client();
+ my $backend = $self->{backend1_store}->get_client();
++ my $backend_admin = $self->{backend1_adminstore}->get_client();
+
+ # create a destination folder (on both frontend and backend)
+ $frontend->create($dest_folder);
+ $self->assert_str_equals('ok', $frontend->get_last_completion_response());
+
+ # nuke the destination folder (on the backend only)
+- $backend->localdelete($dest_folder);
+- $self->assert_str_equals('ok', $backend->get_last_completion_response());
++ # n.b. 'INBOX' is part of the mailbox name, it's not just the cassandane
++ # user's inbox, hence the weird admin path here. Not sure why I did it
++ # that way, might have been just what was in the bug report.
++ $backend_admin->localdelete('user.cassandane.INBOX.dest');
++ $self->assert_str_equals('ok',
$backend_admin->get_last_completion_response());
+
+ my $f_folders = $frontend->list('', '*');
+ $self->assert_deep_equals(
+diff --git a/imap/imapd.c b/imap/imapd.c
+index 2cb5126..51aecc7 100644
+--- a/imap/imapd.c
++++ b/imap/imapd.c
+@@ -7346,19 +7346,22 @@ static void cmd_delete(char *tag, char *name, int
localonly, int force)
+ /* local mailbox */
+ if (!r) {
+ int isadmin = imapd_userisadmin || imapd_userisproxyadmin;
++ int delflags = force ? MBOXLIST_DELETE_FORCE : 0;
+
+ if (mbname_isdeleted(mbname)) {
+ r = mboxlist_deletemailbox(mbname_intname(mbname),
+ isadmin, imapd_userid,
+ imapd_authstate, mboxevent,
+- MBOXLIST_DELETE_LOCALONLY);
++ delflags | MBOXLIST_DELETE_LOCALONLY);
+ }
+ else if (!isadmin && mbname_issystem(mbname)) {
+ r = IMAP_PERMISSION_DENIED;
+ }
+ else {
+ delete_user = mboxname_isusermailbox(mbname_intname(mbname), 1);
+- int delflags = (1-force) ? MBOXLIST_DELETE_CHECKACL : 0;
++
++ if (!force)
++ delflags |= MBOXLIST_DELETE_CHECKACL;
+
+ if (!delete_user && mboxlist_haschildren(mbname_intname(mbname)))
{
+ r = IMAP_MAILBOX_HASCHILDREN;
diff -Nru cyrus-imapd-3.10.2/debian/patches/series
cyrus-imapd-3.10.2/debian/patches/series
--- cyrus-imapd-3.10.2/debian/patches/series 2026-07-12 21:29:05.000000000
+0200
+++ cyrus-imapd-3.10.2/debian/patches/series 2026-08-02 12:00:00.000000000
+0200
@@ -8,3 +8,12 @@
0018-increase-test-timeout.patch
#0019-propagate-XXFLAGS.patch
eventsource-without-websocket.patch
+0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch
+0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch
+0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch
+0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch
+0024-imapd-LISTRIGHTS-requires-admin-rights.patch
+0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch
+0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch
+0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch
+0028-imapd-fix-cmd_delete-force-handling.patch