opencode is owner-only until it carries an identity
a turn on the opencode harness ran as the owner, in the owner's home, whoever asked. handleOpenCodeChat resolves its cwd against getOwnerHomeDir(email), which discards the email it is given, and the sidecar runs one shared `opencode serve` as the service user — sendOpenCodeStreaming accepts userId/email/username and forwards none of them. it carried a comment calling itself owner-only; nothing enforced it. reachable by any account with the `chat` grant, which every role holds by default (DEFAULT_ROLE_CAPABILITIES), and isClaudeModel is a startsWith, so a typo'd model string landed there too. the model is client-supplied and never checked against the catalogue. the same gap on the read side: opencode's session store has no per-user scoping at all, so loadOpenCodeSession/delete/rename take an id and no identity, and the list and live routes returned other people's conversations. so: ChatIdentity carries isOwner as its own fact (not inferred from osUser === null, which holds only while resolveHomeDir refuses a member without one), and every opencode door in chat.ts checks it — list, load, live, delete, rename — plus a refusal on the execution path in handleChat. /chat/models hides opencode from non-owners as a courtesy; the socket refuses regardless. a stopgap, not a design. the fix is to thread identity through the opencode sidecar the way spawnClaudeAsMember does, and TODO.md has been saying so. not fixed here, and worth knowing: a member's session list is still empty and /chat/pwds still 500s, because readdirSync on their ~/.claude/projects is EACCES — claude creates it at mode 700, which zeroes the ACL mask. visible in officer-error.log right now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,14 @@ export type ChatIdentity = {
|
||||
* is the whole reason a member's chat list was empty while their chat worked.
|
||||
*/
|
||||
osUser: string | null;
|
||||
/**
|
||||
* From `resolveHomeDir`. Carried as its own fact rather than inferred from `osUser === null` — that
|
||||
* equivalence holds today only because `resolveHomeDir` refuses a member without one, so reading it as
|
||||
* "is the owner" would silently become wrong the moment that refusal is relaxed.
|
||||
*
|
||||
* Used to gate the OpenCode harness, which is owner-only until it carries an identity. See `chat.ts`.
|
||||
*/
|
||||
isOwner: boolean;
|
||||
};
|
||||
|
||||
const claudeProjectsDir = (home: string): string => join(home, '.claude', 'projects');
|
||||
|
||||
Reference in New Issue
Block a user