Skip to content

fix(sandbox): pass real workspace to sessions_spawn when workspaceAccess is ro#40757

Merged
mcaxtr merged 2 commits intoopenclaw:mainfrom
dsantoreis:fix/40582-sandbox-workspace-clean
Mar 10, 2026
Merged

fix(sandbox): pass real workspace to sessions_spawn when workspaceAccess is ro#40757
mcaxtr merged 2 commits intoopenclaw:mainfrom
dsantoreis:fix/40582-sandbox-workspace-clean

Conversation

@dsantoreis
Copy link
Copy Markdown
Contributor

@dsantoreis dsantoreis commented Mar 9, 2026

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, createSessionsSpawnTool received the sandboxed copy as its workspaceDir, which subagents then inherited as their agentWorkspaceDir. 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) added workspaceDir: effectiveWorkspace to the tools call in attempt.ts. When workspaceAccess === "ro", effectiveWorkspace = sandbox.workspaceDir (sandbox copy), which propagated into createSessionsSpawnTool as the workspace for subagents to inherit.

Fix: add a spawnWorkspaceDir field threaded through createOpenClawCodingToolscreateOpenClawToolscreateSessionsSpawnTool. In attempt.ts, when a read-only sandbox is active, spawnWorkspaceDir is set to resolvedWorkspace (the real workspace path). File tools continue to use effectiveWorkspace (the sandbox copy) for isolation. No behavior change when workspaceAccess === "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.

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: XS labels Mar 9, 2026
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 9, 2026

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟠 High workspaceAccess="none" can be bypassed via sessions_spawn inheriting real workspace path
2 🟠 High Sandbox workspace isolation bypass via spawnWorkspaceDir in sessions_spawn inheritance

1. 🟠 workspaceAccess="none" can be bypassed via sessions_spawn inheriting real workspace path

Property Value
Severity High
CWE CWE-284
Location src/agents/pi-embedded-runner/run/attempt.ts:869-876

Description

When a session is sandboxed with workspaceAccess: "none", the intent is to prevent exposing the real host workspace directory to the agent. However, the new spawnWorkspaceDir behavior passes the real workspace path (resolvedWorkspace) into tool creation whenever workspaceAccess !== "rw" (i.e., for both ro and none).

This value is then fed into sessions_spawn as its workspaceDir context, which becomes the spawned run's workspaceDir override. A spawned child targeting an agent/runtime configured with workspaceAccess: "ro" or "rw" can therefore receive a sandbox mount of the real workspace path, bypassing the parent session’s workspaceAccess: "none" restriction.

Vulnerable flow:

  • Parent run in sandbox with workspaceAccess: "none" sets spawnWorkspaceDir to the real workspace
  • openclaw-tools passes that as workspaceDir to createSessionsSpawnTool
  • sessions_spawn forwards workspaceDir into spawnSubagentDirect context
  • spawnSubagentDirect writes it into spawned run metadata (workspaceDir), which the gateway accepts as an internal-only workspace override for spawned runs
  • Child run starts with params.workspaceDir set to that real path; if the child’s sandbox policy is ro/rw, the real workspace gets mounted/readable (and writable for rw)

This is a privilege escalation / access-control bypass of the workspaceAccess: "none" confinement model.

Vulnerable code:

// attempt.ts
spawnWorkspaceDir:
  sandbox?.enabled && sandbox.workspaceAccess !== "rw" ? resolvedWorkspace : undefined,
// openclaw-tools.ts
createSessionsSpawnTool({
  ...,
  workspaceDir: spawnWorkspaceDir,
})

Recommendation

Treat workspaceAccess: "none" as non-inheritable for workspace paths.

Minimal fix (recommended): only pass spawnWorkspaceDir when workspaceAccess === "ro" (not none). That preserves the intended none isolation while keeping the ro correctness improvement.

// 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 createSessionsSpawnTool or spawnSubagentDirect) by ignoring/clearing ctx.workspaceDir when the requester session is sandboxed with workspaceAccess: "none", so callers cannot bypass by other entry points.


2. 🟠 Sandbox workspace isolation bypass via spawnWorkspaceDir in sessions_spawn inheritance

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:

  • runEmbeddedAttempt sets spawnWorkspaceDir to resolvedWorkspace (real workspace) whenever sandbox.workspaceAccess !== "rw".
  • createOpenClawTools resolves spawnWorkspaceDir and uses it as the workspaceDir passed into createSessionsSpawnTool.
  • sessions_spawn calls spawnSubagentDirect with ctx.workspaceDir coming from that value; spawnSubagentDirect uses 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", /agent becomes 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 allowed agentId), /workspace becomes a writable bind mount of the real workspace → privilege escalation / write access despite the parent being confined to a copied workspace.

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:

  1. Do not override sessions_spawn workspaceDir from a sandboxed copied workspace (keep inheriting the sandbox copy):
// openclaw-tools.ts
createSessionsSpawnTool({
  ...,// Keep subagents inside the same effective workspace boundary
  workspaceDir,
});
  1. 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.

  2. 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 workspaceAccess is less restrictive than the requester’s).


Analyzed PR: #40757 at commit 0e8b27b

Last updated on: 2026-03-10T08:41:39Z

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This 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 spawnWorkspaceDir field threaded through createOpenClawCodingToolscreateOpenClawToolscreateSessionsSpawnTool, while leaving file tool isolation intact via effectiveWorkspace.

Key changes:

  • attempt.ts: Sets spawnWorkspaceDir = resolvedWorkspace when sandbox.workspaceAccess !== "rw", exactly mirroring the condition under which effectiveWorkspace diverges from resolvedWorkspace
  • pi-tools.ts / openclaw-tools.ts: New spawnWorkspaceDir option with proper JSDoc, resolved and forwarded through the tool creation chain; falls back to workspaceDir when unset, preserving existing behavior for rw or no-sandbox sessions
  • No changes to how file tools consume workspaceDir — sandbox isolation is preserved for the running session; only the workspace inherited by spawned subagents is corrected

Confidence Score: 5/5

  • This PR is safe to merge — it is a narrowly scoped, well-reasoned bug fix with no behavior change for non-sandbox or rw-sandbox sessions.
  • The condition used for spawnWorkspaceDir in attempt.ts exactly mirrors the existing effectiveWorkspace branching, so it activates precisely when and only when the sandbox copy diverges from the real workspace. resolveWorkspaceRoot is idempotent on absolute paths, so the double-resolution across pi-tools.ts and openclaw-tools.ts is harmless. The fallback to workspaceDir when spawnWorkspaceDir is unset preserves all existing behavior. No other spawn paths were missed — the only subagent workspace inheritance goes through createSessionsSpawnTool via this chain.
  • No files require special attention.

Last reviewed commit: dcbc2a9

@dsantoreis

This comment was marked as spam.

@dsantoreis

This comment was marked as spam.

@mcaxtr mcaxtr self-assigned this Mar 10, 2026
@mcaxtr mcaxtr force-pushed the fix/40582-sandbox-workspace-clean branch from fd2ef61 to a1acb82 Compare March 10, 2026 06:18
@mcaxtr mcaxtr force-pushed the fix/40582-sandbox-workspace-clean branch from a1acb82 to ffdb5f5 Compare March 10, 2026 06:37
@mcaxtr mcaxtr force-pushed the fix/40582-sandbox-workspace-clean branch from ffdb5f5 to 0e8b27b Compare March 10, 2026 07:11
@mcaxtr mcaxtr merged commit 3495563 into openclaw:main Mar 10, 2026
7 checks passed
@mcaxtr
Copy link
Copy Markdown
Contributor

mcaxtr commented Mar 10, 2026

Merged via squash.

Thanks @dsantoreis!

mrosmarin added a commit to mrosmarin/openclaw that referenced this pull request Mar 10, 2026
* 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)
  ...
Moshiii pushed a commit to Moshiii/openclaw that referenced this pull request Mar 11, 2026
…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
Moshiii pushed a commit to Moshiii/openclaw that referenced this pull request Mar 11, 2026
…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
frankekn pushed a commit to MoerAI/openclaw that referenced this pull request Mar 11, 2026
…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
frankekn pushed a commit to Effet/openclaw that referenced this pull request Mar 11, 2026
…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
frankekn pushed a commit to ImLukeF/openclaw that referenced this pull request Mar 11, 2026
…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
dominicnunez pushed a commit to dominicnunez/openclaw that referenced this pull request Mar 11, 2026
…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
dhoman pushed a commit to dhoman/chrono-claw that referenced this pull request Mar 11, 2026
…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
ahelpercn pushed a commit to ahelpercn/openclaw that referenced this pull request Mar 12, 2026
…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
Ruijie-Ysp pushed a commit to Ruijie-Ysp/clawdbot that referenced this pull request Mar 12, 2026
…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
qipyle pushed a commit to qipyle/openclaw that referenced this pull request Mar 12, 2026
…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
Interstellar-code pushed a commit to Interstellar-code/operator1 that referenced this pull request Mar 16, 2026
…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)
senw-developers pushed a commit to senw-developers/va-openclaw that referenced this pull request Mar 17, 2026
…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
V-Gutierrez pushed a commit to V-Gutierrez/openclaw-vendor that referenced this pull request Mar 17, 2026
…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
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 22, 2026
…ess is ro (openclaw#40757)

(cherry picked from commit 3495563)

Co-authored-by: Daniel Reis <[email protected]>
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 22, 2026
…ess is ro (openclaw#40757)

(cherry picked from commit 3495563)

Co-authored-by: Daniel Reis <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: workspaceAccess: "ro" mounts sandbox directory instead of configured workspace

2 participants