42: the first member turn ran clean, and its cwd is unreachable by the member
The privilege drop and the allowlist both held in production. Captured from /proc during the first real member chat turn: uid 1001, parent sudo, HOME and CLAUDE_CONFIG_DIR both inside the member's home, and exactly nine environment variables — the allowlist plus what setpriv supplies. Zero ANTHROPIC_*, POSTGRES_URL or JWT_SECRET. The defect is the cwd. websocket.ts:134 defaults a chat turn to the email-derived general_chat_sessions, and confineUserTree makes every sibling of home the platform's at 0700. So the member's turn starts in a directory it cannot enter — verified, cd fails — and every Bash call in that turn dies instantly, which is what the owner saw as "the shell is unusable". 29 reasoned that those paths stay email-derived because they live under DATA_PATH rather than a home. That is true and it does not follow that they are usable: platform-owned by design means a member's turn can never run there. Fix is a member-owned default, and I would put general_chat_sessions inside their home rather than repointing at the home itself, since it keeps the existing shape for both parties and does not change the owner's path at all. The sibling's 0700 should not be loosened — it holds attachments and email_accounts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# 42 — the first member turn ran, and its cwd is a directory the member cannot enter
|
||||
|
||||
**The privilege drop and the allowlist both held in production.** Captured from `/proc` during the first real
|
||||
member chat turn:
|
||||
|
||||
```
|
||||
uid=1001 parent=sudo secrets=0
|
||||
HOME = …/jg@pertento.ai/home
|
||||
CLAUDE_CONFIG_DIR = …/jg@pertento.ai/home/.claude
|
||||
env = CLAUDE_CODE_ENTRYPOINT CLAUDE_CONFIG_DIR HOME LANG LOGNAME PATH SHELL TERM USER
|
||||
```
|
||||
|
||||
Nine variables, exactly the allowlist plus what `setpriv --init-groups --reset-env` supplies. Zero
|
||||
`ANTHROPIC_*`, zero `POSTGRES_URL`, zero `JWT_SECRET`. That is the first live test of the whole design and it
|
||||
passed.
|
||||
|
||||
## The defect
|
||||
|
||||
```
|
||||
cwd = …/jg@pertento.ai/general_chat_sessions
|
||||
drwx------ pastilhas:pastilhas
|
||||
```
|
||||
|
||||
**The member cannot enter their own turn's working directory.** Verified: `sudo -u green … cd` →
|
||||
`can't cd to …/general_chat_sessions`. Every Bash tool call in that turn fails immediately, including
|
||||
`echo hello`, which is what the owner observed — the agent reported its shell as broken and could not read
|
||||
its own cwd.
|
||||
|
||||
`websocket.ts:134`:
|
||||
|
||||
```ts
|
||||
if (msg.context === 'chat') return msg.cwd?.trim() ? resolveCwd(home, msg.cwd) : ensureGeneralChatSessionsCwd(email);
|
||||
```
|
||||
|
||||
The default chat cwd is email-derived, and `confineUserTree:307-312` deliberately makes every sibling of
|
||||
`home` the platform's at 0700 — *"Every sibling of `home` is the platform's. 700 means traversal alone does
|
||||
not open them."* Correct for `attachments` and `email_accounts`; fatal as a member's cwd.
|
||||
|
||||
`29` reasoned about this and got half of it right:
|
||||
|
||||
> *"`general_chat_sessions` lives under `DATA_PATH/<email>`, not under a home, so those paths stay
|
||||
> email-derived. Collapsing them would have been the same class of mistake as `undefined` meaning 'the owner'."*
|
||||
|
||||
The path being email-derived is true. What did not follow is that it is therefore usable — it is
|
||||
platform-owned by design, so a member's turn can never run there. The email/home split was right; this is the
|
||||
one place where the answer is neither.
|
||||
|
||||
## Fix
|
||||
|
||||
A member's default chat cwd should be somewhere they own. Two options:
|
||||
|
||||
- **their home** — one line, and it matches what an unqualified turn should mean for them
|
||||
- **`~/general_chat_sessions` inside their home** — keeps the "chat sessions have their own directory"
|
||||
behaviour, member-owned, and leaves the platform's sibling untouched
|
||||
|
||||
I would take the second: it preserves the existing shape for both parties, and the owner's path does not
|
||||
change at all. The first is smaller if the directory carries no meaning for a member.
|
||||
|
||||
Do **not** loosen the sibling. It holds `attachments` and `email_accounts`, and the 0700 is doing real work.
|
||||
|
||||
## Also observed
|
||||
|
||||
The member's agent reported every claude.ai connector as needing authorization. Expected: those belong to the
|
||||
Anthropic account they signed in with, not to us, and members get no MCP config from the platform by design.
|
||||
Worth knowing it presents as a wall of "needs auth" rather than as an absence.
|
||||
|
||||
Terminal path unaffected — green's `claude` in the web terminal runs fine as uid 1001 in their own home.
|
||||
Reference in New Issue
Block a user