Skip to content

Isolate the benchmark agent's shell and give each task its own venv - #235

Open
t-timms wants to merge 1 commit into
mainfrom
feat/benchmark-agent-isolation
Open

t-timms wants to merge 1 commit into
mainfrom
feat/benchmark-agent-isolation

Conversation

@t-timms

@t-timms t-timms commented Sep 24, 2026

Copy link
Copy Markdown
Owner

Why

Two problems seen in a real SWE-bench Lite evaluation of local models (details in docs/benchmark_hygiene.md):

  1. The agent installs into the harness. It ran pip install -e . and pip install "setuptools<81"; with Godspeed's venv first on PATH those landed in Godspeed's own venv (a task's editable install was still there for the next task).
  2. The agent hunts for the answer on the host. After the verify tool printed a failing test name, one model ran find / -name "test_datetime_list_inner_format", grep -rn <that name> /home/..., greps of the swebench package for FAIL_TO_PASS, find / -name "gold_results*" and find / -name "*1359*" (the instance number). Gold patches, the dataset cache and other runs' predictions live on that filesystem. In that unsandboxed run 21 of 376 shell commands left the workspace, versus 1 of 165 for another model under the same harness.

Changes

  • Shell tool: on POSIX $GODSPEED_SHELL_WRAPPER replaces /bin/bash (called as WRAPPER -c COMMAND). Read once and cached; ignored on Windows.
  • scripts/agent_shell_isolate.sh: one command per call in a private mount + PID namespace. $HOME and /mnt are empty tmpfs; /tmp is a per-task private directory that persists across that task's commands; the workspace and task venv stay visible and writable; uv-managed Pythons are visible read-only (an agent had pip installed into the shared base interpreter); /etc/resolv.conf is kept (on WSL2 it points into the masked /mnt/wsl, and without it the agent silently loses DNS); all capabilities are dropped; fails closed if namespaces cannot be created.
  • experiments/swebench_lite/isolation.py + run.py --isolate-agent-shell / --task-python VERSION: per-task throwaway venv via uv (first on PATH, pytest preinstalled), wrapper env applied for the task and restored afterwards, preflight exits 2 if isolation is requested but unavailable.
  • scripts/audit_agent_commands.py: flags agent commands that search or read outside the workspace (wide-search, outside-path, hunt-term; --allow, --term, --fail-on-flag).
  • docs/benchmark_hygiene.md (usage, requirements, and what this does not do), CHANGELOG.

Verification

  • New tests: test_shell_wrapper.py, test_benchmark_isolation.py (13; environment handling, preflight, and integration tests of the real script: home empty and planted "gold" unreachable, workspace edits reach the host, private /tmp persists across commands, capabilities dropped and mounts cannot be undone, fails closed with a broken unshare, usage error, venv visible at its own path under /tmp), test_audit_agent_commands.py (built from real commands an agent ran).
  • The venv test is a regression test: a first version of the script re-checked the venv path after /tmp had been replaced and silently skipped binding it; the test fails on that version and passes on the fix (checked).
  • End to end in one process: task_isolation(python="3.9", isolate_shell=True) + the real ShellTool: agent sees Python 3.9.25 from its own venv, $HOME shows only the uv directory, /mnt only the resolver file, a find / -name "*1359*" hunt finds nothing, pip install six works over the network and lands only in the task venv, edits reach the host, environment restored afterwards.
  • The audit tool run over the real logs: unsandboxed KAT run 20 flagged / 377 commands, unsandboxed Qwen run 0 / 165 (with the venv directory allowed).
  • ruff check . / ruff format --check . clean; run.py --isolate-agent-shell with no unshare on PATH exits 2 with a clear message.

Not verified / limits

  • Not a security boundary against a determined adversary: kernel, CPU and network are shared (an agent with network access can still fetch upstream history or the fix). The in-loop verify tool still prints failing test names by design.
  • Integration tests skip where unprivileged user+mount namespaces are unavailable (e.g. Ubuntu 24.04 runners with kernel.apparmor_restrict_unprivileged_userns=1, macOS, Windows), so CI may not exercise them; I ran them on WSL2 (kernel 6.18).
  • The full test suite was not re-run on this branch; the touched areas (test_shell*, test_benchmark_isolation, test_audit_agent_commands, test_source_encoding) pass (558 tests) and CI runs the rest.

Two problems seen in a real SWE-bench Lite evaluation of local models:

- The agent ran `pip install -e .` and `pip install "setuptools<81"` and, with Godspeed's own
  venv first on PATH, those installs landed in Godspeed's venv and leaked between tasks.
- The agent's shell shares the host filesystem, and one model searched it for hidden tests and
  gold data (`find / -name "*1359*"`, greps of a failing test's name over /home, greps of the
  swebench package for FAIL_TO_PASS).

Changes:

- shell tool: on POSIX, $GODSPEED_SHELL_WRAPPER replaces /bin/bash (called as WRAPPER -c COMMAND).
- scripts/agent_shell_isolate.sh: runs one command in a private mount + PID namespace. $HOME and
  /mnt are empty tmpfs, /tmp is a per-task private directory (persists across that task's
  commands), the workspace and task venv stay visible and writable, uv Pythons are visible
  read-only, /etc/resolv.conf is kept (on WSL2 it points into the masked /mnt/wsl), all
  capabilities are dropped, and it fails closed if namespaces cannot be created.
- experiments/swebench_lite/isolation.py + run.py flags --isolate-agent-shell and
  --task-python VERSION: per-task throwaway venv (uv) first on PATH, wrapper env applied for the
  task and restored afterwards; preflight exits 2 if isolation is requested but unavailable.
- scripts/audit_agent_commands.py: flags agent commands that search or read outside the
  workspace (wide-search, outside-path, hunt-term) in a run log.
- docs/benchmark_hygiene.md, CHANGELOG.

Tests: tests/test_tools/test_shell_wrapper.py, tests/test_benchmark_isolation.py (environment
handling, preflight, and integration tests of the real script that skip where unprivileged
namespaces are unavailable, incl. a regression test for the venv being visible when it lives
under /tmp), tests/test_audit_agent_commands.py (built from real commands an agent ran).
@github-actions

Copy link
Copy Markdown

🤖 Godspeed Review

LLM review skipped (no API key configured).

Security

No secrets detected in changed files.

),
):
raise RuntimeError("boom")
assert "AGENT_PRIV" not in os.environ
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.

2 participants