give a member a chat cwd they can actually enter
host captured the first live member turn: uid 1001, nine env vars, zero ANTHROPIC_*, zero POSTGRES_URL, zero JWT_SECRET. The privilege drop and the allowlist both held. One defect. The default chat cwd was DATA_PATH/<email>/general_chat_sessions — a sibling of the member's home, which confineUserTree deliberately makes the platform's at 0700 because the other siblings are attachments and email_accounts. So the turn ran in a directory the member cannot enter, and every Bash call failed on its own cwd. The agent reported its shell as broken, which was true. A member's default is now ~member/general_chat_sessions, created as them through runAs. mkdir -p, so it is idempotent per turn and needs no reprovision. The owner's path does not change, and the sibling stays 0700 — loosening it would trade a broken shell for an open directory holding attachments and mail. 29 said this path is email-derived and therefore stays email-derived. True, and it did not follow that it is usable: an email-derived path under DATA_PATH is precisely the set a member is locked out of. Splitting identity from filesystem path was right; assuming the identity side was inert was not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,23 @@ export async function provisionClaudeCli(params: { email: string; osUser: string
|
||||
return { ok: true, binPath, wrote: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* A member's own chat-sessions directory, created as them, inside their home.
|
||||
*
|
||||
* The owner's equivalent lives at `DATA_PATH/<email>/general_chat_sessions` — a SIBLING of the home, which
|
||||
* `confineUserTree` deliberately makes the platform's at 0700 because the other siblings are `attachments`
|
||||
* and `email_accounts`. Correct for those, and fatal as a member's cwd: the first live member turn ran there
|
||||
* and every Bash call failed, because the account could not enter its own working directory.
|
||||
*
|
||||
* So a member's goes inside their home instead. Created through `runAs` because the platform cannot mkdir
|
||||
* into a 0700 home it does not own — and `mkdir -p` is idempotent, so this is safe to call per turn.
|
||||
*/
|
||||
export async function ensureMemberChatCwd(params: { osUser: string; home: string }): Promise<string> {
|
||||
const dir = join(params.home, 'general_chat_sessions');
|
||||
await asMember(params.osUser, ['mkdir', '-p', dir]);
|
||||
return dir;
|
||||
}
|
||||
|
||||
export type ClaudeLoginState = {
|
||||
/** The binary is present and executable in their home. */
|
||||
installed: boolean;
|
||||
|
||||
Reference in New Issue
Block a user