fix(sandbox): pass real workspace to sessions_spawn when workspaceAccess is ro#40757
Conversation
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟠 workspaceAccess="none" can be bypassed via sessions_spawn inheriting real workspace path
DescriptionWhen a session is sandboxed with This value is then fed into Vulnerable flow:
This is a privilege escalation / access-control bypass of the Vulnerable code: // attempt.ts
spawnWorkspaceDir:
sandbox?.enabled && sandbox.workspaceAccess !== "rw" ? resolvedWorkspace : undefined,// openclaw-tools.ts
createSessionsSpawnTool({
...,
workspaceDir: spawnWorkspaceDir,
})RecommendationTreat Minimal fix (recommended): only pass // src/agents/pi-embedded-runner/run/attempt.ts
spawnWorkspaceDir:
sandbox?.enabled && sandbox.workspaceAccess === "ro" ? resolvedWorkspace : undefined,Defense-in-depth: also enforce this at the spawn layer (e.g., in 2. 🟠 Sandbox workspace isolation bypass via
|
| Property | Value |
|---|---|
| Severity | High |
| CWE | CWE-284 |
| Location | src/agents/openclaw-tools.ts:82-196 |
Description
When a parent agent run is sandboxed with a copied workspace (workspaceAccess ro/none), the new spawnWorkspaceDir plumbing causes sessions_spawn to pass the real configured workspace path to spawned subagents.
This weakens the sandbox boundary that previously kept subagents operating only on the parent’s sandbox copy:
runEmbeddedAttemptsetsspawnWorkspaceDirtoresolvedWorkspace(real workspace) wheneversandbox.workspaceAccess !== "rw".createOpenClawToolsresolvesspawnWorkspaceDirand uses it as theworkspaceDirpassed intocreateSessionsSpawnTool.sessions_spawncallsspawnSubagentDirectwithctx.workspaceDircoming from that value;spawnSubagentDirectuses it as an explicit workspace override for the child run metadata.- The child run then resolves its sandbox context using that workspace path as its agent workspace:
- If the spawned agent’s sandbox config has
workspaceAccess: "ro",/agentbecomes a bind mount of the real workspace (read-only) → information disclosure beyond the parent’s sandbox copy. - If the spawned agent’s sandbox config has
workspaceAccess: "rw"(e.g., a different allowedagentId),/workspacebecomes a writable bind mount of the real workspace → privilege escalation / write access despite the parent being confined to a copied workspace.
- If the spawned agent’s sandbox config has
This is practical whenever the parent is sandboxed and allowed to choose a different agentId via sessions_spawn (controlled by agents.list[*].subagents.allowAgents). Prior to this change, the child inherited the parent’s sandbox copy path, which prevented escalation to the real workspace even when the target agent had a more permissive workspaceAccess.
Recommendation
Treat the parent sandbox’s effective workspace and workspaceAccess as the maximum privilege that spawned subagents may receive.
Concrete options:
- Do not override
sessions_spawnworkspaceDir from a sandboxed copied workspace (keep inheriting the sandbox copy):
// openclaw-tools.ts
createSessionsSpawnTool({
...,
// Keep subagents inside the same effective workspace boundary
workspaceDir,
});-
If you need subagents to know the real workspace path for UX, pass it as a non-authoritative hint (not used for mounts/FS resolution), or add a separate field (e.g.
agentWorkspaceDirHint) that is never used to mount/resolve filesystem access. -
Add an enforcement check in the spawn path so that sandboxed requesters cannot spawn children with broader workspace access (e.g., prevent spawning an agentId whose sandbox
workspaceAccessis less restrictive than the requester’s).
Analyzed PR: #40757 at commit 0e8b27b
Last updated on: 2026-03-10T08:41:39Z
Greptile SummaryThis PR fixes a regression introduced in v2026.3.7 where subagents spawned by a session running inside a read-only sandbox would incorrectly inherit the sandboxed workspace directory instead of the real configured workspace path. The fix introduces a Key changes:
Confidence Score: 5/5
Last reviewed commit: dcbc2a9 |
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
fd2ef61 to
a1acb82
Compare
a1acb82 to
ffdb5f5
Compare
ffdb5f5 to
0e8b27b
Compare
|
Merged via squash.
Thanks @dsantoreis! |
* main: (43 commits) docs: add openclaw#42173 to CHANGELOG — strip leaked model control tokens (openclaw#42216) Agents: align onPayload callback and OAuth imports docs: add Tengji (George) Zhang to maintainer table (openclaw#42190) fix: strip leaked model control tokens from user-facing text (openclaw#42173) Changelog: add unreleased March 9 entries chore: add .dev-state to .gitignore (openclaw#41848) fix(agents): avoid duplicate same-provider cooldown probes in fallback runs (openclaw#41711) fix(mattermost): preserve markdown formatting and native tables (openclaw#18655) feat(acp): add resumeSessionId to sessions_spawn for ACP session resume (openclaw#41847) ACPX: bump bundled acpx to 0.1.16 (openclaw#41975) mattermost: fix DM media upload for unprefixed user IDs (openclaw#29925) fix(msteams): use General channel conversation ID as team key for Bot Framework compatibility (openclaw#41838) fix(mattermost): read replyTo param in plugin handleAction send (openclaw#41176) fix(sandbox): pass real workspace to sessions_spawn when workspaceAccess is ro (openclaw#40757) fix(ui): replace Manual RPC text input with sorted method dropdown (openclaw#14967) CI: select Swift 6.2 toolchain for CodeQL (openclaw#41787) fix(agents): forward memory flush write path (openclaw#41761) fix(telegram): move network fallback to resolver-scoped dispatchers (openclaw#40740) fix(security): harden replaceMarkers() to catch space/underscore boundary marker variants (openclaw#35983) fix(web-search): recover OpenRouter Perplexity citations from message annotations (openclaw#40881) ...
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr (cherry picked from commit 3495563)
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) Merged via squash. Prepared head SHA: 0e8b27b Co-authored-by: dsantoreis <[email protected]> Co-authored-by: mcaxtr <[email protected]> Reviewed-by: @mcaxtr
…ess is ro (openclaw#40757) (cherry picked from commit 3495563) Co-authored-by: Daniel Reis <[email protected]>
…ess is ro (openclaw#40757) (cherry picked from commit 3495563) Co-authored-by: Daniel Reis <[email protected]>
When a session runs inside a read-only sandbox, the effective workspace sent to file tools is the sandboxed copy of the workspace directory, not the actual agent workspace. Before this change,
createSessionsSpawnToolreceived the sandboxed copy as itsworkspaceDir, which subagents then inherited as theiragentWorkspaceDir. This caused the/agent/mount in subagent Docker containers to point to the parent session's sandbox dir instead of the configured workspace (regression since v2026.3.7).Root cause: commit
fee91fefc(context plugin system, v2026.3.7) addedworkspaceDir: effectiveWorkspaceto the tools call inattempt.ts. WhenworkspaceAccess === "ro",effectiveWorkspace = sandbox.workspaceDir(sandbox copy), which propagated intocreateSessionsSpawnToolas the workspace for subagents to inherit.Fix: add a
spawnWorkspaceDirfield threaded throughcreateOpenClawCodingTools→createOpenClawTools→createSessionsSpawnTool. Inattempt.ts, when a read-only sandbox is active,spawnWorkspaceDiris set toresolvedWorkspace(the real workspace path). File tools continue to useeffectiveWorkspace(the sandbox copy) for isolation. No behavior change whenworkspaceAccess === "rw"or no sandbox.Fixes #40582.
Resubmission of #40601 (closed by barnacle bot). This time with a clean single-commit branch.
AI-assisted (Claude). Fully tested and reviewed.