Skip to content

emrg: make the process boundary the default bash tool (bash tool v2, P6) - #1545

Merged
argszero merged 3 commits into
masterfrom
feature/bash-tool-v2-p6
Sep 22, 2026
Merged

argszero merged 3 commits into
masterfrom
feature/bash-tool-v2-p6

Conversation

@argszero

Copy link
Copy Markdown
Owner

Bash tool v2, phase P6 — the switch flips. Design bash-tool-v2-design.md §D7/§D10.

What changes for an instance that says nothing

[sandbox] bash_tool_v2 now defaults to true: the tool named bash is the one whose confinement is the OS process boundary (macOS Seatbelt, Linux bubblewrap, Windows ACL-restricted tokens) rather than a static scan of the command text. P1/P3/P4 landed the three backends; this is the phase that makes them the thing running.

The frozen tool stays reachable, and the rollback is a supported path, not an accident:

[sandbox]
bash_tool_v2 = false        # back to the static scan

EMRG_BASH_TOOL_V2=0 does the same for a single launch, without editing anything. Both are pinned by tests — a boundary that cannot be turned off in the field is not deployable.

danger-full-access is unaffected and still runs bare; a task configured with it (the evolution task on this host, for instance) sees no behaviour change at all.

The defect the flip made visible

Flipping the switch turned a latent break into a real one, and it is the reason this PR is not a one-line default change. A confined run cannot write the home directory, which is where package managers keep their caches. Measured on the dev host through this tool at workspace-write:

tool default cache what happens under the boundary
uv ~/.cache/uv uv run fails: error: Failed to initialize cache at /Users/<host>/.cache/uv
npm ~/.npm the directory cannot be created: touch: Operation not permitted
pip ~/Library/Caches/pip silently disables its cache: The directory … is not writable … The cache has been disabled

So the tool now sets four variables for the confined child — XDG_CACHE_HOME, UV_CACHE_DIR, PIP_CACHE_DIR, npm_config_cache — to <temp>/emrg-confined-cache. Three properties keep this from being a boundary widening, and each is a test:

  • the directory is inside a root the policy already grants (asserted against writable_roots, the same derivation the Seatbelt profile is built from — not against a second copy of the rules);
  • a variable the deployer set is never overridden, so a warm cache stays reachable wherever the deployer put it (that is also how you opt out per tool);
  • a read-only run relocates nothing — there is no writable root to move a cache into, and naming one would describe a boundary the run does not have;
  • the base is canonicalised (canonical_path), which the first cut of this change was not: on darwin gettempdir() reports /var/folders/… while the granted root is /private/var/folders/…, and the containment test caught it (design §3.5's spelling trap, again).

pip's row is why the relocation is not optional even though only uv fails loudly: a cache that is silently disabled is the failure that costs an afternoon.

Declared deviations

  • The blueprint has no cache mechanism, and this adds one. dsh does not need it: it is launched from the deployer's shell, so "set UV_CACHE_DIR" is an instruction the deployer can carry out. EMRG's daemon is normally started by the GUI or a launcher and inherits neither, so the same instruction would be one nobody can follow. The addition is registered here rather than hidden: it relocates caches, never credentials or configuration (~/.config, ~/.ssh, the credential store stay put, and a confined run that needs to write those fails loudly).
  • Linux without bwrap refuses every command. That is the design's own fail-closed contract, and it is why DEVELOPMENT.md says to leave the switch off there until bwrap is installed and user namespaces are allowed. It is the honest cost of removing a boundary that only looked like one, and it is stated rather than softened. Windows is the same shape where the ACL backend cannot be established.

Measured

  • Full suite on this tree: 5112 passed, 29 skipped (the count moved with the new tests; nothing else changed).
  • Six mutation arms, each killed by a named test: relocation removed (A) · relocation allowed under read-only (B) · the deployer's value overridden (C) · the base left uncanonicalised (D) · the default flipped back (E) · the env rollback path dropped (F).
  • End-to-end on darwin: a confined command sees the relocated variable, and uv run — which failed before this change — succeeds.

EMRG Evolution added 3 commits September 22, 2026 17:31
`[sandbox] bash_tool_v2` now defaults to v2.  The frozen tool stays reachable
as the rollback — one line in config.toml, or EMRG_BASH_TOOL_V2=0 for a single
launch — and that path is pinned by its own tests, because a boundary that
cannot be turned off in the field is not deployable.

Flipping the switch made a measured defect visible: a confined run cannot write
the home directory, which is where uv, pip and npm keep their caches.  uv fails
outright, npm's ~/.npm cannot be created, and pip silently disables its cache.
The tool now points those four variables at <temp>/emrg-confined-cache for the
confined child — inside a root the policy already grants, so nothing is widened,
and a variable the deployer set is never overridden.

Six mutation arms each kill a named test.
…ts (bash tool v2, P6)

The four new tests built a policy on the literal /tmp, which is absolute on POSIX
and not on Windows, so SandboxPolicy refused it on the windows leg while the dev
host stayed green.  They now take tmp_path — a directory that exists, as a
workspace root should be — and the section says why.
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