VYPR
Unrated severityNVD Advisory· Published May 22, 2026· Updated May 22, 2026

Sanitize team member data returned by API

CVE-2026-3636

Description

Mattermost versions 11.6.x <= 11.6.0, 11.5.x <= 11.5.3, 11.4.x <= 11.4.4, 10.11.x <= 10.11.14 fail to sanitize team member data when returned via API to users without elevated permissions which allows a user without permissions to get data about team members roles via invoking various team API endpoints.. Mattermost Advisory ID: MMSA-2026-00626

Affected products

1
  • Range: >=11.4.0 <=11.4.4 or >=11.5.0 <=11.5.3 or >=11.6.0 <=11.6.0 or >=10.11.0 <=10.11.14

Patches

4
610a28e9faa9

Automated cherry pick of #35562 (#36095)

https://github.com/mattermost/mattermostMattermost BuildApr 16, 2026Fixed in 10.11.15via llm-release-walk
3 files changed · +297 0
  • server/channels/api4/team.go+38 0 modified
    @@ -604,6 +604,10 @@ func getTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    +	if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
    +		team.SanitizeRoleData(c.AppContext.Session().UserId)
    +	}
    +
     	if err := json.NewEncoder(w).Encode(team); err != nil {
     		c.Logger.Warn("Error while writing response", mlog.Err(err))
     	}
    @@ -642,6 +646,13 @@ func getTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    +	currentUserId := c.AppContext.Session().UserId
    +	if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
    +		for _, m := range members {
    +			m.SanitizeRoleData(currentUserId)
    +		}
    +	}
    +
     	js, err := json.Marshal(members)
     	if err != nil {
     		c.Err = model.NewAppError("getTeamMembers", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
    @@ -681,6 +692,13 @@ func getTeamMembersForUser(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    +	currentUserId := c.AppContext.Session().UserId
    +	for _, m := range members {
    +		if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), m.TeamId, model.PermissionManageTeamRoles) {
    +			m.SanitizeRoleData(currentUserId)
    +		}
    +	}
    +
     	js, err := json.Marshal(members)
     	if err != nil {
     		c.Err = model.NewAppError("getTeamMembersForUser", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
    @@ -724,6 +742,13 @@ func getTeamMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    +	currentUserId := c.AppContext.Session().UserId
    +	if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
    +		for _, m := range members {
    +			m.SanitizeRoleData(currentUserId)
    +		}
    +	}
    +
     	js, err := json.Marshal(members)
     	if err != nil {
     		c.Err = model.NewAppError("getTeamMembersByIds", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
    @@ -830,6 +855,10 @@ func addTeamMember(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddEventObjectType("team_member") // TODO verify this is the final state. should it be the team instead?
     	auditRec.Success()
     
    +	if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
    +		tm.SanitizeRoleData(c.AppContext.Session().UserId)
    +	}
    +
     	w.WriteHeader(http.StatusCreated)
     	if err := json.NewEncoder(w).Encode(tm); err != nil {
     		c.Logger.Warn("Error while writing response", mlog.Err(err))
    @@ -984,6 +1013,15 @@ func addTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    +	currentUserId := c.AppContext.Session().UserId
    +	if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PermissionManageTeamRoles) {
    +		for _, m := range membersWithErrors {
    +			if m.Member != nil {
    +				m.Member.SanitizeRoleData(currentUserId)
    +			}
    +		}
    +	}
    +
     	var (
     		js  []byte
     		err error
    
  • server/channels/api4/team_test.go+248 0 modified
    @@ -4574,3 +4574,251 @@ func TestInvalidateAllEmailInvites(t *testing.T) {
     		CheckOKStatus(t, res)
     	})
     }
    +
    +func setupTeamWithAdminAndMember(t *testing.T, th *TestHelper) *model.Client4 {
    +	t.Helper()
    +	th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam)
    +	require.Nil(t, th.App.Srv().InvalidateAllCaches())
    +	teamAdminClient := th.CreateClient()
    +	_, _, err := teamAdminClient.Login(context.Background(), th.BasicUser2.Email, th.BasicUser2.Password)
    +	require.NoError(t, err)
    +	return teamAdminClient
    +}
    +
    +func assertRoleDataSanitized(t *testing.T, m *model.TeamMember) {
    +	t.Helper()
    +	assert.Empty(t, m.Roles)
    +	assert.Empty(t, m.ExplicitRoles)
    +	assert.False(t, m.SchemeAdmin)
    +	assert.False(t, m.SchemeGuest)
    +	assert.False(t, m.SchemeUser)
    +	assert.Equal(t, int64(-1), m.DeleteAt)
    +}
    +
    +func TestGetTeamMembersRoleDataSanitization(t *testing.T) {
    +	mainHelper.Parallel(t)
    +	th := Setup(t).InitBasic()
    +	teamAdminClient := setupTeamWithAdminAndMember(t, th)
    +
    +	t.Run("non-admin cannot see role data of others", func(t *testing.T) {
    +		members, _, err := th.Client.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
    +		require.NoError(t, err)
    +
    +		for _, m := range members {
    +			if m.UserId != th.BasicUser.Id {
    +				assertRoleDataSanitized(t, m)
    +			}
    +		}
    +	})
    +
    +	t.Run("non-admin sees own role data", func(t *testing.T) {
    +		members, _, err := th.Client.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
    +		require.NoError(t, err)
    +
    +		for _, m := range members {
    +			if m.UserId == th.BasicUser.Id {
    +				assert.True(t, m.SchemeUser)
    +				return
    +			}
    +		}
    +		require.Fail(t, "current user not found in members")
    +	})
    +
    +	t.Run("team admin sees full role data for other user", func(t *testing.T) {
    +		members, _, err := teamAdminClient.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
    +		require.NoError(t, err)
    +
    +		for _, m := range members {
    +			if m.UserId == th.BasicUser.Id {
    +				assert.True(t, m.SchemeUser)
    +				return
    +			}
    +		}
    +		require.Fail(t, "target user not found in members")
    +	})
    +
    +	t.Run("system admin sees full role data", func(t *testing.T) {
    +		members, _, err := th.SystemAdminClient.GetTeamMembers(context.Background(), th.BasicTeam.Id, 0, 100, "")
    +		require.NoError(t, err)
    +
    +		for _, m := range members {
    +			if m.UserId == th.BasicUser2.Id {
    +				assert.True(t, m.SchemeAdmin)
    +				return
    +			}
    +		}
    +		require.Fail(t, "team admin not found in members")
    +	})
    +}
    +
    +func TestGetTeamMemberRoleDataSanitization(t *testing.T) {
    +	mainHelper.Parallel(t)
    +	th := Setup(t).InitBasic()
    +	teamAdminClient := setupTeamWithAdminAndMember(t, th)
    +
    +	t.Run("non-admin cannot see role data of others", func(t *testing.T) {
    +		member, _, err := th.Client.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser2.Id, "")
    +		require.NoError(t, err)
    +		assertRoleDataSanitized(t, member)
    +	})
    +
    +	t.Run("non-admin sees own role data", func(t *testing.T) {
    +		member, _, err := th.Client.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "")
    +		require.NoError(t, err)
    +		assert.True(t, member.SchemeUser)
    +	})
    +
    +	t.Run("team admin sees full role data for other user", func(t *testing.T) {
    +		member, _, err := teamAdminClient.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser.Id, "")
    +		require.NoError(t, err)
    +		assert.True(t, member.SchemeUser)
    +	})
    +
    +	t.Run("system admin sees full role data", func(t *testing.T) {
    +		member, _, err := th.SystemAdminClient.GetTeamMember(context.Background(), th.BasicTeam.Id, th.BasicUser2.Id, "")
    +		require.NoError(t, err)
    +		assert.True(t, member.SchemeAdmin)
    +	})
    +}
    +
    +func TestGetTeamMembersByIdsRoleDataSanitization(t *testing.T) {
    +	mainHelper.Parallel(t)
    +	th := Setup(t).InitBasic()
    +	teamAdminClient := setupTeamWithAdminAndMember(t, th)
    +
    +	t.Run("non-admin cannot see role data of others", func(t *testing.T) {
    +		members, _, err := th.Client.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser2.Id})
    +		require.NoError(t, err)
    +		require.Len(t, members, 1)
    +		assertRoleDataSanitized(t, members[0])
    +	})
    +
    +	t.Run("non-admin sees own role data", func(t *testing.T) {
    +		members, _, err := th.Client.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser.Id})
    +		require.NoError(t, err)
    +		require.Len(t, members, 1)
    +		assert.True(t, members[0].SchemeUser)
    +	})
    +
    +	t.Run("team admin sees full role data for other user", func(t *testing.T) {
    +		members, _, err := teamAdminClient.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser.Id})
    +		require.NoError(t, err)
    +		require.Len(t, members, 1)
    +		assert.True(t, members[0].SchemeUser)
    +	})
    +
    +	t.Run("system admin sees full role data", func(t *testing.T) {
    +		members, _, err := th.SystemAdminClient.GetTeamMembersByIds(context.Background(), th.BasicTeam.Id, []string{th.BasicUser2.Id})
    +		require.NoError(t, err)
    +		require.Len(t, members, 1)
    +		assert.True(t, members[0].SchemeAdmin)
    +	})
    +}
    +
    +func TestAddTeamMemberRoleDataSanitization(t *testing.T) {
    +	mainHelper.Parallel(t)
    +	th := Setup(t).InitBasic()
    +	teamAdminClient := setupTeamWithAdminAndMember(t, th)
    +
    +	t.Run("team admin adding user sees full role data in response", func(t *testing.T) {
    +		newUser := th.CreateUser()
    +		tm, _, err := teamAdminClient.AddTeamMember(context.Background(), th.BasicTeam.Id, newUser.Id)
    +		require.NoError(t, err)
    +		assert.True(t, tm.SchemeUser)
    +	})
    +
    +	t.Run("non-admin adding user sees sanitized role data in response", func(t *testing.T) {
    +		defaultRolePermissions := th.SaveDefaultRolePermissions()
    +		defer th.RestoreDefaultRolePermissions(defaultRolePermissions)
    +		th.AddPermissionToRole(model.PermissionAddUserToTeam.Id, model.TeamUserRoleId)
    +
    +		newUser := th.CreateUser()
    +		tm, _, err := th.Client.AddTeamMember(context.Background(), th.BasicTeam.Id, newUser.Id)
    +		require.NoError(t, err)
    +		assertRoleDataSanitized(t, tm)
    +	})
    +}
    +
    +func TestAddTeamMembersRoleDataSanitization(t *testing.T) {
    +	mainHelper.Parallel(t)
    +	th := Setup(t).InitBasic()
    +	teamAdminClient := setupTeamWithAdminAndMember(t, th)
    +
    +	t.Run("team admin adding users sees full role data in response", func(t *testing.T) {
    +		newUser := th.CreateUser()
    +		members, _, err := teamAdminClient.AddTeamMembers(context.Background(), th.BasicTeam.Id, []string{newUser.Id})
    +		require.NoError(t, err)
    +		require.Len(t, members, 1)
    +		assert.True(t, members[0].SchemeUser)
    +	})
    +
    +	t.Run("non-admin adding users sees sanitized role data in response", func(t *testing.T) {
    +		defaultRolePermissions := th.SaveDefaultRolePermissions()
    +		defer th.RestoreDefaultRolePermissions(defaultRolePermissions)
    +		th.AddPermissionToRole(model.PermissionAddUserToTeam.Id, model.TeamUserRoleId)
    +
    +		newUser := th.CreateUser()
    +		members, _, err := th.Client.AddTeamMembers(context.Background(), th.BasicTeam.Id, []string{newUser.Id})
    +		require.NoError(t, err)
    +		require.Len(t, members, 1)
    +		assertRoleDataSanitized(t, members[0])
    +	})
    +}
    +
    +func TestGetTeamMembersForUserRoleDataSanitization(t *testing.T) {
    +	mainHelper.Parallel(t)
    +	th := Setup(t).InitBasic()
    +	teamAdminClient := setupTeamWithAdminAndMember(t, th)
    +
    +	t.Run("user sees own role data", func(t *testing.T) {
    +		members, _, err := th.Client.GetTeamMembersForUser(context.Background(), th.BasicUser.Id, "")
    +		require.NoError(t, err)
    +		require.NotEmpty(t, members)
    +		for _, m := range members {
    +			assert.True(t, m.SchemeUser)
    +		}
    +	})
    +
    +	t.Run("non-admin cannot see role data of another user", func(t *testing.T) {
    +		defaultRolePermissions := th.SaveDefaultRolePermissions()
    +		defer th.RestoreDefaultRolePermissions(defaultRolePermissions)
    +		th.AddPermissionToRole(model.PermissionReadOtherUsersTeams.Id, model.SystemUserRoleId)
    +
    +		members, _, err := th.Client.GetTeamMembersForUser(context.Background(), th.BasicUser2.Id, "")
    +		require.NoError(t, err)
    +		require.NotEmpty(t, members)
    +		for _, m := range members {
    +			assertRoleDataSanitized(t, m)
    +		}
    +	})
    +
    +	t.Run("team admin sees full role data for other user in managed team", func(t *testing.T) {
    +		defaultRolePermissions := th.SaveDefaultRolePermissions()
    +		defer th.RestoreDefaultRolePermissions(defaultRolePermissions)
    +		th.AddPermissionToRole(model.PermissionReadOtherUsersTeams.Id, model.SystemUserRoleId)
    +
    +		members, _, err := teamAdminClient.GetTeamMembersForUser(context.Background(), th.BasicUser.Id, "")
    +		require.NoError(t, err)
    +		require.NotEmpty(t, members)
    +		for _, m := range members {
    +			if m.TeamId == th.BasicTeam.Id {
    +				assert.True(t, m.SchemeUser)
    +				return
    +			}
    +		}
    +		require.Fail(t, "basic team membership not found")
    +	})
    +
    +	t.Run("system admin sees full role data", func(t *testing.T) {
    +		members, _, err := th.SystemAdminClient.GetTeamMembersForUser(context.Background(), th.BasicUser2.Id, "")
    +		require.NoError(t, err)
    +		require.NotEmpty(t, members)
    +		for _, m := range members {
    +			if m.TeamId == th.BasicTeam.Id {
    +				assert.True(t, m.SchemeAdmin)
    +				return
    +			}
    +		}
    +		require.Fail(t, "basic team membership not found")
    +	})
    +}
    
  • server/public/model/team_member.go+11 0 modified
    @@ -142,3 +142,14 @@ func (o *TeamMember) PreUpdate() {
     func (o *TeamMember) GetRoles() []string {
     	return strings.Fields(o.Roles)
     }
    +
    +func (o *TeamMember) SanitizeRoleData(currentUserId string) {
    +	if o.UserId != currentUserId {
    +		o.Roles = ""
    +		o.ExplicitRoles = ""
    +		o.SchemeAdmin = false
    +		o.SchemeGuest = false
    +		o.SchemeUser = false
    +		o.DeleteAt = -1
    +	}
    +}
    
ffee10a61081

Bump Boards FIPS version to v9.2.4 (#36165) (#36168)

https://github.com/mattermost/mattermostMattermost BuildApr 17, 2026Fixed in 11.6.1via release-tag
1 file changed · +1 1
  • server/Makefile+1 1 modified
    @@ -183,7 +183,7 @@ PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.3.0
     ifeq ($(FIPS_ENABLED),true)
     	PLUGIN_PACKAGES  = mattermost-plugin-playbooks-v2.8.0%2Bc4449ac-fips
     	PLUGIN_PACKAGES += mattermost-plugin-agents-v1.7.2%2B866e2dd-fips
    -	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.2%2B4282c63-fips
    +	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.4%2B5855fe1-fips
     endif
     
     EE_PACKAGES=$(shell $(GO) list $(BUILD_ENTERPRISE_DIR)/...)
    
292d4b7ea15b

Bump Boards FIPS version to v9.2.4 (#36165) (#36170)

https://github.com/mattermost/mattermostMattermost BuildApr 17, 2026Fixed in 11.4.5via release-tag
1 file changed · +1 1
  • server/Makefile+1 1 modified
    @@ -174,7 +174,7 @@ PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.3.0
     ifeq ($(FIPS_ENABLED),true)
     	PLUGIN_PACKAGES  = mattermost-plugin-playbooks-v2.8.0%2Bc4449ac-fips
     	PLUGIN_PACKAGES += mattermost-plugin-agents-v1.7.2%2B866e2dd-fips
    -	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.2%2B4282c63-fips
    +	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.4%2B5855fe1-fips
     endif
     
     EE_PACKAGES=$(shell $(GO) list $(BUILD_ENTERPRISE_DIR)/...)
    
fff6ab3a5851

Bump Boards FIPS version to v9.2.4 (#36165) (#36169)

https://github.com/mattermost/mattermostMattermost BuildApr 17, 2026Fixed in 11.5.4via release-tag
1 file changed · +1 1
  • server/Makefile+1 1 modified
    @@ -176,7 +176,7 @@ PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.3.0
     ifeq ($(FIPS_ENABLED),true)
     	PLUGIN_PACKAGES  = mattermost-plugin-playbooks-v2.8.0%2Bc4449ac-fips
     	PLUGIN_PACKAGES += mattermost-plugin-agents-v1.7.2%2B866e2dd-fips
    -	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.2%2B4282c63-fips
    +	PLUGIN_PACKAGES += mattermost-plugin-boards-v9.2.4%2B5855fe1-fips
     endif
     
     EE_PACKAGES=$(shell $(GO) list $(BUILD_ENTERPRISE_DIR)/...)
    

Vulnerability mechanics

Root cause

"Missing authorization check for PermissionManageTeamRoles before returning team member role data in API responses allows unprivileged users to see the roles of other team members."

Attack vector

An attacker with a valid low-privileged session (e.g., a regular team member) can invoke any of the team member API endpoints — `GET /api/v4/teams/{team_id}/members`, `GET /api/v4/teams/{team_id}/members/{user_id}`, `GET /api/v4/users/{user_id}/teams/{team_id}/members`, `POST /api/v4/teams/{team_id}/members`, or `POST /api/v4/teams/{team_id}/members/batch` — to retrieve `TeamMember` objects for other users. Because the server did not check whether the caller had `PermissionManageTeamRoles` before returning role data, the response included sensitive fields such as `Roles`, `ExplicitRoles`, `SchemeAdmin`, `SchemeGuest`, and `SchemeUser` for every team member. No special payload or crafted input is required; simply calling the standard API endpoints as an unprivileged user leaks the role assignments of all other team members.

Affected code

The vulnerability exists in `server/channels/api4/team.go` across five API handlers: `getTeamMember`, `getTeamMembers`, `getTeamMembersForUser`, `getTeamMembersByIds`, `addTeamMember`, and `addTeamMembers`. These endpoints returned `TeamMember` objects without sanitizing role-related fields (Roles, ExplicitRoles, SchemeAdmin, SchemeGuest, SchemeUser, DeleteAt) for users lacking the `PermissionManageTeamRoles` permission. The fix introduces a new `SanitizeRoleData` method in `server/public/model/team_member.go` that clears these fields when the requesting user is not the target user and lacks the required permission.

What the fix does

The patch adds a `SanitizeRoleData` method to the `TeamMember` model in `server/public/model/team_member.go` [patch_id=1693050]. This method clears the `Roles`, `ExplicitRoles`, `SchemeAdmin`, `SchemeGuest`, `SchemeUser`, and `DeleteAt` fields when the requesting user's ID does not match the team member's user ID. In each of the six API handlers in `server/channels/api4/team.go`, the patch inserts a permission check for `PermissionManageTeamRoles` before encoding the response. If the caller lacks this permission, `SanitizeRoleData` is called on each returned `TeamMember` object (or on the member in the response for add endpoints), ensuring that only the user's own role data is visible. This closes the information disclosure by enforcing that role visibility requires either team management privileges or self-ownership of the membership record.

Preconditions

  • authAttacker must have a valid session as a regular team member (non-admin, non-team-admin) on the target team.
  • networkAttacker must be able to send HTTP requests to one of the vulnerable team member API endpoints.
  • configNo special configuration or elevated privileges are required; default Mattermost permissions suffice.

Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

1

News mentions

0

No linked articles in our index yet.