Repository navigation
Members of a group a secret is shared with can't save changes for everyone #1285
Description
Problem
A secret shared with a group is meant to be editable by every member of that group, for everyone it is shared with. Today a member can only change their own copy:
ShareSyncService::writeContextgives a group membereffectiveGrade: 'none'. A grade only comes from team-folder membership (ShareAuthorizationService::resolveGrade→TeamFolderQueryService::resolveGrade), and only the owner or a delegate counts asowner.ShareSyncService::resolveSyncWriterrefuses anyone who isn't the owner, a delegate or awrite/manageteam-folder member. share-save's copy-editor path goes through it (ShareSaveScope::writerOn, again under the lock inassertStillWriter), so a group member's share-save is refused withnot_allowed.- The clients therefore save a group member's edit to their own copy only, with a plain
PUT /api/v1/secrets/{id}. The web app does that without saying so (saveSharedinsrc/services/shareSave.js); the browser extension says "Only your copy changed. The owner and the others it is shared with keep the old value."
The result: the member holds a password nobody else has, and the owner's next save overwrites only the fields the owner changed, so the copy can end up with a mix of both.
Found while testing the browser extension's move to share-save (#1249): Alice shared a login with the admin group, a member changed its username and password, and Alice's secret kept the old values. The project lead and the backend lead confirmed group members should be able to edit it.
Proposed solution
- A user who holds a copy of the source through a group share (
ShareTarget.groupShareIdset) may save for every copy, on share-save's copy-editor path. writeContextanswerseffectiveGrade: 'write'for them, and returnsownerCertificateas it does for awritegrade. Both clients take the copy-editor path forowner,writeandmanageonly (COPY_WRITER_GRADESinsrc/services/shareSave.js,COPY_EDITOR_GRADESinbrowser-extension/src/vault/share-save.ts), so neither client needs a change if the grade name stayswrite.resolveSyncWriteraccepts them, so share-save (and the old/sync) let them write the source and every other copy. Partner shares stay with the owner's next save, as for a team-folder editor (ownerOnly).- Their saves are audited on the source as non-owner writes, as
folder-permission-gradesrequires for a team-folder editor. - Specs:
openspec/specs/sharing-groupgains the requirement.openspec/specs/user-sharing"Recipient updates shared secret" and its share-save delta (openspec/changes/sharing-atomic-share-save/specs/user-sharing/spec.md, "who holds a write grade") are reconciled with it.
- Tests: a group member's share-save reaching the source and the other copies,
writeContextansweringwrite, and a member removed from the group being refused again.
Open questions
1. Should a direct-share recipient also save for everyone?
This decides how far this fix reaches. A direct-share recipient has the same problem as a group member: their edit only changes their own copy. The user-sharing scenario "Recipient updates shared secret" says any recipient's edit reaches everyone; the share-save delta narrowed that to a recipient "who holds a write grade".
- A. Group members only (as proposed). Direct-share recipients keep editing only their own copy, so their copy can still drift from the owner's. The
user-sharingscenario is reworded to say so, and [FEATURE] Choose read-only or edit permission when sharing a secret #1286 later lets the sharer choose per share. - B. Every recipient. One rule: holding a copy lets you save for everyone. Every existing direct share becomes editable for all its holders on upgrade, and an owner can't make one read-only until [FEATURE] Choose read-only or edit permission when sharing a secret #1286 ships.
Suggested: A. It does what was confirmed, and widening write access later is easier than taking it back.
2. Must a save re-check that the user is still in the group?
This decides whether an ex-member can keep editing. Correction to the earlier text: a member who leaves does not keep their copy. UserRemovedFromGroupListener calls GroupShareService::handleMemberLeave, which deletes the copy and its ShareTarget. The listener does catch and only log a failure, though, and then the copy stays.
- A. Trust the copy.
resolveSyncWriteraccepts a caller who holds a copy withgroupShareIdset. No extra lookup, but when the leave cleanup failed, the ex-member keeps saving for everyone until the copy is removed by hand. - B. Also check the group.
resolveSyncWriteralso checks that the caller is still in the group share's group (IGroupManager::isInGroup). One extra lookup per save, and a failed cleanup no longer grants write access, though the ex-member can still read their copy.
Suggested: B. It is cheap, and the planned test "a member removed from the group is refused again" then holds even when the listener fails.
3. Fix #1266 in the same change?
This decides the scope of the change. #1266: writeContext answers any logged-in user for any secret id. This issue changes the same method. Whichever option is picked, the new check must ask whether the caller holds a group-derived copy of the source, not whether the secret id passed in is one. Otherwise writeContext answers write to anyone who knows a copy's id.
- A. Fix both here. The caller check The share write-context answers any logged-in user for any secret #1266 needs (owns the secret, holds a copy, or is a delegate) is the lookup this issue adds, so one helper serves both and
writeContextis changed and tested once. The change grows by a 404 answer and a test for a caller with no access. - B. Keep them apart. This change stays limited to group members. The share write-context answers any logged-in user for any secret #1266 follows on its own and changes
writeContextagain.
Suggested: A.
Anything else?
The browser extension (#1249) needs no change for this: once writeContext answers write for a group member and share-save accepts them, their edit takes the copy-editor path and reaches the owner and every other copy.
Activity
github-actions commentedon Oct 16, 2026
Thank you for opening this issue! It has been assigned to @rubenvdlinde for review.
What happens next:
Triage labels:
triage:accept-feature— accepted as a new featuretriage:accept-bugfix— accepted as a bug fixtriage:needs-info— more information neededtriage:wontfix— will not be addressedtriage:duplicate— duplicate of another issue