Repository navigation
Conversation
## Problem A `python3` or `python` that a program starts skips the Shell. The alias reads the script with the agent's own access, sends the source straight to Monty with no `shell:exec` decision, and waits under its own 35-second limit, while Monty allows 120 seconds and the Shell's deadline is configurable. The same script typed in the Shell takes a different route with different decisions and limits. Every alias also starts its command in the workspace, not in the folder it was run from. Measured on main: from a subfolder, `zsh -c 'cat here.txt'` read the workspace's `here.txt` and exited 0. A read silently gets the wrong file, and a write lands in the wrong folder. ## Solution The broker protocol moves to version 9, with one new client frame, `run`. It carries a command and the caller's folder. The command is text for `zsh`, `bash` and `sh`, or a name and an argument list for `python3` and `python`. `call` and every other frame are unchanged; an alias built for version 8 is refused by version. - Every alias sends the folder it was run from: `PWD` when it names `.`, otherwise `getcwd`. If the alias can't tell, it exits 125 and sends nothing, so a command never falls back to the workspace. - The `python3` and `python` aliases open a Shell Program and send their name and arguments unchanged. They parse no option and read no file. The trusted process single-quotes every word, the quoting `xargs` and `find -exec` already use, so `$(...)`, backticks and newlines stay literal. The Shell's own `python3` then runs Monty, exactly as a typed `python3` does, and refuses a bare `python3` or `python3 -` with status 2. - The alias's 35-second Python wait is gone; `limits.shell.timeout` applies, and a `Signal` can end the call. - Before the command runs, `Program::enter` moves the Shell into the folder through `Mediated::change_dir`, the `fs:read` `change_dir` decision and reachable paths check a typed `cd` gets. The folder the Program already starts in, the workspace, takes no decision, so a file-scoped policy still runs `zsh -c` from there. A refused folder, a missing one, or a file runs nothing and exits 126 with `strands-box: cannot start in <folder>: <reason>`. Interfaces that move, which need a maintainer's approval (the broker protocol and the aliases are frozen surfaces): - The `python3` alias now raises a `shell:exec` decision. A policy with only `fs:*` permits refuses it with 126. - The Shell reads a script file under `fs:read`, from the caller's folder, not with the agent's own access. - `bash -c`, `zsh -c`, `sh -c`, `python3` and `python` start in the caller's folder, and a folder other than the workspace raises an `fs:read` `change_dir` decision. - Through the Python alias, a refused form exits 2 instead of 125, and `--version` and `--help` work. Monty still roots a relative path at the workspace. Making it follow the Shell's folder changes the vendored Shell's script hook, which the move to upstream Strands Shell replaces, so it is a follow-up. The direct `Interpreter::Python` route stays in the daemon for one release; no alias opens it. Docs: `docs/user/monty.md`, `docs/user/shell.md`, `docs/user/config.md`, `docs/design/monty.md`, `docs/design/shell.md`, `docs/design/binaries.md`, `docs/design/architecture.md`, the `python-in-the-shell-is-monty` entry in `docs/design/decisions.md`, and `crates/box/AGENTS.md`. ## Tests Ran on macOS: `cargo fmt -p strands-box --check`, clippy with the CI allow list and `-D warnings`, the alias tests (45 pass), the library tests (948 pass, 1 ignored), and every test target with `--no-run`. `Cargo.lock` is unchanged. - Alias: `a_python_alias_forwards_its_name_and_arguments_unchanged` (no arguments, `-`, `-m`, `$(rm x)`, backticks, newlines, quotes, spaces, the empty string), `a_python_alias_refuses_a_non_utf8_argument`, `the_working_directory_comes_from_pwd_only_when_pwd_names_it`, `an_unknown_working_directory_is_refused`, `every_submission_sends_the_callers_directory`. The `-c`, `-lc`, `-c -l` and `SCRIPT` tests still pass. - Protocol: `a_run_frame_round_trips_in_both_command_shapes`, `a_malformed_run_frame_is_refused`, `the_protocol_version_that_carries_run_is_nine`. - Broker: `a_command_as_words_keeps_every_argument_literal`, `a_run_starts_in_the_callers_directory`, `a_python_run_reaches_the_shells_python_and_reads_the_callers_script`, `a_python_run_with_no_script_is_refused_by_the_shell`, `a_python_run_needs_a_shell_exec_permit`, `only_a_folder_other_than_the_programs_own_meets_the_change_dir_decision`, `a_folder_the_box_cannot_enter_is_refused_and_runs_nothing`, `a_python_program_refuses_a_run`, `the_version_before_run_is_refused`. Replacing the `change_dir` call with a plain assignment, and dropping the quoting, each fails four of these. - In a box (`box_shell.rs`), compiled here and run only by CI: `an_alias_run_from_a_subfolder_runs_in_that_subfolder`, `an_alias_in_a_folder_the_policy_refuses_runs_nothing`, `the_python_alias_needs_a_shell_exec_permit`. Not run: anything that starts a box (`box_shell.rs` and the other end-to-end suites, `test-integ`), and Linux. This Mac can't start a box inside its own sandbox. The Mermaid blocks in the changed design pages were not compiled here.
## Problem Two review findings on the previous commit. - The broker still served `open` in Python mode. A `call` on that Program ran Monty with no `shell:exec` decision. No version 9 alias opens it, but any process in the agent's sandbox can connect to `run/box.sock`, so a policy that forbids `shell:exec` on `python3` did not stop a client the workload wrote itself. - A caller's folder that no host path backs was entered. The reachable paths check passes a name outside the host set, and the Shell's in-memory tree holds `/`, `/tmp`, `/home`, `/bin` and `/usr/bin`. So `printf RAN` run from `/` or `/tmp` ran in memory and exited 0, and a write landed nowhere on the host. A `..` spelling that starts inside the workspace reached the same place. Smaller findings: entering the folder ran outside the call's deadline, and the docs left out the input limit on `python3 -c`, a script that never pauses, a folder name that isn't valid UTF-8, and the agent's home in one troubleshooting row. The previous commit message also said a `Signal` can end a Python call through the alias; the alias sends no `Signal`. ## Solution - The broker refuses `open` in Python mode with a `transport_error` denial that says the mode was removed in protocol 9. The Shell is now the only way to Monty. The old serving code stays, unreachable: deleting it also retires `REQUEST_TIMEOUT` and the `fetch.rs` timeout ordering check, which is a separate change. - `Program::enter` refuses a folder `Reach::names_a_host_path` doesn't back, both as the alias spelled it and as the Shell resolved it after `change_dir`, and restores the old folder. The refusal is the usual 126 with `strands-box: cannot start in <folder>: outside this box's home and every declared bind`. - Entering the folder counts against the call's deadline, and the command gets what is left of it. - Docs: `docs/user/monty.md` (input limit, never-pausing script, non-UTF-8 folder, the troubleshooting row), `docs/user/shell.md`, `docs/design/shell.md`, `docs/design/monty.md`, `docs/design/binaries.md`, the `python-in-the-shell-is-monty` entry, `crates/box/AGENTS.md`, and the protocol and Python module docs. ## Tests Ran on macOS: fmt check, clippy with the CI allow list and `-D warnings`, the alias tests, the library tests, and every test target with `--no-run`. `Cargo.lock` is unchanged. - `an_open_in_python_mode_is_refused_and_runs_nothing` replaces `a_python_program_refuses_input_and_signal_by_name` and `a_python_program_refuses_a_run`: the open is refused by name, a `call` for that id finds nothing open and writes nothing, and the connection still serves a Shell Program. With the old open restored, it fails. - `a_folder_the_box_cannot_enter_is_refused_and_runs_nothing` gains `/`, `/tmp`, a made-up absolute path, and a `..` spelling from the workspace into `/tmp`. Without both checks, `/` prints `RAN`. Without the check after `change_dir`, the `..` row prints `RAN`. Not run here: the in-box suites and Linux.
… docs ## Problem Three small findings from the review of the previous commit. - `tokio::time::timeout` polls the folder entry before its timer. A synchronous lookup that overran the call's deadline still returned `Ok`, and the command then started with no time left, so it could run before the timeout was reported. - `docs/design/architecture.md` said the broker hands Python scripts to Monty, and the `monty-is-per-request-and-buffers-its-output` entry in `docs/design/decisions.md` described an outer limit on direct Python calls. The previous commit removed that route. - `an_open_in_python_mode_is_refused_and_runs_nothing` waited on an unbounded read, so a regression would fail only at the 300-second idle timeout. ## Solution - After the folder entry, the broker reports a timeout and exits 125 without running the command if no time is left. - `architecture.md` now says the Shell handles the Python aliases and runs scripts in Monty. The decisions entry drops the outer limit and the "separate five-minute allowance" sentence, and says Python runs through the Shell. - The refusal test bounds each read to 5 seconds. ## Tests Ran on macOS: fmt check, clippy with the CI allow list and `-D warnings`, the alias tests, the library tests, and every test target with `--no-run`. `Cargo.lock` is unchanged. - `a_run_whose_folder_entry_passes_the_deadline_runs_nothing` runs a `run` into a subfolder 20 times with a zero deadline and checks that each reports the timeout, exits 125, and writes nothing. With the old remaining-time line restored, it fails on the first attempt. - On this code, replacing `change_dir` with a plain assignment fails 2 broker tests, and removing the quoting fails 4. Not run here: the in-box suites and Linux.
crates/box/src/bin/strands-box-sock-alias.rs: kept the run frame, the caller's folder and python3 words; submission now sends Open and Run, and forward attaches main's stdin frames (each Input, then InputEof, or InputEof alone) after Run for every alias; forwards_stdin is true for Text (shell aliases) and false for Words (python3/python); main's stdin tests now send Run. crates/box/src/run/broker/host.rs: imported both Command and main's MAX_CHUNK_BYTES and MAX_ENCODED_CHUNK_BYTES; main's Input handling is per Program, so it serves a Run as it serves a Call. crates/box/src/run/broker/program.rs: kept no_host_folder for Program::enter and dropped poisoned(), which main's stdin buffer no longer calls. crates/box/AGENTS.md: kept main's stdin notes with Call changed to Run, said python3/python send InputEof right after Run and forward no stdin, and kept the StdinEof note for every alias. docs/user/shell.md: kept the caller's folder rule and main's credential route placeholder, and said the python3 and python aliases forward no standard input. PROTOCOL_VERSION stays 9: main is still at 8, and the Input frames it uses existed before.
…d Python tool ## Problem A bare `python3` or `python` in a box always runs Monty, Box's built-in Python subset (#371). An agent that needs `-m pytest`, a package, or a module Monty lacks gets a refusal. A `[tool.python3]` table the operator declares is accepted, but a bare `python3` never runs it, and nothing says so. ## Solution - A new top-level `box.toml` key, `monty`, defaults to true. `monty = false` is stored at record version 34, so an older build refuses the box instead of quietly running Monty. - With Monty off, `[tool.python3]` or `[tool.python]` answers to both `python3` and `python`. The table name decides, not the file name. If both tables exist, they must name the same command as written. - Nothing falls back to Monty. With no Python table, both names exit 126 with a reason, before any policy decision. With a table but no `shell:spawn` permit, the call exits 126 with `default-deny`. - Startup says what a bare `python3` runs on every box. It names a Python table that Monty leaves unused, and with Monty off it states the costs. - Monty's refusals for `-m`, a program from stdin, and a failed import say how to run real Python. - Versioned names such as `python3.14`, and a venv's `bin/` scripts such as `pytest`, behave as before. - The Shell gains two embedder hooks: a declared file can replace one of its commands, and a bare name can be refused with a reason. `crates/shell/UPSTREAM.md` records both. Interfaces that move, for maintainer approval: the `monty` key and record version 34; what the `python3` and `python` aliases run with Monty off; a new startup line on every box; new 126 reasons; Monty's `-m` refusal (exit 2 with its own text) and its hint texts; and strands-shell's public items `ReplacedCommands`, `RefusedCommands`, `Kernel::replaces_command`, `Kernel::refused_command`, `ShellBuilder::replaced_commands`, `ShellBuilder::refused_commands`, two `VfsKernel` fields, and `commands::REAL_PYTHON_HINT`. ## Tests - Record: the default, misspelled and non-boolean values refused, the round trip at version 34, every older version refused, and the two-table refusal. - Bare names: both names answer to either table, both are refused without one, an unused table is listed, and versioned names stay separate. - Startup lines: one full-output test for each case. - Shell: a replaced name runs its declared file. A refused name exits 126 before Monty, a declared file, `PATH` and policy, and stops a pipeline. `command -v` and `type` find nothing for it. Monty's hint texts. - In a real box: Monty off with no tool exits 126; with a tool, both names run it; without a permit, 126 `default-deny`; with Monty on, the table is unused and `python3` is Monty.
With Monty off, Box reads the Python tool's install from its files: a venv, a standard prefix, Homebrew, or Debian. Anything else refuses startup. Box refuses every write grant that reaches the folders Python loads code from, and every Shell or Monty write into them while the box runs. An editable install whose source is in the workspace, or under a write grant, is refused too. Without this, an agent could plant a `.pth` file that runs inside the tool and later outside the box with the operator's rights.
…the timeout kill on macOS A program the Shell starts can't receive stdin yet. When a Python tool call's stdin carries data, Box hands Python a write-only descriptor, so a script that reads stdin fails with EBADF instead of reading nothing. On a failure Box adds one stderr line saying why. A call that would read its program from stdin, such as `python3 -` or a bare `python3` in a pipe, exits 126. A new macOS test checks that the Shell timeout kills a tool's process group, as the Linux tests already do.
With Monty off, Box adds the Python tool's reads on its install folders, a venv's `pyvenv.cfg`, and its certificate bundle. When the table has no `filesystem` list, Box also adds read and write on the workspace. An operator's own list replaces only the workspace grant. Each added grant is marked in the startup notice.
…, and wire the read-only hint
With monty = false, Box walks every .so (and .dylib on macOS) under the Python tool's code folders, up to 5000, through containment's dependency walk, and adds read_file on each library no read already reaches, marked 'libraries' in the startup notice. Containment exposes loaded_libraries(images, config), seeded with the tool's write grants, so a dependency reached through a writable chain is not derived. On Homebrew this grants openssl@3, sqlite, xz, zstd and mpdecimal, so ssl, sqlite3 and lzma import.
…as Linux requires
…e its write grants
…nd run the Mach-O library test only on macOS
…bin/sleep is outside its grants
This was referenced Nov 13, 2026
## Problem
With `monty = false` and no `[tool.python3.filesystem]`, a box whose Python is Homebrew's refused
to start on macOS:
`[tool.python3]` filesystem entry "/opt/homebrew/opt/mpdecimal/lib/libmpdec.4.dylib" is
refused: ... is a symbolic link to /opt/homebrew/Cellar/mpdecimal/4.0.1/lib/libmpdec.4.0.1.dylib
Box added each library the install's extension modules load as a `read_file` under the name the
load requests, and Homebrew names every one through `/opt/homebrew/opt/<formula>` links. Box's lists
refuse a link. Past that, the venv's `python3` couldn't start either: its chain is
`python3 -> python3.14 -> /opt/homebrew/opt/python@3.14/bin/python3.14`, and `exec` failed with
`Operation not permitted`, because containment's traversal walk recorded file links but not the
`/opt/homebrew/opt/python@3.14` folder link the lookup reads. The Linux test used Debian's Python,
whose libraries are in system folders and whose venv links to `/usr/bin/python3`, and the box tests
used a stand-in install, so neither reached this.
## Solution
- Each added library is a `read_file` on the real file, judged like any other entry, and carries
the name the load requests (`DirectGrant::loaded_as`). A name with `..` is spelled from its
folder's real path. The startup notice adds `(loaded as <name>)`.
- At launch Box grants the file to containment under that name in place of the real path, after
checking the name still leads to the judged file, so containment renders the links on the way.
It is one grant per file because containment refuses a file granted the same operation twice;
the first name wins.
- Containment's traversal walk (`PathGrant::walk_traversal_nodes`) also records each linked
directory any spelling on the chain passes through, parent-canonical, and walks that link's own
target. macOS renders `file-read-metadata` on every node, so the exec chain and the library
names get the folder rule, and the floor and refusals judge these nodes like the rest. A grant
spelled canonically, which is every entry an operator writes, gains no node. Linux already binds
a grant under both names.
## Tests
- `a_file_read_spelled_through_homebrew_links_renders_metadata_on_each_link` and
`an_exec_chain_through_a_linked_folder_renders_metadata_on_the_folder_link` (containment, macOS):
each fails without the change (the `opt/...` folder rule is missing) and passes with it. Rendering
the real venv's chain on this host now gives
`(allow file-read-metadata (literal "/opt/homebrew/opt/python@3.14"))`.
- `a_library_named_through_links_is_granted_on_its_file_with_the_names_alongside` (Box): one entry
per file, the first name kept, a `..` name respelled, a missing name dropped.
- `a_library_is_granted_once_under_the_name_its_load_requests` (Box): the translated config holds
one grant for the file, under the name; on macOS the profile renders with the linked folder's
metadata; a name relinked to another file refuses the spawn. Granting both the real path and
the name fails it, which is what a first version of this fix did ("granted Read twice").
- `with_monty_off_a_homebrew_python_loads_the_libraries_its_modules_name_through_links`
(`crates/box/tests/python_tool.rs`, in a box): a venv of the host's Homebrew Python with no
filesystem table starts, lists its libraries with their names, and imports `ssl`, `lzma`,
`sqlite3` and `decimal`. Skips when the host has no Homebrew Python.
- Locally on macOS: the Box lib suite and CI's clippy are clean. The containment lib
suite passes except the two `floors` tests that need a setuid bit this host drops; they fail
without this change too. Box startup with Homebrew Python 3.14.7 now lists six libraries, each
with its `loaded as` name; this host can't apply a sandbox, so the in-box test runs in CI.
- End to end on macOS with Homebrew Python 3.14.7, a read-only venv outside the project and no
filesystem table: 49 of 49 checks pass, including the `ssl`, `sqlite3` and `lzma` imports, pytest,
`PermissionError` on reads outside the grants and on writes into the venv, and the Shell timeout.
A venv outside the operator's home failed at startup on macOS, before running anything:
python3: realpath: /private/var/tmp/.../venv/bin/: Operation not permitted
Python resolves the folder its command was started from, which needs metadata on that folder.
Under the operator's home the discovery rules give it, which is why the end-to-end run, with its
venv under the home, passed. The in-box Homebrew test, whose venv is under /private/var/tmp, failed
on the macOS runner.
On macOS Box now adds `metadata` on the folder holding the command, such as a venv's `bin/`,
marked `(added for [tool.python3]: install)`. Nothing in the folder becomes readable, and none is
added when a read already covers it. Linux adds nothing, because its view makes the folder
traversable.
Tests: the three added-grant tests expect the entry on macOS. The in-box test
`with_monty_off_a_homebrew_python_loads_the_libraries_its_modules_name_through_links` covers the
venv outside the home.
The Linux runner has no Homebrew, so the test skipped there, and Linux CI fails a job on a skip that `scripts/check-linux-test-output.py` doesn't expect. The macOS runner has Homebrew Python, and the case the test covers, libraries named through Homebrew's `opt/<formula>` links, is a macOS one in practice. The candidates are Homebrew's two macOS prefixes.
zastrowm
reviewed
Nov 14, 2026
|
|
||
| /// One Python install, as its files describe it. | ||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub(crate) struct PythonInstall { |
Member
There was a problem hiding this comment.
None of this should be included in Box; Box does't need to know about this. If monty is disabled, it's up to the author to include python as a tool.
That's what I've been trying to emphasize - the only case that Box should special case python3 is for/using Monty. But if Monty is disabled, python should just be a normal tool
This branch has not been deployed
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.
Description
Problem
A bare
python3orpythonin a box always runs Monty, Box's built-in Python subset (#371). An agent that needs-m pytest, a package, or a module Monty lacks gets a refusal. A[tool.python3]table the operator declares is accepted, but a barepython3never runs it, and nothing tells the operator.Running real Python through a writable venv has a second problem. Measured on
main(61ade09, macOS, Homebrew Python 3.14.7): the agent wrote a.pthfile into an in-project venv'ssite-packagesthrough the Shell. Python ran it inside the tool, and again after the box ended, with the operator's full rights.What changes
monty = false. A new top-levelbox.tomlkey, default true. With Monty off,[tool.python3]or[tool.python]answers to bothpython3andpython, and nothing falls back to Monty. With no Python table, both names exit 126 with a reason, before any policy decision. The table name decides, not the file name. Both tables must name the same command as written, with no fixed arguments.lib/, link targets outside the install, editable-install sources, and folders named by.pthlines. A file with another hard link refuses startup.pyvenv.cfg, the certificate bundle, and the libraries the install's extension modules load, plus, on macOS, metadata on the folder that holds the command, which Python resolves at startup. When the table has nofilesystemlist, Box also grants read and write on the workspace. An operator's own list replaces only the workspace grant. The startup notice marks each added grant./opt/homebrew/opt/openssl@3/lib/libssl.3.dylib. Box's own lists refuse a link, so each library entry is on the real file and carries the requested name,(loaded as …)in the startup notice, and Box grants the file to containment under that name. Containment's traversal walk now records each linked folder a chain passes through, such as/opt/homebrew/opt/python@3.14in a Homebrew venv'spython3, so macOS gets a metadata rule on it and the floor judges it like the other nodes.python3 -, or a barepython3in a pipe) exits 126. Only a replaced name waits for the stdin answer. Other spawns are unchanged.python3runs, on every box. It names a Python table that Monty leaves unused, and with Monty off it states the costs. Monty's refusals for-m, a program from stdin, and a failed import say how to run real Python. A failed write into the read-only install adds one stderr line.[limits.shell] timeoutkills a tool's process group, as the Linux tests already do.Decisions this depends on
monty = false, a[tool.python3]or[tool.python]table, and ashell:spawnpermit on the resolved file.[limits.shell] timeoutbounds real Python too. There is no Python-only setting.python3.14, and a venv'sbin/scripts such aspytest, behave as before.docs/design/decisions.mdrewrites#python-in-the-shell-is-montyand#a-bare-python-name-always-means-monty, keeping both anchors. It adds#box-refuses-writes-into-the-python-installand#box-grants-a-python-tool-its-install-and-the-workspace.Interfaces that move (approval requested for each)
box.tomlkeysmonty(bool, default true)monty = falseis stored at record version 34; an older build refuses the boxpython3andpythonreach the Python tool instead of Monty(loaded as <name>)file-read-metadataon that folder link (macOS), and the floor anddenychecks judge it. A grant spelled as a real path, which is every grant an operator writes, gets no new rule-mexits 2 with its own text; Monty's hint texts; two new stderr linesstrands-shellpublic itemsReplacedCommands,RefusedCommands,SpawnStdin,HostSpawn.stdinandHostSpawn.name(new fields on a public struct),Kernel::replaces_command,Kernel::refused_command,ShellBuilder::replaced_commands,ShellBuilder::refused_commands, twoVfsKernelfields,commands::REAL_PYTHON_HINT.crates/shell/UPSTREAM.mdrecords each.The broker protocol, CLI, policy actions and telemetry names don't change. A refused name raises no policy decision, so it leaves no decision record.
Known gaps
opt/openssl@3/libforssl. On Linux that grant is mounted noexec, so a library outside the system folders (conda'slib/, for example) may still fail to load. Box also stops after 5000 extension modules without saying so.deny_writefor the agent. It addresses the same in-project venv case from the agent's side.Related Issues
Part of #371. Stacked on #417; merge after it. Follows #397. Takes the place of the design in #389.
Type of Change
New feature
Testing
cargo build --workspace --locked --all-targets --all-features: ok.cargo test -p strands-box --lib --all-features: 1050+ passed.cargo test -p strands-shell: 2048 passed.cargo test -p strands-box --all-features --test four_inputs: 5 passed.cargo fmt --checkandcargo clippy --workspace --all-targets --all-featureswith the workflow's flags: clean. Cargo.lock is unchanged.ci.ymlon 60d7400, which adds Containment (both OSes) and Verdict, both of which skip a pull request into a feature branch: Containment, Verdict, lint and macOS unit tests passed. https://github.com/strands-agents/box/actions/runs/43506436419 Its Linux unit job failed once intests/mcp_alias_fallback.rswithText file busywhile starting a test program. That file isn't touched here, the same test passed in the full suite's Linux job on the same commit, and it passed on 8ce3568.python3: realpath: …/venv/bin/: Operation not permitted, which 5191fad fixes, and skipped on Linux, where CI fails an unexpected skip, which 60d7400 fixes by making it macOS-only.monty = falseand[tool.python3]with no filesystem table): 44 of 44 checks passed.python3andpythonrun the venv;ssl,sqlite3,lzmaandhashlibimport;python3 -m pytestruns;.pthfile can't be planted through the shell or through Python;pip installfails with the read-only line;EBADFand the no-stdin line;python3 -andecho 1 | python3exit 126;python3.12is refused;default-deny;[limits.shell] timeoutstops Python, and the box keeps serving.monty = falseand[tool.python3]with no filesystem table), on 8ce3568: 49 of 49 checks passed. These are the Linux checks plus a box whose own table grants Homebrew's folders. Two earlier runs found the bugs that 8ce3568 fixes: every such box refused to start, because the library grants named Homebrew's links, and thenexecof the venv'spython3failed, because nothing rendered the/opt/homebrew/opt/python@3.14folder link.ssl,sqlite3,lzmaandhashlibimport with no filesystem table;PermissionError.Main new tests:
Record: the round trip at version 34, every older version refused, the two-table and fixed-argument refusals.
Bare names and startup lines.
Shell: a replaced name and a refused name.
Install layouts on fake trees: hard links, link targets, missing folders,
.pthpaths, editable sources.Write grants refused in both directions, and Shell writes into the install refused.
The added grants and their disclosure.
Classifying stdin.
The program-from-stdin argument scan.
Libraries named through links: one entry per real file with the requested name; the translated config grants the file once, under that name, and a relinked name refuses the spawn.
Containment (macOS): a file read and an exec chain through a linked folder render metadata on the folder link. Each fails without the walk change.
In a box,
tests/python_tool.rs: a venv of the runner's Homebrew Python with no filesystem table importsssl,lzma,sqlite3anddecimal. It skips when the host has no Homebrew Python.I ran the relevant suites (
just check, orcargo test --workspace --all-features)If I touched Rust, I ran
cargo fmt --allandcargo clippy --workspace --all-targets --all-features -- -D warningsChecklist