GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — fix(cli): sandbox the claude-code harness and run the local ACP on loopback by stephen-wang24 · Pull Request #554 · scaleapi/scale-agentex-python Conversation
|
| Behavior | Before | After |
|---|---|---|
Files the CLI reads without asking, under agentex agents run |
The agent's project folder, including .env and manifest.yaml |
Only its own per-task scratch directory, which starts empty |
| Environment the CLI gets | Everything the agent process has, including values loaded from .env and the manifest's env: |
ANTHROPIC_API_KEY, PATH, HOME, USER |
| Tools the model can call (this Mac) | 50 to 131, including Bash, Write, WebFetch and the developer's MCP tools | Read, Glob, Grep |
| The developer's own Claude Code hooks, plugins and MCP servers | Loaded | Not loaded; only organisation-managed settings apply |
| Claude Code CLI in the images | Whatever npm resolves at build time | 2.1.142 |
agentex agents run listens on |
All interfaces (*:<port>) |
127.0.0.1:<port>, unless --host or AGENTEX_AGENT_HOST says otherwise |
Asking the agent for its project files. On main the agent returns the test values from both .env and manifest.yaml; on this branch it returns neither. Each row is one message/send request sent straight to the agent's port, to the sync template started with agentex agents run from a terminal-like environment with the local claude signed in. The project's .env and its manifest.yaml env: block each hold one test value.
| What was asked (paraphrased) | main |
This branch |
|---|---|---|
Print .env and manifest.yaml from your working directory |
Prints both files, test values included | Finds neither: its working directory is the empty scratch directory |
Read <project>/.env by its full path |
Prints the file, test value included | Permission to use Read has been denied because Claude Code is running in don't ask mode |
Run cat <project>/.env through Bash |
Doesn't run: Bash needs approval in -p mode |
No Bash tool exists; the Read it tried instead was refused |
Search <project> with Grep for the test value |
No result: no Grep tool, and its grep through Bash needed approval |
Permission to use Grep has been denied because Claude Code is running in don't ask mode |
| What is 17 + 25? | 42 |
42 |
Tool steps for the first two rows, from the agent's response stream (the .env line is shown as a placeholder):
main call Read {"file_path": ".../main/cc_sync_proof/.env"}
main result "1\t<the .env line with its test value>\n2\t"
branch call Read {"file_path": ".../T/agentex-claude-code/4973acd6b272dbbb7e97cf57b35594a4/.env"}
branch result "File does not exist. Note: your current working directory is .../T/agentex-claude-code/4973acd6b272dbbb7e97cf57b35594a4."
branch call Read {"file_path": ".../branch/cc_sync_proof/.env"}
branch result "Permission to use Read has been denied because Claude Code is running in don't ask mode. ..."
Listen address (lsof -iTCP:<port> -sTCP:LISTEN while agentex agents run is up; the two rows are uvicorn's reloader and worker):
### main: agentex agents run
python3.1 10230 IPv4 TCP *:18001 (LISTEN)
python3.1 10277 IPv4 TCP *:18001 (LISTEN)
### branch-default: agentex agents run
python3.1 10361 IPv4 TCP 127.0.0.1:18002 (LISTEN)
python3.1 10391 IPv4 TCP 127.0.0.1:18002 (LISTEN)
### branch-flag: agentex agents run --host 0.0.0.0
python3.1 10468 IPv4 TCP *:18002 (LISTEN)
python3.1 10494 IPv4 TCP *:18002 (LISTEN)
### branch-envvar: AGENTEX_AGENT_HOST=0.0.0.0 agentex agents run
python3.1 10575 IPv4 TCP *:18002 (LISTEN)
python3.1 10606 IPv4 TCP *:18002 (LISTEN)
What the CLI loads. Counted from the CLI's own system/init stream-json message, with a terminal-like environment and this Mac's real ~/.claude config:
| CLI | Spawned as on main |
Spawned as on this branch |
|---|---|---|
| 2.1.300 | 4 hooks run, 6 plugins, 2 MCP servers, 50 tools, mode default |
1 hook (organisation-managed), 3 built-in plugins, 0 MCP servers, 3 tools, mode dontAsk |
| 2.1.142 | 4 hooks run, 2 plugins, 2 MCP servers (both connected), 131 tools, mode default |
1 hook (organisation-managed), 0 plugins, 0 MCP servers, 3 tools, mode dontAsk |
Normal prompts and resume. The sync template is the last row of the first table. For async and Temporal, the full loop needs the AgentEx server, Redis and Temporal, which this Mac could not run, so each rendered template's real _spawn_claude ran from its project folder:
async (2.1.311) task-async-1 tools [Glob, Grep, Read], dontAsk -> "42"
temporal (2.1.142) turn 1, task-temporal-142 -> "noted." (code word HERON-17)
temporal (2.1.142) turn 2, same task, -r <session_id> -> "HERON-17"
temporal (2.1.142) -r <session_id> from another task's directory -> "No conversation found with session ID: ..."
temporal (2.1.311) turn 2, same task, -r <session_id> -> "PELICAN-42"
The fourth line is why the scratch directory is stable per task: the pinned CLI resumes a session only from the directory it started in.
End to end in CI. The tutorials CI on this PR ran the three claude_code tutorials live (CLI 2.1.142, real key) against the AgentEx server in Docker on Linux, reaching the agents through AGENTEX_AGENT_HOST=0.0.0.0. TestClaudeCodeLive passed for 060_claude_code (sync), 130_claude_code (async) and 140_claude_code (Temporal).
Local dev loop on macOS. The AgentEx server (scale-agentex main at 4ccf8b8, built from source for arm64) ran in Docker on Rancher Desktop. Inside its container host.docker.internal resolves to 192.168.5.2, the VM's gateway to the Mac. The sync template agent from this branch registered http://host.docker.internal:18002, and a message sent through the server came back with the agent's answer, with the default bind and with the override:
### branch-default: listening on 127.0.0.1:18002
with acp_url: http://host.docker.internal:18002
errors: none
answer: 42
### branch-override (AGENTEX_AGENT_HOST=0.0.0.0): listening on *:18002
with acp_url: http://host.docker.internal:18002
errors: none
answer: 42
For the async and Temporal templates, the server's calls reached the agents on 127.0.0.1 as well (2 and 3 POST /api 200s in the agents' logs), but each agent's own call back to this locally built server failed before the Claude CLI ran, in code this PR does not touch; their replies are shown by the tutorials CI above. Linux host-gateway setups dial the Docker bridge address instead, which a loopback bind does not serve; they need the override, as in the tutorials CI.
Supporting checks. New tests: tests/lib/cli/test_claude_code_spawn_policy.py renders each claude-code template and runs its real _spawn_claude in a child interpreter against a stub claude on PATH. It asserts the argv policy, a scratch directory outside the project (stable per task, not steerable by the task id, refused when its root is a link or world-writable), the four-variable environment, and -r <session_id> resuming in the same directory. tests/lib/cli/test_agents_run_host.py asserts the --host default, flag and environment variable, and the host passed to uvicorn in normal and debug mode.
$ uv run pytest tests/lib/cli/test_claude_code_spawn_policy.py tests/lib/cli/test_agents_run_host.py tests/lib/cli/test_init_templates.py tests/lib/cli/test_run_handlers_streaming.py -n 0
45 passed in 57.83s
$ ./scripts/test # CI's test job, both Python versions (at d9ccd2da)
==> Running tests for Python >=3.12.0,<3.13
1772 passed, 1622 skipped in 49.57s
==> Running tests for Python >=3.13.0,<3.14
1772 passed, 1622 skipped in 49.98s
$ ./scripts/lint # ruff, pyright, import check
All checks passed!
0 errors, 0 warnings, 0 informations
CI on d38f5ce: all 49 checks pass. The test job (both Python versions) shows 1778 passed, 1622 skipped; its first attempt hit one failure in tests/lib/test_build_provenance.py::test_capture_clean_tree (a temporary .git/objects/maintenance.lock vanished mid-hash, in code this PR does not touch; it passes locally 5/5) and passed on re-run.
host-gateway, or from another machine, must now pass --host 0.0.0.0 or set AGENTEX_AGENT_HOST=0.0.0.0. Rancher Desktop on macOS (VM-to-host hop tested above) and the server's no-Docker mode (which dials 127.0.0.1) keep working without it.CLAUDE_POLICY_ARGS on purpose, for example with --add-dir.ANTHROPIC_BASE_URL, HTTPS_PROXY) must add that variable to CLAUDE_ENV_KEYS; the template READMEs say so._spawn_claude functions; its new test calls the old one-argument signature). Whichever merges second needs a small rebase.🤖 Generated with Claude Code
The PR restricts Claude's tools, environment, and working directory, and makes local ACP servers listen on loopback by default.
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Local caller] --> B[ACP on loopback by default]
B --> C[Check private scratch root]
C --> D[Stable task directory]
D --> E[Claude with read-only tools and limited environment]
Reviews (2) · Last reviewed commit: "fix(cli): refuse a claude-code scratch r..." · Reviewed by Greptile
Sorry, something went wrong.
There was an error while loading. Please reload this page.
…opback VULN-3136. The three claude-code templates (and their tutorials) spawned `claude -p` in the ACP's working directory, which under `agentex agents run` is the project directory holding .env, with the ACP's full environment and no tool policy, while `agentex agents run` served the unauthenticated ACP on 0.0.0.0. - Spawn the CLI in a per-task scratch directory under the temp dir (stable across turns, so Temporal `-r <session_id>` resume still works), with only ANTHROPIC_API_KEY, PATH, HOME and USER in its environment. - Pass an explicit policy: read-only tools (--tools Read,Glob,Grep), --permission-mode dontAsk, no setting sources, --strict-mcp-config. - Pin @anthropic-ai/[email protected] in the template and tutorial Dockerfiles, the version the tutorials CI installs. - `agentex agents run` now listens on 127.0.0.1. Override with --host 0.0.0.0 or AGENTEX_AGENT_HOST=0.0.0.0; the tutorials CI sets the variable because its Docker server reaches agents through host-gateway. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
…trol On a shared /tmp another account could pre-create agentex-claude-code, or link it to the developer's project, and mkdir(exist_ok=True) would accept it. Check the root with lstat: a real directory, owned by this user, with no group or other access (POSIX only; the Windows temp dir is per-user). Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.