close the anthropic proxy secret, and re-close it on every boot

data/sidecar/claude-state.json holds proxySecret, and that string IS the
owner's Claude Max subscription: officer-anthropic-proxy accepts it as
x-api-key, swaps it for the owner's OAuth bearer and forwards to
api.anthropic.com. it listens on 127.0.0.1, which every local account can
reach.

it was 664 in a 775 directory. nothing had ever set a mode - mkdirSync and
Bun.write at ubuntu's default umask of 002. confirmed by reading the live
secret as 'nobody', an account with no relationship to the platform, and by
confirming the proxy 401s without the key so the secret is the only control.

this does NOT mean members shared the subscription. spawnClaudeAsMember runs
their claude through setpriv --reset-env with a six-name allowlist, so
ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY cannot cross into it and their turns
go straight to anthropic on their own OAuth. that design is intact - it could
just be walked around by anyone with a shell.

fixed twice on purpose. state.ts sets 0600/0700 on every write, which is the
authoritative fix, and on every write rather than at creation because
writeFileSync leaves an existing file's mode alone. secureRuntimeSecrets()
sweeps at boot because officer-anthropic-proxy is a separate pm2 process that
can recreate the file hours after officer started, and because every existing
install has the old mode until something repairs it.

it repairs rather than refusing to boot, unlike assertSecretsClosed beside it.
the line is who wrote the file: .env is the installer's and we must not guess
at it; this is our own output at the machine's umask, so refusing to start
would punish the user for our bug.

the per-account copies are swept too. no live secret in them today, but they
map session keys to transcript uuids for named accounts.

tests verified to bite: removing the chmod while still reporting success fails
four of them.

a chmod does not undo exposure - the secret still needs rotating on the
production host. TODO.md has the sequence, including that officer-claude-code
caches it and must be restarted alongside the proxy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 22:27:09 +00:00
co-authored by Claude Opus 5
parent 63f866750b
commit 56ddb6bebf
6 changed files with 280 additions and 5 deletions
+24
View File
@@ -402,6 +402,30 @@ outside the example's new README:
## Waiting on the owner to run it
- [ ] **Rotate the Anthropic proxy secret on the production host.** The file modes are fixed in code
(`state.ts` writes 0600, `secureRuntimeSecrets()` sweeps every boot) and were repaired on the dev box
— but `data/sidecar/claude-state.json` was world-readable for the whole life of the install, so any
account that read it still holds a key to the owner's Claude Max subscription. **A chmod closes the
door; it does not change the lock.**
On the production host — which is the one with six accounts and real members — this matters and the
dev box does not.
```bash
pm2 restart officer # runs the sweep; check the log for what it closed
# then rotate:
# stop officer-anthropic-proxy, clear "proxySecret" in data/sidecar/claude-state.json, start it
pm2 restart officer-anthropic-proxy officer-claude-code
```
**Both sidecars, together.** `officer-claude-code` caches the secret in `process.env.ANTHROPIC_API_KEY`
(`ensureAnthropicEnv` returns early once it is set), so rotating the proxy alone makes every chat turn
401 until the agent sidecar is also restarted.
Worth checking while there: `ls -la $OFFICER_ROOT/data/sidecar/` on production, and whether any member
account has a shell history or a `~/.claude/settings.json` naming `ANTHROPIC_BASE_URL` — that would be
evidence someone actually used it, rather than merely could have.
- [ ] **Plan mode is built and has never executed.** Landed `1b811dfa` on 2026-08-17, typechecks, unit
tested at the translation layer — and not one line of it has run on a machine. The owner was away
when it was finished and asked to be reminded, which is what this entry is.