Summary
CompositeFilesystem routes root-level ls/grep operations to the default backend with an absolute / path, and the UNRESTRICTED resolver passes absolute paths through unchanged — so the operation anchors at the OS root instead of the workspace root. In the default RemoteFilesystemSpec configuration, list_files(".") from the model enumerates the drive root (C:\ top level on Windows), breaking workspace containment.
This is point 4 of the #3245 evaluation ("list_files('.') can enumerate outside the workspace (drive root), since no containment check exists on that path"), which was deliberately tracked separately from PR #3247 (that PR blocks traversal only for opt-in sharedLocalWorkspace users; the default namespaced backend is still UNRESTRICTED).
Framing correction (per evaluation): RemoteFilesystemSpec is itself opt-in (it throws at build time without a distributed store); the harness default is LocalFilesystemSpec (ROOTED), where ls("/") is denied. The accurate scope is configuration-gated host-root disclosure via the UNRESTRICTED backend mode — and the escape is namespace-independent (/ is exempt from prefixing). Ordinary absolute paths passing through the same backend are tracked separately in #3258.
Root cause (verified against main @ ed77530)
CompositeFilesystem.ls — agentscope-harness/.../CompositeFilesystem.java:199-201: if ("/".equals(path) || ".".equals(path)) { ... defaultBackend.ls(runtimeContext, "/") ... }
CompositeFilesystem.grep — :280-282: the root branch passes path through verbatim to defaultBackend.grep(...); a "/" path hits the same passthrough.
LocalFilesystem.resolveUnrestricted — LocalFilesystem.java:681-687: absolute paths are returned unchanged.
LocalFilesystem.glob is already anchored (resolvePath(".")) — only ls and grep("/") are affected.
Proposed fix
Route the default backend's root operations through the backend's own root anchor: pass a blank path instead of "/". LocalFilesystem.resolvePath already resolves a blank key to the backend cwd (LocalFilesystem.java:598-600) and applyNamespacePrefix leaves blank keys unprefixed (:691-693) — a one-line change per site. Shared-route backends keep their "/" (a virtual store root for them). No change to per-user namespace isolation for ordinary (non-root) paths.
Tests required (per the #3245 follow-up evaluation)
- a
.// listing cannot escape the workspace in both spec modes (namespaced default backend and sharedLocalWorkspace=true)
grep_files(path="/") is contained the same way
- per-user namespace isolation for ordinary paths is unchanged (existing tests pin this and must keep passing)
Summary
CompositeFilesystemroutes root-levells/grepoperations to the default backend with an absolute/path, and the UNRESTRICTED resolver passes absolute paths through unchanged — so the operation anchors at the OS root instead of the workspace root. In the defaultRemoteFilesystemSpecconfiguration,list_files(".")from the model enumerates the drive root (C:\top level on Windows), breaking workspace containment.This is point 4 of the #3245 evaluation ("list_files('.') can enumerate outside the workspace (drive root), since no containment check exists on that path"), which was deliberately tracked separately from PR #3247 (that PR blocks traversal only for opt-in
sharedLocalWorkspaceusers; the default namespaced backend is still UNRESTRICTED).Framing correction (per evaluation):
RemoteFilesystemSpecis itself opt-in (it throws at build time without a distributed store); the harness default isLocalFilesystemSpec(ROOTED), wherels("/")is denied. The accurate scope is configuration-gated host-root disclosure via the UNRESTRICTED backend mode — and the escape is namespace-independent (/is exempt from prefixing). Ordinary absolute paths passing through the same backend are tracked separately in #3258.Root cause (verified against main @ ed77530)
CompositeFilesystem.ls—agentscope-harness/.../CompositeFilesystem.java:199-201:if ("/".equals(path) || ".".equals(path)) { ... defaultBackend.ls(runtimeContext, "/") ... }CompositeFilesystem.grep—:280-282: the root branch passespaththrough verbatim todefaultBackend.grep(...); a"/"path hits the same passthrough.LocalFilesystem.resolveUnrestricted—LocalFilesystem.java:681-687: absolute paths are returned unchanged.LocalFilesystem.globis already anchored (resolvePath(".")) — onlylsandgrep("/")are affected.Proposed fix
Route the default backend's root operations through the backend's own root anchor: pass a blank path instead of
"/".LocalFilesystem.resolvePathalready resolves a blank key to the backend cwd (LocalFilesystem.java:598-600) andapplyNamespacePrefixleaves blank keys unprefixed (:691-693) — a one-line change per site. Shared-route backends keep their"/"(a virtual store root for them). No change to per-user namespace isolation for ordinary (non-root) paths.Tests required (per the #3245 follow-up evaluation)
.//listing cannot escape the workspace in both spec modes (namespaced default backend andsharedLocalWorkspace=true)grep_files(path="/")is contained the same way