fix(teams): exempt a member's own membership from the privacy policy by HarshMN2345 · Pull Request #13786 · appwrite/appwrite · GitHub
Skip to content

fix(teams): exempt a member's own membership from the privacy policy - #13786

Merged
HarshMN2345 merged 3 commits into
mainfrom
fix/membership-privacy-self
Sep 21, 2026
Merged

HarshMN2345 merged 3 commits into
mainfrom
fix/membership-privacy-self

Conversation

@HarshMN2345

Copy link
Copy Markdown
Member

What does this PR do?

The membership privacy policy is documented as controlling what a member can see about other members:

Updating this policy allows you to control if team members can see other members information. When enabled, all team members can see ID, name, email, phone number, and MFA status of other members.

But GET /v1/teams/:teamId/memberships and GET /v1/teams/:teamId/memberships/:membershipId applied it to every membership in the response, the requester's own included.

New projects are created with every flag disabled (membershipsUserId => false in Projects/Http/Projects/Create.php) — the ?? true fallback in the endpoints only covers projects that predate the policy. So on a fresh project a standard user listing team memberships gets userId: "" on every row, including their own, and has no way to tell which membership is theirs.

That is not just cosmetic. DELETE /v1/teams/:teamId/memberships/:membershipId explicitly supports self-removal, but finding your own membershipId requires matching on userId — so a member could not leave a team. The same applies to requests authenticated with a user JWT from a function, which is neither privileged nor a key.

This exempts the requester's own membership from the policy, treating it like a privileged read. It reuses the userInternalId === $user->getSequence() check that Memberships/Delete.php and Memberships/Update.php already use to recognise the caller's own membership. Nothing is actually concealed by the old behaviour — GET /v1/account returns every one of these fields about yourself.

Other members' details are unaffected: the policy still governs them exactly as before, and API keys / admin mode are unchanged.

Test Plan

tests/e2e/Services/Teams and tests/e2e/Services/Project.

Three existing tests asserted the old behaviour on a team where the viewer was the only member, so they now cover the distinction between self and other rather than conflating them:

  • TeamsCustomClientTest::testGetMembershipPrivacy — invites a second member. Policy assertions target that member; the viewer's own row is asserted visible in all three policy states.
  • PoliciesMembershipPrivacyIntegrationTest::testMembershipPrivacyIntegration — step 4 now asserts user2's row is hidden from user1 and user1's own row is not.
  • testMembershipUserAccessedAtDefault / testMembershipUserAccessedAtPolicy — createTeamWithMember now creates a signed-in viewer plus one other member, so the userAccessedAt policy is exercised against the other member.

Manual reproduction from the issue:

  1. Create a project, add a user under Auth, create a team, add the user to the team.
  2. Sign in as that user with a client SDK and call teams.listMemberships(teamId).
  3. Before: userId is "" on the user's own membership. After: it is their user ID, while any other member's row still follows the project policy.

composer lint and composer analyze pass on the four changed files.

Related PRs and Issues

Checklist

  • Have you read the Contributing Guidelines on issues?
  • If the PR includes a change to an API's metadata (desc, label, params, etc.), does it also include updated API specs and example docs? — no metadata change, response behaviour only

@greptile-apps

greptile-apps Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

The membership privacy policy is documented as controlling what members
can see about *other* members, but it was applied to every membership in
the list, including the requester's own. New projects are created with
`membershipsUserId` disabled, so a standard user listing team
memberships got an empty `userId` on every row - including their own -
and could no longer tell which membership was theirs. That also left
them unable to leave a team, since removing a membership needs its ID.

Treat the requester's own membership like a privileged read in both
`GET /v1/teams/:teamId/memberships` and
`GET /v1/teams/:teamId/memberships/:membershipId`, matching how
`Memberships/Delete.php` and `Memberships/Update.php` already identify
the caller's own membership. None of these fields are secret from the
member anyway - `GET /v1/account` returns all of them.

Fixes #13767
@HarshMN2345
HarshMN2345 force-pushed the fix/membership-privacy-self branch from 04daa29 to 2f4091f Compare September 21, 2026 06:00
@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

✨ Benchmark results

Comparing main (before) → fix/membership-privacy-self (after).

Metric Before After Change
🚀 Requests/sec 197.82 198.11 ⚪ +0.1%
⏱️ Latency P50 87.48 ms 88.08 ms ⚪ +0.7%
⏱️ Latency P95 204.11 ms 204.73 ms ⚪ +0.3%
Per-scenario breakdown & investigation details

Metrics below reflect the current branch (after). Δ P95 compares against the base.

Scenario P50 (ms) P95 (ms) Requests RPS Δ P95 (ms)
API total 88.08 204.73 12,540 198.11 +0.62
Account 167.76 312.42 660 10.98 +0.73
TablesDB 84.52 156.39 6,820 110.15 -14.99
Storage 83.87 180.59 3,300 54.99 +11.2
Functions 127.52 245.29 1,760 29.97 +9.16

Top API waits (after)

API request Max wait (ms)
storage.buckets.create 466.03
functions.delete 446.97
account.name.update 441.41
functions.create 439.61
functions.variables.update 406.24

@Meldiron Meldiron left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, lets just confirm we really need this

@HarshMN2345
HarshMN2345 merged commit 8aca509 into main Sep 21, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standard users are not sent userId when listing memberships

2 participants