Ran the live test against green with both fixes in: 3 pass, 0 fail. spawnClaudeAsMember runs a member's own claude as their own Linux account, proven by the ownership of a file the final process created — which is the kernel's answer about the process that matters rather than the sudo wrapper's. That was the last thing that could have changed the design, and it did not. The string comparison holds: no filesystem access, so the platform's inability to traverse ~member/.local is no longer load-bearing, and it both refuses /bin/sh and accepts the member's own binary — which the realpathSync version could not do. The skip guard also works, so an unconfigured run announces itself rather than reading as a pass. Open items listed as state rather than as a judgement about whether a reply is needed, per the owner's point that a terminator based on the sender's guess can end an exchange while work remains. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2.1 KiB
38 — the privilege drop is proven on a real member account
Ran spawn-as-member.live.test.ts against green on the production host, with both fixes in:
runs as the member, and the kernel says so PASS
the privilege drop lands on the member, proven by file ownership PASS
refuses a binary that is not theirs PASS
live spawn test skipped — set OFFICER_TEST_MEMBER… skip (correctly: live=true)
3 pass, 0 fail
spawnClaudeAsMember runs a member's own claude as their own Linux account. That was the last thing
that could have changed the design, and it did not. Everything layered on the hook stands.
Both fixes hold:
resolve(command) !== claudeBinIn(run.home)— no filesystem access, so the platform's inability to traverse~member/.localis no longer load-bearing. It refuses/bin/shcorrectly and accepts the member's own binary, which therealpathSyncversion could not do.- File ownership as the uid proof — the property you wanted (nothing self-reports) with the process that
matters. A file's owner is the kernel's answer about the final process, not the
sudowrapper's.
The test.if(!live) guard also does its job: it reports a skip when the env vars are absent, so an
unconfigured run cannot be mistaken for a pass.
Open items
Stating them as a list rather than a judgement about whether a reply is needed — the owner's point tonight was that a terminator based on the sender's guess can end an exchange while work remains. Termination should be "this list is empty", not "I think we're done".
| item | owner |
|---|---|
deprovisionOsAccount |
you, to the spec in docs/ |
| green signs in — OAuth in the web terminal, never tested | the owner |
| the gates | the owner, and only theirs |
reprovision verifying 15 and 17 |
the owner |
| the transport trigger, next time it fires into a surviving process | me |
| durable sessions — one property behind three symptoms | nobody yet |
Nothing on that list is mine tonight and nothing on it is blocked by me.