fix(server-mode): close the gaps the review of workspace sharing found

- A Terminal tab a collaborator opens records them (dashboard_runs.started_by):
  its pane token acts as them — and only while still shared with them —
  never as the workspace owner (userFromPaneToken).
- POST /api/run always starts the lane's AI tool: a collaborator gets
  ECOLLABSTART whatever the body says (shell: true no longer slips past).
- A collaborator disabled since no longer locks the share list: kept on it
  (shown disabled) or taken off; only a new addition must be enabled.
- The workspace home tells a collaborator to open an existing session and
  keeps Start disabled instead of failing on every submit.
This commit is contained in:
2026-11-06 10:48:29 +07:00
parent 0bf552b793
commit 54001585e2
16 changed files with 201 additions and 31 deletions
+4 -1
View File
@@ -2111,7 +2111,10 @@ owner or an admin only (`403 ENOTOWNER`), same-origin, each change audited. Loca
mode: `GET` answers an empty list, `PUT` `400 ELOCALMODE`. A user it is shared with
sees and works in it like its owner, but `PATCH {title}`, `reset`/`remove`/`purge`,
`DELETE` and `PUT …/shares` answer them `403 ENOTOWNER`, and a non-shell
`POST /api/lanes/:id/start` or `POST /api/run` `403 ECOLLABSTART`. Users taken off
`POST /api/lanes/:id/start` or any `POST /api/run` `403 ECOLLABSTART`. A Terminal
tab they open (`start {shell: true}`) records them in `dashboard_runs.started_by`:
its pane token acts as them, not the owner. A user disabled since stays on the
list (`disabled: true`) and may be kept or removed; only a new one must be enabled. Users taken off
get the WebSocket message `lane_access` `{laneId, userIds}` (sent to them only). See
`docs/SERVER-MODE.md` → Sharing a workspace.
+10 -2
View File
@@ -257,9 +257,17 @@ decides the account. They can read everything in the owner's home (the owner's
other workspaces, SSH keys, AI logins, the pane tokens of the owner's panes)
and their commits carry the owner's git identity; only AgentHub's `audit_log`
records who did what. The Share dialog says so. Share only with people you
would hand your keyboard to.
would hand your keyboard to. Two consequences follow and are accepted, not
bugs: a collaborator can read the pane token of a pane the OWNER started (and
so act as the owner through the CLI from it), and `ECOLLABSTART` is a
dashboard rule, not a barrier — in a terminal they can run `claude` as the
owner's account. A Terminal tab the collaborator opens records them as its
starter (`dashboard_runs.started_by`), so its own pane token acts as THEM, and
only while the workspace is still shared with them (`userFromPaneToken`).
Every share and unshare is audited (`share workspace <id>` /
A user already on the list who was disabled since stays listed (marked
disabled, granting nothing) and can be taken off; only a NEW addition must be
enabled. Every share and unshare is audited (`share workspace <id>` /
`unshare workspace <id>`). Taking it back sends the users taken off
`lane_access` (their list re-reads) and closes their open terminals on it at
the next attach re-check. Deleting the workspace, or the user, drops the rows;
+6
View File
@@ -1,6 +1,12 @@
# Workspace & session sharing — plan
Status: **phase 1 built 2026-11-06** (workspace sharing; R1 decided as option **(a)** — accept and warn hard). Phases 2–4 not built.
An independent review of phase 1 found and got fixed: a collaborator's Terminal
tab acted as the owner through its pane token (now `dashboard_runs.started_by`),
`POST /api/run {shell:true}` bypassed `ECOLLABSTART`, a disabled collaborator
locked the share list, and the home's prompt box let a collaborator type into a
Start that always failed. Accepted under (a): the owner's own panes' tokens are
readable by a collaborator, and `ECOLLABSTART` is advisory under S3.
Server mode only. In local mode there are no users, so there is nothing to share
and every piece below is a no-op (`isServerMode()`).