feat: isolated cwd_mode for untrusted launcher CWD (RM-13) - #33
Merged
Conversation
Add `cwd_mode` registry field. "project" (default/empty) preserves existing byte-for-byte behavior; "isolated" skips project-root detection, sets `--workdir /root`, and never bind-mounts the host CWD. Argument path mapping still runs against a sentinel root so every path falls through to the existing external `/cb/mounts/N` mount logic. `project_volumes` is rejected when `cwd_mode = "isolated"` because a project-scoped volume requires a project identity. `cb inspect` and `cb trace` expose the isolated mode behavior. Registry, dockerrun, and CLI tests cover parsing, validation, the default-mode regression (byte-for-byte unchanged argv), isolated-mode no-CWD-mount/workdir, and the external path mapping. README documents the new field. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…13 r2) GLM-5.2 flagged that the default-mode regression test only called buildDockerArgs with a hand-built runContext, never exercising the refactored resolveRunContext or the rel != "." branch. Add three tests: - resolveRunContext with no project markers: root == cwd, found == false, workspaceRoot == "/workspace", containerWD == "/workspace" - resolveRunContext with cwd nested under a .git marker: root == parent, found == true, containerWD == "/workspace/subdir" - end-to-end default-mode argv: resolveRunContext -> buildDockerArgs equals the pre-refactor argv exactly Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds cwd_mode = "isolated" to prevent untrusted launcher CWDs from becoming project mounts.
Changes:
- Adds and validates the registry setting.
- Refactors Docker context, argument, and volume construction.
- Updates diagnostics, documentation, and tests.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents isolated mode. |
| internal/registry/registry.go | Parses and validates cwd_mode. |
| internal/registry/registry_test.go | Tests registry behavior. |
| internal/dockerrun/dockerrun.go | Implements isolated execution. |
| internal/dockerrun/dockerrun_test.go | Tests Docker arguments and context resolution. |
| internal/cli/cli.go | Updates inspect and trace output. |
| internal/cli/cli_test.go | Tests isolated diagnostics. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…-13 r3) The python provider has its own project/compat venv split via pathmap.PythonEnvID. With cwd_mode = "isolated", RunTool forces found=false, which would make an isolated python tool silently use the shared "cb-python-313-global" compat volume. Reject the combination at registry load rather than patching every consumer (cli.Env, internal/state, self-test cleanup). Also reject cwd_mode = "isolated" with a non-empty project_markers list, because project_markers are dead configuration when project-root detection is skipped. Add tests for both rejections and a README sentence noting that cb expose generated profiles do not inherit cwd_mode. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…Black/container-bin into roadmap/RM-13-cwd-mode
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (2)
README.md:208
- The documented compatibility rules omit two registry-load failures introduced here: isolated mode is also rejected for the
pythonprovider and wheneverproject_markersis declared. As written, users are told onlyproject_volumesis incompatible and will get unexpected validation errors for otherwise documented profiles. List all three restrictions here.
under `/cb/mounts/N`. `cwd_mode = "isolated"` cannot be combined with
`project_volumes` (a project-scoped volume conceptually requires a project
identity), but `shared_volumes`, `host_mounts` and environment allowlisting work
exactly as they do in project mode. Exposed profiles created by `cb expose`
from an npm-shaped source profile do **not** inherit that source's `cwd_mode`.
internal/dockerrun/dockerrun_test.go:253
- This test hand-constructs the isolated
runContext, so it never exercises the new isolated branch inresolveRunContext. A regression there could restore the wrong workdir/root while this builder test stays green—the same gap the new default-mode resolver tests avoid. Resolve the context from the tool here and assert its isolated fields before building the argv.
ctx := runContext{
cwd: cwd,
root: IsolatedRoot,
workspaceRoot: "/root",
containerWD: "/root",
README's isolated-mode section only named project_volumes as incompatible, not the python-provider and project_markers rejections added in the r3 commit. Listed all three. TestBuildDockerArgsIsolatedNoCwdMount hand-built its runContext, the same gap GLM caught for the default-mode tests in round 2 -- a regression in resolveRunContext's isolated branch could restore a real root/workdir while that builder-only test stayed green. Added TestResolveRunContextIsolated calling resolveRunContext directly and chaining into buildDockerArgs, mirroring the round-2 default-mode fix pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Why
Filed as a follow-up in issue #2 (split out of RM-12/issue #29's "Related finding" section,
because it changes a different mechanism —
RunTool's project-root logic, not a new declaredmount). Confirmed real during RM-12's scoping:
dockerrun.RunToolalways treats the process'sCWD as, or as the basis for, a project. For an interactive CLI invocation (
cd D:\my-project; npx eslint .) that's correct. For a background/GUI/MCP launcher that inherits an arbitrary CWDit doesn't control — e.g. Claude Desktop launching an MCP server shim with
C:\Windows\System32as the inherited working directory — it isn't: today that produces a realbind mount of
System32into the container, plus (for a stateful tool) a project-scoped volumekeyed by a hash of that path.
What changed
New registry field
cwd_mode, two values:"project"(default, and an unset value is byte-for-byte identical to it) — unchangedbehavior. This PR does not touch the default path's outcome, only how it's computed internally
(see refactor note below).
"isolated"— skips project-root discovery entirely, never bind-mounts the host CWD, sets--workdir /root, and routes any argument path through the existing external/cb/mounts/Nnarrow-mount logic (no new mount mechanism — the same code path already used for any argument
outside a project root).
project_volumeson an isolated tool is a registry-load-time error;shared_volumes/host_mounts/env allowlisting are completely unaffected, which is exactly whyRM-12's
host_mountsand this feature are designed to compose (an isolated background toolgets its state via
shared_volumes/host_mounts, never via a project-scoped volume).RunToolwas refactored into three functions along the way —resolveRunContext(computesroot/workspace/workdir),
buildDockerArgs(pure argv construction, no Docker/daemon calls), andensureDockerVolumes(the volume side-effects) — so the isolated-mode branch and its testcoverage stay clean without duplicating the whole function.
Review depth
This touches the project's own documented "highest-risk area" (a silently-wrong-mount bug class,
not a crash), so it got the corresponding scrutiny: SWE-1.7 Max implement → GLM-5.2 blind verify.
Round 1 found one real gap — the default-mode regression test only called the pure argv-builder
with a hand-constructed context, never the refactored
resolveRunContextitself, so the branchhandling a CWD nested under a discovered project root (
rel != ".") had zero coverage despitebeing exactly the kind of thing a refactor of this function could silently break. Fixed with
three targeted tests exercising
resolveRunContextdirectly (no markers, nested-under-marker,and an end-to-end default-mode argv pin), verified in a second GLM pass to confirm they're not
tautological — each asserts against independently-derived expected values, not values echoed
back from the function under test.
Independently re-validated at every stage (not just the implementer's reports) inside
golang:1.24:Clean throughout.
Validation
🤖 Generated with a multi-agent pipeline (SWE-1.7 Max implementer, GLM-5.2 High blind verifier, orchestrated by Claude Code)