fix(macos): pin PATH on the share sweep's guest round trip - #162
Merged
Conversation
… calls
ssh_macos() { # only ever driven with the piped `python3 -c` shape here
printf '%s\n' "$*" >> "$SSHLOG"
cat > "$STDINLOG"
The header was true when it was written and stopped being true when the
share-refresh mode arms began driving `cmd_claude_macos` and `cmd_shell_macos`.
Those reach the stub as `-t` interactive shapes with nothing piped, and the
unconditional `cat` then drains whatever stdin the SUITE inherited.
Under CI stdin is /dev/null, so `cat` sees EOF and the bug is invisible. On a
developer's terminal it is the TTY: `make offline-tests` stops dead, no error, no
timeout. That is verbatim the failure tests/39_no_stdin_block.sh exists for — see
its header for the two files that shipped in this state before this one.
Measured on a pristine `origin/main` worktree: tests/41 completes in 26 s with an
ordinary stdin and does not finish in 70 s with a FIFO held open. tests/39 did not
catch it because its discovery rule is "any offline test that mentions
`.augur-env`", and tests/41 did not.
Fixed the way tests/36 and tests/38 already do it — discriminate on the command
shape rather than draining unconditionally. `printf %s` is excluded AHEAD of the
`python3 -c` arm because the freshness tripwire's probes carry both: their pipe is
inside the remote command string, so from the stub's side they are unpiped like the
interactive shapes, and matching `python3 -c` alone would regrow this the first time
this file drives them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…h report The sweep's guest round trip invoked a bare `python3`, while augur's own `~/.augur-env` puts `$HOME/.local/bin` FIRST in the PATH of every guest shell — including the non-login shell `ssh <host> <cmd>` runs, which still sources `~/.zshenv`. A guest that plants `~/.local/bin/python3` therefore decides what that round trip returns. verify_macos_egress_locked has pinned its own probes against exactly this since #131 (augur:1748-1755, with the reasoning written out). The sweep did not, and the asymmetry was unexplained. THIS SITE, NOT THE TRIPWIRE'S. It is the mechanism, not the check. A shadowed interpreter here prints `ok=N` while invalidating nothing, so the operator is told their edit reached the guest when it did not — and the edit that did not land may be the one tightening the rules on a guest that is misbehaving. Shadowing only verify_macos_share_freshness would make the self-test lie while real sweeps still worked. It also runs at four attach points AND every refresh interval for the life of the VM, unattended, where the tripwire runs twice per `up`. The tripwire's own three round trips stay unpinned here: that is the check rather than the mechanism, and it carries the ADR-0016 §5 removal signal, which deserves its own argument. NOT A COMPLETE ANSWER, said at the point of use so it is not read as one: PATH pinning does not stop a shell FUNCTION or an alias. `python3() { … }` in `~/.zshenv` — the file augur itself appends to — defeats it, measured. This removes the cheapest PATH-resolved vector and brings the sweep in line with its sibling; it does not make the guest's answer trustworthy, and the sibling's pin has the same hole. One correction rides along in the comment: the sibling says the real tools are safe because a project clone "has no sudo grant to write" system directories. That is false — augur:3202-3204 uses password sudo against project VMs. The reason is the sealed read-only system volume with SIP on, measured on a 26.6 guest: `/` is `apfs, sealed, local, read-only`, `csrutil status: enabled`, `ls -lO /usr/bin/python3` shows `restricted`. One executable line changes; the rest of the augur hunk is comment. tests/41 gains two arms — the pin precedes `python3` on the recorded command line (order is the property: a pin appended after the interpreter has resolved is not a pin), and the NUL list still crosses on stdin untouched. Mutation-checked: removing the pin turns the first arm red against a 126/0 control. Source: docs/security-reviews/2026-07-28-egress.md §6 item 39(a). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
🧭 Egress drift reminderThis PR changes the egress core ( If this change alters the egress posture, please:
Non-blocking reminder. It does not verify correctness (that is the egress fail-closed E2E + |
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
The share sweep's guest round trip invoked a bare
python3:augur's own
~/.augur-envputs$HOME/.local/binfirst in thePATHof every guest shell —including the non-login shell
ssh <host> <cmd>runs, which still sources~/.zshenv. So a guestthat writes
~/.local/bin/python3decides what that call returns.verify_macos_egress_lockedhas pinned its own probes against exactly this since #131(
augur:1748-1755, with the reasoning written out). The sweep did not, and the asymmetry wasunexplained.
Why this call site and not the tripwire's
This one is the mechanism, not the check. A shadowed interpreter here prints
ok=Nwhileinvalidating nothing, so the operator is told their edit reached the guest when it did not — and the
edit that did not land may be the one tightening the rules on a guest that is misbehaving. Shadowing
only
verify_macos_share_freshnesswould make the self-test lie while real sweeps still worked.It also runs far more often, and unattended: four attach points plus every
AUGUR_MACOS_REFRESH_INTERVALseconds for the life of the VM, where the tripwire runs twice perup.The tripwire's own three round trips are still unpinned. That is the remaining half of the finding and
is deliberately not in this PR — it is the check rather than the mechanism, and it is worth its own
argument about the removal signal it carries.
Scope, stated so it is not read as more than it is
Not host code execution: the guest's response is
case-matched and echoed, neverevaled orsourced. Not an egress widening: no invariant in
INVARIANTS.mdcovers guest-returned text. What thisprotects is mechanism integrity — that a sweep reporting success actually invalidated something.
PATH pinning does not stop a shell function or an alias.
python3() { … }in~/.zshenv— thefile augur itself appends to — defeats it, measured. This removes the cheapest PATH-resolved vector
and brings the sweep in line with its sibling; it does not make the guest's answer trustworthy. The
sibling's pin has the same hole, and the new comment says so at the point of use.
One correction carried into the comment
The rationale at
verify_macos_egress_lockedsays the real tools are safe because a project clone"has no sudo grant to write" system directories. That is false —
augur:3202-3204uses passwordsudo against project VMs. The actual reason is the sealed, read-only system volume with SIP on,
measured on a 26.6 guest:
/isapfs, sealed, local, read-only,csrutil status: enabled, andls -lO /usr/bin/python3showsrestricted. The new comment records the correction rather thanrepeating the wrong premise.
Verification
One executable line changes; the rest of the
augurhunk is comment.tests/41gains two arms: the pin precedespython3on the recorded command line (order is theproperty — a pin appended after the interpreter has resolved is not a pin), and the NUL-separated list
still crosses on stdin untouched, so the pin did not cost the wire protocol.
Mutation-checked: removing the pin turns the first arm red against a 126/0 control.
Source:
docs/security-reviews/2026-07-28-egress.md, §6 item 39(a).A second commit, and why it is in this PR
tests/41'sssh_macosstub drained the suite's inherited stdin on every unpiped call:The header was true when written. It stopped being true when the share-refresh mode arms began
driving
cmd_claude_macosandcmd_shell_macos, which reach the stub as-tinteractive shapeswith nothing piped. Under CI stdin is
/dev/nullsocatsees EOF and it is invisible; on adeveloper's terminal it is the TTY and
make offline-testsstops dead with no error and no timeout —verbatim the failure
tests/39_no_stdin_block.shexists for.It was live on
main. Measured on a pristineorigin/mainworktree:tests/41completes in 26 swith an ordinary stdin and does not finish in 70 s with a FIFO held open.
tests/39did not catch itbecause its discovery rule is "any offline test that mentions
.augur-env", andtests/41did not —until the comment in this PR's other commit mentioned it. So the coverage extension is accidental and
the bug it found is not.
Fixed the way
tests/36andtests/38already do it, and ordered first so both commits are greenon their own: without the arms,
tests/41is not yet atests/39candidate, and with the stub alreadyfixed the arms cannot turn it red.
Verification
ALL GREEN (30 scripts)on the branch head. At the intermediate commit,tests/395/0 andtests/41124/0.
tests/41at the head: 126/0, and it completes in 26 s with stdin held open.🤖 Generated with Claude Code