Skip to content

fix(tool-shell): seed PATH in the sandbox-host engine so full-access resolves bare names#203

Merged
hartsock merged 1 commit into
mainfrom
fix/host-shell-seed-path
Jul 6, 2026
Merged

fix(tool-shell): seed PATH in the sandbox-host engine so full-access resolves bare names#203
hartsock merged 1 commit into
mainfrom
fix/host-shell-seed-path

Conversation

@hartsock

@hartsock hartsock commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

The sandbox-host engine (HostShellTool, ADR 0019 / #199) spawns /bin/sh -c <cmd> through ConfinedCommand, whose shared spawn does env_clear() — so no ambient environment (including PATH) crosses the boundary; only the caller-supplied env is re-applied. When the caller sends no PATH (e.g. an embedder whose structured env seam is empty), the child shell had no PATH and fell back to the shell's compiled _CS_PATH (dash: /usr/local/sbin:…:/usr/bin:/sbin:/bin).

That resolves standard /usr/bin tools by luck, but silently drops every tool in a non-standard dir the user actually has on PATH~/.cargo/bin, ~/.local/bin, /opt/homebrew/bin, language shims — and is fragile across non-dash /bin/sh and macOS. Net effect: full-access sessions where bare grep/ls/find-style names (and the user's own tools) fail to resolve.

Fix: seed PATH from a new agent_bridle_core::default_exec_path() (ambient $PATH if set, else the conventional RootfsPolicy search dirs, platform-joined) unless the caller already provided one. This is authority-consistent: the engine only reaches env construction when exec is unrestricted (a restricted exec/net grant is refused above, ADR 0019 D2), so the child is already permitted to run anything on the host — seeding PATH grants nothing new. env_clear() still scrubs everything else; a caller-provided PATH wins.

This unblocks the newt-side adoption (newt #868) of the host-shell engine for --full-access, where the user "often needs find/grep/ls and other shell tools."

Test plan

  • full_access_seeds_default_path_into_the_child (regression): the child echoes its own $PATH; asserts it equals default_exec_path() — verified failing on the pre-fix engine (child saw only dash's minimal _CS_PATH), passing after.
  • bare_name_resolves_when_path_includes_its_dir: a bare program name resolves and runs when its dir is on PATH.
  • Existing host-shell tests unchanged and green, including the real-Landlock keystone dynamic_construct_runs_but_out_of_scope_write_is_kernel_denied (gnuc, 6.8 kernel).
  • Full just check green (fmt + clippy -D warnings all-features & no-default-features + tests both ways); just publish-check green (core stays publishable — no new deps).

risk:low — additive, single concern, regression-tested, no CI/hooks/build changes, deletes nothing.

…resolves bare names

The sandbox-host engine (HostShellTool, ADR 0019) spawns `/bin/sh -c <cmd>`
through `ConfinedCommand`, whose shared spawn does `env_clear()` — so no ambient
environment (including `PATH`) crosses the boundary. It then only re-applies the
caller-supplied `env`. When the caller sends no `PATH` (e.g. newt's structured
env seam is empty absent a venv), the child shell had no `PATH` and fell back to
the shell's compiled `_CS_PATH` (dash: `/usr/local/sbin:…:/usr/bin:/sbin:/bin`).
That resolves standard `/usr/bin` tools by luck, but silently drops every tool in
a non-standard dir the user actually has on `PATH` — `~/.cargo/bin`,
`~/.local/bin`, `/opt/homebrew/bin`, language shims — and is fragile across
non-dash `/bin/sh` and macOS. The result: full-access sessions where bare
`grep`/`ls`/`find`-style names (and the user's own tools) fail to resolve.

Fix: when building the child env, seed `PATH` from a new
`agent_bridle_core::default_exec_path()` (ambient `$PATH` if set, else the
conventional `RootfsPolicy` search dirs, platform-joined) unless the caller
already provided one. This is authority-consistent: the engine only reaches env
construction when `exec` is unrestricted (a restricted `exec`/`net` grant is
refused above, ADR 0019 D2), so the child is already permitted to run anything on
the host — seeding `PATH` grants nothing new. `env_clear()` still scrubs
everything else; a caller-provided `PATH` wins.

Regression test `full_access_seeds_default_path_into_the_child` has the child echo
its own `$PATH` and asserts it equals `default_exec_path()` — empty/minimal
pre-fix, the full path post-fix (verified failing on the pre-fix engine).
`bare_name_resolves_when_path_includes_its_dir` proves a bare program name
resolves and runs when its dir is on `PATH`. Existing host-shell tests (incl. the
real-Landlock keystone) unchanged and green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRv5vteHCw4jUayHGL5esx
@hartsock hartsock merged commit 4860146 into main Jul 6, 2026
10 checks passed
@hartsock hartsock deleted the fix/host-shell-seed-path branch July 6, 2026 11:52
hartsock added a commit that referenced this pull request Jul 6, 2026
Cut the 0.7.0-rc.1 line carrying the OCAP shell-engine work: the sandbox-host
engine + full-access PATH seed (#203), the carried brush engine + carried
coreutils (#204/#206, via the published brush-ocap-* crates), and the honest
per-axis L3 disclosure. The stale crates.io `agent-bridle-core 0.1.0` predated
the macos-seatbelt / windows-appcontainer / host-shell features, so republishing
the workspace at 0.7.0-rc.1 is what lets `agent-bridle-tool-shell` publish with
those features (the pre-existing release blocker F surfaced during the brush
dep-swap).


Claude-Session: https://claude.ai/code/session_01HRv5vteHCw4jUayHGL5esx

Co-authored-by: Shawn Hartsock <hartsock@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant