stop handing a member's turn the owner's mcp config, and close the file
host found a live credential exposure while answering my question about what else the member branch missed. It was outside the diff, and predates all of it. MCP-CONFIG WAS NOT BRANCHED. mcpHostPath is module-level, written once at the owner's bootstrap, and was applied to every turn. Its env block carries OFFICER_AUTH_TOKEN, a 30-day JWT signing as the owner — so a member's turn would have spawned their MCP server holding it. Now inside the params.member ternary alongside the binary and the spawn, for the reason already written there: these values say whose turn this is and have to move together. A member gets none. What they should get instead is undecided, and undefined beats the owner's. THE FILE WAS 0644. Written with a bare writeFileSync into a 755 directory, on a host where `terminal` is granted to every role by default — so any member could cat it and hold owner-level API access on loopback. host verified that as a real member on the production host rather than reasoning about it. Now 0600. The mode is the only half of that which is code. The token has been world-readable and stays compromised until rotated, the directory chain above it is still 755, and neither is fixable from a commit. Both written up for the owner in COMMS 05, along with why I am stopping here rather than continuing: the next commit should be the rotation, not more feature work stacked on top of an open exposure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -370,7 +370,15 @@ function createSession(params: ClaudeSpawnStreamingParams, onEvent: (event: Chat
|
||||
},
|
||||
...(subModel ? { model: subModel } : {}),
|
||||
...(resumeId ? { resume: resumeId } : {}),
|
||||
...(mcpHostPath ? { extraArgs: { 'mcp-config': mcpHostPath } } : {}),
|
||||
// The owner's MCP config, and only ever the owner's. `mcpHostPath` is module-level, written once at
|
||||
// this process's bootstrap, and its `env` carries OFFICER_AUTH_TOKEN — a JWT that signs as the owner.
|
||||
// Handing it to a member's turn would either spawn their MCP server holding the owner's token, or (once
|
||||
// that file is 0600, which it now is) point their `claude` at a file it cannot read and fail obscurely.
|
||||
//
|
||||
// So a member gets no MCP config at all. What they SHOULD get — their own generated config with a token
|
||||
// scoped to them, or nothing until per-user tools exist — is an open design question; `undefined` is
|
||||
// the correct answer until it is settled, and is strictly better than the owner's.
|
||||
...(mcpHostPath && !params.member ? { extraArgs: { 'mcp-config': mcpHostPath } } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user