Skip to content

fix(workspaces): sharing lends no more than the sharer holds; restore archived workspaces - #550

Merged
huyplb merged 5 commits into
mainfrom
feat/workspace-review
Oct 25, 2026
Merged

huyplb merged 5 commits into
mainfrom
feat/workspace-review

Conversation

@huyplb

@huyplb huyplb commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

What this fixes

Sharing a personal workspace could lend more than its account holds (security). Everyone manages the members of their own workspace, whatever their role there. So a viewer account could invite a colleague as owner. The colleague then had DDL, DML, GRANT and schema migrate on the viewer's saved connections — credentials an admin handed out for reading.

Now:

  • A personal workspace's ceiling is its account's role (workspaceRoleCeiling). Every read of a member's role goes through it (effectiveMemberRole / cappedRole), including rows stored before this rule or by an older build. That covers the session's permissions, the workspace list, the members list, the admin list of owners, and workspace scripts. Scripts read the raw role, so a capped colleague could still save and delete them.
  • Nobody gives more than they hold (assertRoleGrantable). This applies to inviting and to changing a member's role, on the server. The settings dialog offers only those roles and says why (workspaceGrantCeiling in @foxschema/shared, the same rule on both sides).
  • One permission check (requireWorkspacePermission) replaces the copies in the directory and invite services.

Archived workspaces can come back. Archiving promised its data is kept, but nothing could restore it. Admins now get Restore in Access control → Workspaces (POST /workspaces/:id/restore).

Escape closes the workspace settings dialog. The listener is registered once and calls the latest onClose.

Smaller:

  • The members list sorts by the role as it takes effect.
  • The admin row shows Restore or the live controls.
  • e2e helpers live in helpers/accounts.ts (apiCall, signInAs, browserAs).

Tests

Every fix was tested both ways: the test fails without the fix and passes with it.

  • workspace-role-ceiling.test.ts: invite and raise refused above the ceiling, the ceiling follows the account role, a stored-above-ceiling role acts at the ceiling everywhere (permissions, list, members order, scripts, admin owners), and the capped member cannot manage members.
  • WorkspaceSettingsDialog.test.tsx: the roles on offer per account and workspace, Escape, and Escape after a re-render.
  • e2e workspaces-sharing-limits.test.ts (new) plus workspaces.test.ts: 10/10 pass against this branch's servers.
  • Typecheck (web, e2e, cli), eslint ., security lint: clean.
  • Unit suite: 6425 pass. The one failure is the code-cell-execute timing test. It is not touched here, it flakes under machine load, and it failed a different case when re-run alone.

Follow-ups (not in this PR)

  • The ceiling caps the role; a custom role mapping that gives viewers more is not intersected with the account's own permissions.
  • The dialogs could share a useDismissable hook.
  • Four other e2e specs still carry their own api() copies.

🤖 Generated with Claude Code


Note

High Risk
Changes authorization for workspace invites, member roles, and effective permissions (including scripts), plus a new admin restore path—security-sensitive workspace and access-control behavior.

Overview
Closes a workspace sharing privilege-escalation hole: in personal workspaces, members can no longer invite or promote others above what the sharer’s account role allows. The server now caps effective roles on every read (effectiveMemberRole / cappedRole) and rejects over-grants on invites and member updates (assertRoleGrantable), with shared helpers (workspaceGrantCeiling, workspaceRoleAtMost) mirrored in Workspace settings (disabled role options + workspace-invite-ceiling copy).

Admins can restore archived workspaces via POST /workspaces/:id/restore and a Restore control in Access control → Workspaces (archived rows show Restore instead of join/open/members/archive).

Smaller UX: Escape dismisses the workspace rail menu and settings dialog. E2E adds helpers/accounts.ts and workspaces-sharing-limits.test.ts, wired into the workspaces suite in run-all.mjs.

Reviewed by Cursor Bugbot for commit c530b2a. Bugbot is set up for automated code reviews on this repo. Configure here.

huyplb and others added 5 commits October 25, 2026 09:28
# Conflicts:
#	apps/web/src/frontend/features/workspaces/index.ts
#	docs/testing/TEST_IDS.md
…ipts capped too

- effectiveMemberRole / cappedRole: every read of a member's role goes
  through the ceiling. Workspace scripts read the raw stored role, so a
  colleague stored as editor in a viewer's personal workspace could still
  save and delete its scripts; the admin list counted them as owners.
- requireWorkspacePermission + assertRoleGrantable replace the copies in
  the directory and invite services.
- workspaceGrantCeiling in @foxschema/shared: the dialog offers what the
  server accepts, from the same rule.
- Settings dialog builds its role options once and registers Escape once.
- e2e: apiCall / signInAs / browserAs helpers in helpers/accounts.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A colleague stored as owner of a viewer's personal workspace is a viewer
there, but the list sorted by the stored role and put them first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 7146b014-311c-4104-b553-9c25b35cebe2)

@github-actions

Copy link
Copy Markdown
Contributor

Test IDs

Added (2)

  • admin-workspace-restore-{…}
  • workspace-invite-ceiling

{…} is a part filled in at run time. The full tree: docs/testing/TEST_IDS.md.

@huyplb
huyplb merged commit 03d1c2c into main Oct 25, 2026
13 checks passed
@huyplb
huyplb deleted the feat/workspace-review branch October 25, 2026 17:19
huyplb added a commit that referenced this pull request Oct 25, 2026
docs(feature-map): update after #550 – #555 and the P1 test passes
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.

1 participant