Skip to content

Group permissions

Every change to a group’s shared state after creation is a proposal a member signs, and a policy decides whether that member may make it. The policies live in the group itself: the component registry is one component of the group’s app-data dictionary, so the rules travel inside the group context, every member reads the same rules, and a change to the rules is a change to the group that the same engine judges. A client that evaluates a policy differently from the others accepts a commit they reject, or rejects one they accept, and the group forks.

The engine has three inputs: the proposer’s roles, read from the admin and super-admin lists as they stood before the commit; the operation, which is an insert, an update, or a delete of one element of one component; and the policy the registry stores for that component and operation. Two components are not governed by the registry, because the registry depends on them: the registry itself and the super-admin list are super-admin-only, except that either DM participant may change application registry entries under PERM-026. Membership changes carried as MLS Add and Remove proposals are judged by the membership component’s policies, so one policy governs a member’s presence however the change is carried.

In scope: the roles and the components that hold them; the policy language and how it is evaluated; how a write to a component is authorised, including membership changes carried as MLS proposals; what a valid registry is and how an app changes a policy; the non-DM presets and their recognition; authority to act on leave requests; and compatibility across changes to commit acceptance.

Out of scope: when the engine runs and what a rejected commit does to the group (GMOD); component identifiers, the dictionary, registry structure, component types, and byte limits (META); fixed DM policies and the participant-add exception (DMS-002 and DMS-004); exact identity state (IDENT-070); and what a Welcome carries (JOIN).

RelatedRelation
GMODOwns the commit validation pipeline that invokes this engine, the membership component, and the protocol-version floor. GMOD-001 states which proposal types reach the engine.
METAOwns all component ids and ranges, the ComponentMetadata and ComponentPermissions messages, registry structure, and application of component deltas.
DMSDMS-002 owns the fixed policies and initial roles; DMS-004 owns the participant-add exception used in section 3.
EVENTEVENT-001 reports a changed policy set or admin list through conversation.metadata_changed.
TermMeaning
ComponentOne entry of a group’s app-data dictionary, named by a component id. Its value is bytes whose shape the registry’s component_type names. Owned by META section 2.
Component idThe identifier assigned under META section 1. Components are named symbolically here.
RegistryCOMPONENT_REGISTRY: maps component ids to ComponentMetadata. Its permissions field carries the policies this spec evaluates.
Admin listADMIN_LIST: the set of inbox ids that are admins.
Super-admin listSUPER_ADMIN_LIST: the set of inbox ids that are super admins.
AdminAn inbox that is in the admin list. A super admin is not an admin unless it is also in the admin list, but every rule that admits an admin admits a super admin.
Super adminAn inbox that is in the super-admin list.
ProposerThe member whose leaf node signed a proposal.
CommitterThe member whose leaf node signed a commit.
Element changeOne insert, update, or delete of one element of a component, as section 3 derives it from a proposal.
Action policyOne of the four registry policies that govern membership and the admin list: insert_policy and delete_policy of the membership component, and insert_policy and delete_policy of the admin list.
PresetOne of the preconfigured policy sets of section 5.

A group has three tiers: members, admins, and super admins. The two lists are components, so a change to a list is a proposal the engine judges like any other, and a receiver reads the roles from the group context it holds. The group’s creator starts as the only super admin. There is no other standing role: the creator’s inbox id is recorded in the group but grants nothing after creation.

Roles are read from the committed group context, never from a proposal inside the commit being judged. A commit that adds an inbox to the super-admin list and, in the same commit, has that inbox write a super-admin-only component is rejected, because the write is evaluated against the lists as they stood before the commit.

Two rules protect the top tier from the tiers below it. A super admin cannot be removed from the group, whatever the remove-member policy says, so an admin who was delegated removals cannot remove the person who delegated them. The super-admin list cannot be emptied, so a group never reaches a state in which nobody can change its rules. A super admin can remove itself from the list while another super admin remains.

IDTitleRequirementWhy
PERM-001Roles from the committed listsThe client MUST take a proposer’s admin status from the admin list and its super admin status from the super-admin list of the committed group context, and MUST NOT take either from a proposal in the commit being judged.A commit that grants a role and uses it in one step would let any member escalate.
PERM-002The creator is the first super adminWhen a client creates a group whose conversation type is not DM, it MUST write the super-admin list containing only the creator’s inbox id and the admin list empty.
PERM-003Super admins stay membersIf a commit removes from the membership component an inbox that is in the super-admin list, then the client MUST reject the commit, whatever the membership component’s delete_policy says.An admin who could remove the super admins takes the group from them.
PERM-004The super-admin list never emptiesIf a proposal would change a super-admin list that is not empty into one that is empty, then the client MUST reject it.A group with no super admin has nobody who can change its rules, and no rule can restore one.
PERM-026Protect registry and rolesWhen a proposal writes COMPONENT_REGISTRY or SUPER_ADMIN_LIST, the client MUST reject it unless the proposer is a super admin under PERM-001; in a DM, it MUST also allow either DM_MEMBERS participant to insert, update, or delete registry entries only when every changed ID is in 0xC000–0xFEFF. It MUST NOT extend that DM exception to SUPER_ADMIN_LIST or well-known registry entries.A DM has no listed super admin, but a registry exception must not grant control of its roles or membership.

A policy is a MetadataPolicy: a base policy, or a condition over child policies. A component’s entry in the registry carries three of them, one for each operation.

// A policy that governs updating metadata
message MetadataPolicy {
// Base policy
enum MetadataBasePolicy {
METADATA_BASE_POLICY_UNSPECIFIED = 0;
METADATA_BASE_POLICY_ALLOW = 1;
METADATA_BASE_POLICY_DENY = 2;
METADATA_BASE_POLICY_ALLOW_IF_ADMIN = 3;
METADATA_BASE_POLICY_ALLOW_IF_SUPER_ADMIN = 4;
METADATA_BASE_POLICY_ALLOW_IF_SELF_OR_NON_MEMBER = 5;
}
// Combine multiple policies. All must evaluate to true
message AndCondition {
repeated MetadataPolicy policies = 1;
}
// Combine multiple policies. Any must evaluate to true
message AnyCondition {
repeated MetadataPolicy policies = 1;
}
oneof kind {
MetadataBasePolicy base = 1;
AndCondition and_condition = 2;
AnyCondition any_condition = 3;
}
}

META section 2 defines ComponentPermissions and its three policy fields. The MetadataPolicy block above is the policy language used by those fields.

A base policy reads the authenticated proposer, its roles, the element operation and key, and the membership after the commit. It never reads the value being written.

Base policyAllowed when
METADATA_BASE_POLICY_ALLOWAlways
METADATA_BASE_POLICY_DENYNever
METADATA_BASE_POLICY_ALLOW_IF_ADMINThe proposer is an admin or a super admin
METADATA_BASE_POLICY_ALLOW_IF_SUPER_ADMINThe proposer is a super admin
METADATA_BASE_POLICY_ALLOW_IF_SELF_OR_NON_MEMBERFor a TLS_MAP_INBOX_ID_BYTES or TLS_MAP_INBOX_ID_STRING element, Insert or Update: proposer inbox equals the element’s key; Delete: proposer inbox equals the key or the key is absent from the membership after the commit. When a standalone proposal is validated on receipt, the absence test uses the committed membership. Deny for whole-component Remove and all other component types.

METADATA_BASE_POLICY_ALLOW_IF_SELF_OR_NON_MEMBER is an ordinary registry policy. A super admin can replace it on an application mutable-range entry, and META-015 still prevents changes to immutable-range entries. A delete that needs the membership after the commit is inline in the member-removal commit and is validated there under GMOD-019; a standalone proposal is judged against the committed membership.

A policy can be malformed: no kind, a base value the table does not list, or a condition with no children. A malformed policy denies. Evaluation of a condition stops at the first child that decides it, and a malformed child that is reached decides it as denied, so two clients that evaluate the same tree in the same order reach the same answer.

IDTitleRequirementWhy
PERM-027Evaluate base policy contextWhen the client evaluates a MetadataPolicy.base, it MUST use the base-policy table in section 2 with the authenticated proposer, the element operation and key, and the membership after the commit that carries the proposal, whether inline or by reference; when it validates a standalone proposal on receipt, it MUST use the committed membership.A self-owned policy cannot be judged from role flags or map values alone.
PERM-007Condition semanticsThe client MUST evaluate the children of an and_condition in order and stop at the first child that is denied or malformed, with that child’s result, and MUST evaluate the children of an any_condition in order and stop at the first child that is allowed or malformed, with that child’s result. An and_condition whose children are all allowed MUST be allowed, and an any_condition whose children are all denied MUST be denied.Two clients that stop at different children reach different answers for the same tree.
PERM-008A malformed policy deniesWhen the policy that governs a write has no kind, has a base value not in the table above, or is a condition with no children, the client MUST deny the write.A policy that fails open turns a corrupt registry entry into an open door.

A proposal that writes a component is an AppDataUpdate proposal, defined in draft-ietf-mls-extensions-08 §4.7, whose operation is Update with a payload or Remove. The client first turns the proposal into element changes, because a delta to a set or a map can insert one element and delete another in one proposal, and the two operations can have different policies. The registry entry’s component_type says how the payload is read; META-010 owns component types and encodings. PERM-014 owns authorization of a component with no built-in definition; META-010 owns its type-based decoding and application.

Component typeOperation on the wireElement changes
A scalar (COMPONENT_TYPE_BYTES, COMPONENT_TYPE_STRING)UpdateOne update, whether or not the component already has a value
A scalarRemoveOne delete
A set or a mapUpdate carrying a deltaOne change per mutation in the delta: an insert for an insert, an update for an update, a delete for a remove, a remove-by-hash, or a delete
A set or a mapRemoveOne delete

META section 1 owns component id ranges. META-004 rejects writes to an existing immutable component, including insertion into an existing immutable collection. META-014 and META-015 own registry structure and immutable registry entries. These checks apply before policy evaluation; an allowing policy cannot bypass them.

Every other component is governed by its registry entry, and a component with no entry is denied to everyone. The entry is read from the committed registry, so registering a component and writing it are two commits; the same rule keeps a commit from changing a policy and using the changed policy in one step. A client that predates a component still has the registry entry, and evaluates the write under it rather than refusing what it does not know, because a refusal the other members do not share is a fork.

MLS Add and Remove proposals use the membership component’s policies. For an inbox added or removed by a commit, every corresponding proposal retains its own proposer, including when several proposals name installations of that inbox. A change limited to installations of an inbox that remains a member is checked under GMOD and the membership entry’s update policy. An inline proposal has the committer as proposer under RFC 9420 §12.4.2.

A DM has no listed super admin. PERM-026 lets either DM_MEMBERS participant change application registry entries, and PERM-028 lets it satisfy ALLOW_IF_SUPER_ADMIN on application components. Neither grants a role, changes DM membership, or reaches a well-known component.

DMS-004 owns the participant-add exception. That exception takes precedence over denial by the membership insert policy in both the Add path and the dictionary-insert path. It uses the proposer under PERM-009 in both paths and at final membership validation. It does not bypass structural checks or authorize another mutation in the same proposal.

IDTitleRequirementWhy
PERM-009Judge each authenticated proposerWhen the client evaluates a proposal, it MUST use that proposal’s authenticated proposer and the roles under PERM-001, including when other proposals name the same inbox. It MUST NOT substitute another proposal’s proposer or the committer when that actor differs.A committer or a second proposer cannot lend authority to another member’s proposal.
PERM-010The committed registry decidesThe client MUST read the registry entry that governs a write from the committed group context, so that a component registered or a policy changed in the same commit does not govern a write in that commit.
PERM-011Operation selects the policyFor each element change, the client MUST select insert_policy, update_policy, or delete_policy under the element-change table above and MUST reject the proposal if any selected policy denies, except for the membership insertion authorized by DMS-004.
PERM-012Deny by defaultWhen a write targets a component other than COMPONENT_REGISTRY or SUPER_ADMIN_LIST and the committed registry has no entry that decodes as a ComponentMetadata with all three policies present, the client MUST reject the write.
PERM-014Unknown components are judged, not refusedWhen a proposal names a component the client has no built-in definition for, the client MUST evaluate it under the committed registry entry’s component_type and policies, and MUST NOT reject it because the component is unknown.A client that refuses what it does not know forks the group at the first component a newer release adds.
PERM-028DM application policy roleWhen a proposer whose inbox is in a DM’s DM_MEMBERS writes an application component, the client MUST evaluate ALLOW_IF_SUPER_ADMIN and ALLOW_IF_ADMIN as allowed for that proposer, without granting that role for well-known components or membership changes.A DM’s empty super-admin list would otherwise make application policies that use this option impossible to satisfy.
PERM-015Membership proposals use membership policiesWhen a commit adds or removes an inbox from GROUP_MEMBERSHIP, the client MUST evaluate the corresponding insert_policy or delete_policy against each proposer of an Add or Remove for that inbox under PERM-009 and reject a denial, subject to DMS-004 for adds and PERM-003 for removals. The client MUST apply the same policy checks to each standalone Add or Remove proposal.

A registry entry is validated when it is written, so that a corrupt entry never enters the committed registry and every later commit can be judged. The four action policies are validated as complete trees whenever a commit writes the registry, because a commit that leaves one of them malformed would make every later membership or admin change undecidable for every member. The admin list is constrained: its policies are base policies of deny, admin, or super admin, because unrestricted admin assignment would let a member grant itself every admin permission. PERM-026 separately protects the super-admin list.

The registry is written by super admins only, and in a DM by either participant for application entries (PERM-026); no stored value changes that. The permission to update permissions is the super-admin role itself. An app changes a policy through its SDK, which names the action or the metadata field and a policy option; the client writes the registry field the table below names. META section 2 assigns the metadata component ids.

App operationComponentRegistry fieldAccepted options
Add memberGROUP_MEMBERSHIPinsert_policyAllow, Deny, Admin only, Super admin only
Remove memberGROUP_MEMBERSHIPdelete_policyAllow, Deny, Admin only, Super admin only
Add adminADMIN_LISTinsert_policyDeny, Admin only, Super admin only
Remove adminADMIN_LISTdelete_policyDeny, Admin only, Super admin only
Update a metadata fieldThat field’s componentinsert_policy and update_policyAllow, Deny, Admin only, Super admin only
Policy optionWritten as
AllowMETADATA_BASE_POLICY_ALLOW
DenyMETADATA_BASE_POLICY_DENY
Admin onlyMETADATA_BASE_POLICY_ALLOW_IF_ADMIN
Super admin onlyMETADATA_BASE_POLICY_ALLOW_IF_SUPER_ADMIN
IDTitleRequirementWhy
PERM-017Action policies stay well-formedIf the registry a commit produces has no entry for the membership component or the admin list, or an action policy in it is malformed under PERM-008 or is METADATA_BASE_POLICY_ALLOW_IF_SELF_OR_NON_MEMBER at any node, or an admin list policy is not a base of METADATA_BASE_POLICY_DENY, METADATA_BASE_POLICY_ALLOW_IF_ADMIN, or METADATA_BASE_POLICY_ALLOW_IF_SUPER_ADMIN, then the client MUST reject the commit.Every later membership or admin change would be undecidable, and a group whose commits cannot be judged is stuck for every member.
PERM-018Changing a policyAn SDK MUST let an app set each policy in the app-operation table to an option accepted for that operation, encoded under the policy-option table in the named registry fields. It MUST reject Allow for Add admin or Remove admin.

An app creates a non-DM group with All members, Admins only, or custom policies. The creation table states all three policies for each governed component. Allow, Deny, Allow if admin, and Allow if super admin denote the base values in section 2. DMS-002 owns DM creation, including its fixed policies and empty role lists.

Governed policyAll membersAdmins only
Membership: insertAllowAllow if admin
Membership: updateAllowAllow
Membership: deleteAllow if adminAllow if admin
Admin list: insert, update, deleteAllow if super adminAllow if super admin
Group name, description, image URL, app data: insert and updateAllowAllow if admin
Disappearing-message settings, both fields: insert and updateAllow if adminAllow if admin
Minimum protocol version: insert and updateAllow if super adminAllow if super admin
Each metadata field above: deleteAllow if super adminAllow if super admin
Commit-log signer: insert, update, deleteAllow if super adminAllow if super admin
Each immutable component: insertAllow if super adminAllow if super admin
Each immutable component: update and deleteDenyDeny

The registry and super-admin list have no registry policies; PERM-026 applies. PERM-029 sets the policies of USER_DISPLAY_NAME and GROUP_IMAGE. META section 2 owns which component values exist at creation.

Preset recognition reports a policy view, not equality of the registry. The recognition table lists every comparison. Policies compare as ordered trees, including their base values and condition kinds. Metadata policies with a missing field or any malformed node are represented as Deny in this view. An ALLOW_IF_SELF_OR_NON_MEMBER node, which always denies on a scalar metadata field, is represented as Deny in place, and its sibling nodes keep their values. Invalid action policies fail under PERM-017.

Policy in the recognition viewComparison
Membership insert and deleteEqual to the preset’s creation policies
Admin-list insert and deleteEqual to the preset’s creation policies
Group name, description, image URL, app data, both disappearing-message fields, and minimum protocol version: updateEqual to the preset’s creation policies
Authority to change permissionsSuper admin under PERM-026

Membership and admin-list update policies, metadata insert and delete policies, commit-log signer policies, immutable-component policies, and application-component policies are absent from this view. A change to one of them can leave the reported preset unchanged. There is no DM preset result.

IDTitleRequirementWhy
PERM-019A preset writes the tableWhen the client creates a non-DM group with a preset, it MUST write the registry with every policy in that preset’s creation-table column.A preset chosen on one SDK must give every member the same rules.
PERM-029Core field creation policiesWhen a client creates a group or DM, it MUST set USER_DISPLAY_NAME insert, update, and delete policies to ALLOW_IF_SELF_OR_NON_MEMBER; for a group it MUST set GROUP_IMAGE insert, update, and delete policies equal to the respective GROUP_IMAGE_URL policies selected at creation.New members must evaluate writes to the new well-known fields under the same rules.
PERM-024Recognise the exposed policy viewWhen an SDK reports a preset, it MUST report All members or Admins only exactly when the recognition view above equals that preset, and MUST report no preset when neither matches.An app needs a stable meaning for the reported name even when the registry holds other policies.

GMOD section 7 owns requesting leave, authenticating the requester, and completing removal. A request does not grant permission to remove a member. The responder still uses the group’s committed roles and membership policy.

IDTitleRequirementWhy
PERM-025Leave responders need authorityWhen the client acts on a pending leave request by publishing a removal commit, it MUST do so only if its own inbox is a super admin under PERM-001 and the removal is authorized under PERM-003 and PERM-015.A leave request does not grant its reader a role or override a membership policy.

A group’s policies outlive each client version. PERM-014 permits new components whose registered type and policies existing members already understand. A change that makes clients accept different commits needs a version floor before it is used. This includes a new policy value or condition kind, component type, registry encoding, delta mutation, or validation invariant.

The required version is the lowest semantic client version that implements every changed rule the commit depends on. With multiple changed rules, it is the greatest of their first supported versions under GMOD-027. The preceding floor-raising commit uses only rules supported by clients at the old floor. GMOD-022 and GMOD-025 own its validation and the pause it causes.

Active group creation and commit validation use the dictionary and registry. Legacy extension readers, builders, migration helpers, and fixtures do not establish acceptance of those extensions in this workflow.

IDTitleRequirementWhy
PERM-023Changed acceptance follows the floorBefore publishing a commit that depends on a change under which older and newer clients accept different commits, the client MUST ensure that an earlier commit has set the group’s floor to at least the required version defined above. The floor-raising commit MUST use only rules supported at the preceding floor.A same-commit raise cannot protect an older client that cannot validate the new rule.

A client can check a component-specific invariant only when it implements that invariant. Registry policy evaluation alone cannot prevent different acceptance decisions. PERM-023 applies to these changes as well as new encodings.

Evaluation stops at the first child that decides a condition (PERM-007), so an any_condition with an allowing child ahead of a malformed one allows a write. The projection an SDK reports to an app validates the whole tree and reports a malformed metadata policy as deny, so the app can read deny for a field a write to which succeeds. The action policies do not have this gap, because PERM-017 keeps them well-formed at every node.

Legacy groups that carry no app-data dictionary do not satisfy this workflow. Their extension encodings are not an alternate source of authorization here.

The creator’s inbox id is recorded in an immutable component but confers no role. A creator that removes itself from the super-admin list is an ordinary member.