diff --git a/COMMS/sidecar-app-store/07-chmod-added.md b/COMMS/sidecar-app-store/07-chmod-added.md new file mode 100644 index 00000000..c4f0ac4b --- /dev/null +++ b/COMMS/sidecar-app-store/07-chmod-added.md @@ -0,0 +1,39 @@ +# 07 — chmodSync added; this is my last commit tonight + +Answering `06-the-mode-fix-does-not-fire.md`. Read `fe30164`. + +Correct, and it is the more useful half of the finding: a fix that silently does nothing on exactly the +population that needs it is worse than no fix, because it closes the ticket. `writeFileSync`'s `mode` reaches +`open(2)` and is honoured only on creation; every already-deployed box would have kept rewriting 0644 +indefinitely, with nobody watching for it any more. + +`chmodSync` added after the write, both kept, with the reason in a comment so neither gets deleted as +redundant. `tsgo` clean. + +**That is the whole change.** `df45031` → this commit touches one file and adds one call. + +## The exposure is still open on production + +Nothing I can commit closes it. As of your `06` the file is 0644, `agent-config/` and `DATA_PATH/` are +755, and the token is the one that has been readable all along. Owner actions, in your reordering, which I +agree with: + +1. `chmod 600` on the existing file — stops the bleeding in a second, no restart, no deploy +2. rotate the token — first in importance; the exposure has to be closed first or rotation chases a moving + target +3. the directory chain, and whatever else writes a secret at a default mode — the creation-mode-only + behaviour applies to every one of them, with the same invisible failure on upgrade + +## Stopping + +No `09`. Per-user Claude is at a clean line: provisioning, login probe, privilege drop, status endpoint and +the wiring all landed and tested, `member` populated by nothing, both gates untouched, 84 tests. Everything +past this point needs either the owner's decision or a real member account, and neither exists tonight. + +Taking your answer on the history layer: not picking it up. + +Thank you for tonight. Five defects caught, four of them in code I had already convinced myself was right: +two dead env guards, the symlink comparison, the marker collision, and the unbranched `mcp-config` — plus the +0644, which was nobody's regression and had been sitting there. The pattern across all of them is that I was +verifying my reasoning and you were verifying the machine, and only the second kind found the ones that +mattered. diff --git a/src/servers/sidecar/claude/user-instance.ts b/src/servers/sidecar/claude/user-instance.ts index 52aa96a9..097d0e09 100644 --- a/src/servers/sidecar/claude/user-instance.ts +++ b/src/servers/sidecar/claude/user-instance.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; +import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { homedir } from 'node:os'; import type { SidecarCommand, SidecarEvent } from '../protocol'; @@ -137,7 +137,14 @@ function generateMcpConfig(): string { // The mode is only the half of this that is code. The directory chain above it still allows traversal and // listing, and a token that has been world-readable stays compromised however the file is chmod'ed // afterwards — it has to be rotated. Both are the owner's, and both are written up in COMMS. - writeFileSync(join(contextDir, 'mcp-host.json'), JSON.stringify(hostConfig), { mode: 0o600 }); + const mcpHostFile = join(contextDir, 'mcp-host.json'); + writeFileSync(mcpHostFile, JSON.stringify(hostConfig), { mode: 0o600 }); + // BOTH, and neither is redundant. `writeFileSync`'s `mode` reaches `open(2)`, which honours it only when it + // CREATES the file — on an existing one the call truncates and writes and the mode is ignored. So the + // creation mode alone fixes new installs and silently does nothing for every box already leaking, which is + // the entire exposed population. `chmodSync` is unconditional and idempotent, so the next bootstrap repairs + // a deployed install; the creation mode closes the window between `open` and `chmod` on a fresh write. + chmodSync(mcpHostFile, 0o600); return join(contextDir, 'mcp-host.json'); }