You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reported on omac 0.7.1 (macOS 26.6.1, arm64). omac start opencode -- --version
exits 71 with
sandbox-exec: execvp() of 'opencode' failed: No such file or directory
on a machine where which opencode resolves fine. The same binary launched by
its fully resolved path (omac start opencode -inner <realpath> -- --version)
runs and prints its version. So the harness is present and executable — what
fails is reaching it from inside the sandbox.
Problem / What
The reporter's distinguishing property is Homebrew in a non-default prefix
(~/homebrew), which no baseline grant covers — internal/sandboxprofile/baseline.go:106 grants /opt/homebrew, /usr/local/Cellar, /usr/local/opt only. The harness is then reached through
a three-hop symlink chain:
~/homebrew/bin/opencode
-> ~/homebrew/Cellar/opencode/1.18.15/bin/opencode <- never granted
-> ~/homebrew/Cellar/opencode/1.18.15/libexec/.../opencode-ai/bin/opencode.exe
resolveCommandBinaryDirs (internal/sandboxrun/resolve_binary.go:47) grants
only the two ends of that chain — Dir(PATH entry) and Dir(EvalSymlinks(...)). The kernel resolves a chain one hop at a time and
needs read access to each link it reads on the way, so resolution dies on the
middle directory. Seatbelt reports the denied in-sandbox PATH lookup of a bare argv[0] as ENOENT, which names neither the deny nor the path.
Isolated from omac with a hand-written SBPL granting exactly the two ends —
three spellings of the same file behave differently, which is what pins the
cause to path resolution rather than to exec:
argv[0]
result
opencode (bare — what omac passes)
execvp() ... No such file or directory, rc 71
/…/homebrew/bin/opencode (absolute, still the link)
Operation not permitted
/…/libexec/…/opencode.exe (resolved)
runs, rc 0
Adding a read grant on the middle directory makes all three rc 0.
Two secondary gaps make the failure less readable than it needs to be:
BuildChildArgv hands sandbox-exec a bare argv[0]
(internal/sandboxrun/backend_darwin.go:39), so the PATH lookup happens a
second time inside the sandbox, where a failure surfaces as ENOENT.
The pre-flight checks harness.InnerCmd rather than the resolved argv
(internal/cli/start.go:250), so a profile-pinned inner_cmd validates the
wrong binary and --inner skips the check entirely.
Suggested fix / Ask
Grant the directory of every hop of the symlink chain, not just the two ends
Same for interpreter resolution — nvm/mise/asdf shims are multi-hop too
Pass argv[0] to sandbox-exec as an absolute path so the second, in-sandbox lookup disappears
Pre-flight the resolved argv, and warn up front when the inner command is unresolvable or sits inside a protected-path deny
Acceptance criteria
omac start opencode -- --version succeeds against a $HOME-rooted Homebrew prefix with a three-hop chain and no profile grant for that prefix
A profile that denies the tree holding the harness still denies it, and says so before the launch fails
Unit coverage for the multi-hop chain, for cycle termination, and for the unchanged single-hop/plain-binary cases
Non-goals
Resolving the harness out of a denied tree. Exec of a main image consults (allow process-exec*) and never the file-read rules, so passing the EvalSymlinks form would launch a binary the user's profile explicitly
denies. omac must report that conflict, not route around it.
Widening the baseline to cover $HOME-rooted install prefixes wholesale.
Repro: fixture $HOME with the three-hop chain, profile with neither a filesystem.deny nor a grant for the prefix. The fixture must live outside /tmp — the baseline grants /tmp and /private/tmp, which masks the bug
entirely.
0.7.1 → exit 71 with the ENOENT above; a build carrying the chain grant →
prints the version, exit 0.
Context / Summary
Reported on omac 0.7.1 (macOS 26.6.1, arm64).
omac start opencode -- --versionexits 71 with
on a machine where
which opencoderesolves fine. The same binary launched byits fully resolved path (
omac start opencode -inner <realpath> -- --version)runs and prints its version. So the harness is present and executable — what
fails is reaching it from inside the sandbox.
Problem / What
The reporter's distinguishing property is Homebrew in a non-default prefix
(
~/homebrew), which no baseline grant covers —internal/sandboxprofile/baseline.go:106grants/opt/homebrew,/usr/local/Cellar,/usr/local/optonly. The harness is then reached througha three-hop symlink chain:
resolveCommandBinaryDirs(internal/sandboxrun/resolve_binary.go:47) grantsonly the two ends of that chain —
Dir(PATH entry)andDir(EvalSymlinks(...)). The kernel resolves a chain one hop at a time andneeds read access to each link it reads on the way, so resolution dies on the
middle directory. Seatbelt reports the denied in-sandbox PATH lookup of a bare
argv[0]as ENOENT, which names neither the deny nor the path.Isolated from omac with a hand-written SBPL granting exactly the two ends —
three spellings of the same file behave differently, which is what pins the
cause to path resolution rather than to exec:
argv[0]opencode(bare — what omac passes)execvp() ... No such file or directory, rc 71/…/homebrew/bin/opencode(absolute, still the link)Operation not permitted/…/libexec/…/opencode.exe(resolved)Adding a read grant on the middle directory makes all three rc 0.
Two secondary gaps make the failure less readable than it needs to be:
BuildChildArgvhandssandbox-execa bareargv[0](
internal/sandboxrun/backend_darwin.go:39), so the PATH lookup happens asecond time inside the sandbox, where a failure surfaces as ENOENT.
harness.InnerCmdrather than the resolved argv(
internal/cli/start.go:250), so a profile-pinnedinner_cmdvalidates thewrong binary and
--innerskips the check entirely.Suggested fix / Ask
argv[0]tosandbox-execas an absolute path so the second, in-sandbox lookup disappearsAcceptance criteria
omac start opencode -- --versionsucceeds against a$HOME-rooted Homebrew prefix with a three-hop chain and no profile grant for that prefixNon-goals
(allow process-exec*)and never the file-read rules, so passing theEvalSymlinksform would launch a binary the user's profile explicitlydenies. omac must report that conflict, not route around it.
$HOME-rooted install prefixes wholesale.Evidence / Environment
~/homebrew, opencode 1.18.15$HOMEwith the three-hop chain, profile with neither afilesystem.denynor a grant for the prefix. The fixture must live outside/tmp— the baseline grants/tmpand/private/tmp, which masks the bugentirely.
prints the version, exit 0.
sandbox profile "builtin" could not be resolved ... GET /sandbox/denied disabled.That is already fixed on
mainby fix(cli,sandbox): resolve the sandbox plan once so /sandbox/denied answers #219 and is absent from current builds.