Don't let a group's members share its mailboxes on
ci / github (pull_request) Skipped
ci / fork-checks (pull_request) Failing after 1m16s
ci / build (pull_request) Canceled after 5m6s

A group's members reach its mailbox through membership, which counts
as owning the account, so every ACL check was skipped: on a scratch
server a member gave an outsider read access to the group's Inbox with
one Mailbox/set shareWith, with no administrator involved and nothing
audited. Who is in a group is an administrator's decision.

AccessToken::is_group_member_only names that case (in the account
only through a group, without Impersonate). For such a member:

- Mailbox/set with a shareWith change, on create or update, is
  refused as forbidden;
- IMAP SETACL and DELETEACL answer NO [NOPERM];
- myRights reports mayShare false, and MYRIGHTS leaves out "a";
  every other right stays.

Administrators and the account itself are unchanged. The JMAP ACL
test's group section now checks all three for a member and that the
outsider still has nothing (specs/multi-account.md, MA-D0, G1).

jmap_tests and imap_tests pass (RocksDB). The IMAP refusal has no test
of its own yet; imap_tests passing shows the rest is unchanged.
This commit is contained in:
jcoffey-dev committed 2026-10-05 14:15:58 -07:00
1 parent d7bebd454d
commit 2d8728793c
5 files changed
+72 -4

No files matched your search

+18 -4
View File
@@ -212,7 +212,7 @@ impl<T: SessionStream> Session<T> {
}
rights
} else {
vec![
let mut rights = vec![
Rights::Read,
Rights::Lookup,
Rights::Insert,
@@ -223,8 +223,12 @@ impl<T: SessionStream> Session<T> {
Rights::CreateMailbox,
Rights::DeleteMailbox,
Rights::Post,
Rights::Administer,
]
];
// inbuxa: MA-D0: a group's members don't share its mailboxes on.
if !access_token.is_group_member_only(mailbox_id.account_id) {
rights.push(Rights::Administer);
}
rights
};
trc::event!(
@@ -266,10 +270,20 @@ impl<T: SessionStream> Session<T> {
spawn_op!(data, {
// Validate mailbox
let (mailbox_id, current_mailbox, _) = data
let (mailbox_id, current_mailbox, access_token) = data
.get_acl_mailbox(&arguments, true)
.await
.imap_ctx(&arguments.tag, trc::location!())?;
// inbuxa: MA-D0: a group's members don't share its mailboxes on.
if access_token.is_group_member_only(mailbox_id.account_id) {
return Err(trc::ImapEvent::Error
.into_err()
.details("This mailbox belongs to a group. Only an administrator can change who has it.")
.code(ResponseCode::NoPerm)
.id(arguments.tag.to_string()));
}
let current_mailbox = current_mailbox
.into_deserialized::<email::mailbox::Mailbox>()
.imap_ctx(&arguments.tag, trc::location!())?;