Skip to content

fix(sandbox): allow cron tool in sandbox mode (gateway-routed, not containerized)#53565

Open
lupuletic wants to merge 1 commit intoopenclaw:mainfrom
lupuletic:fix/sandbox-cron-allow-50303
Open

fix(sandbox): allow cron tool in sandbox mode (gateway-routed, not containerized)#53565
lupuletic wants to merge 1 commit intoopenclaw:mainfrom
lupuletic:fix/sandbox-cron-allow-50303

Conversation

@lupuletic
Copy link
Copy Markdown
Contributor

The cron tool is in DEFAULT_TOOL_DENY for sandbox mode, so when sandbox.mode='all' it is stripped from the agent's tool list in conversation — causing the model to hallucinate fake tool calls instead of invoking the real cron tool, which is gateway-routed via WebSocket RPC and doesn't need sandbox containment.

Closes #50303

Changes:

  • Remove 'cron' from DEFAULT_TOOL_DENY in src/agents/sandbox/constants.ts
  • Add 'cron' to DEFAULT_TOOL_ALLOW in src/agents/sandbox/constants.ts (it uses callGatewayTool WebSocket RPC dispatch, not container execution)
  • Update sandbox tool-policy tests in src/agents/tool-policy.test.ts to reflect that cron is now allowed by default in sandbox sessions
  • Add a pipeline regression test showing a sandboxed chat session exposes cron after policy filtering but still strips browser
  • Conditionally add cron sandbox hint in system-prompt.ts gated on availableTools.has('cron')
  • Run pnpm test to verify no regressions

Testing:

  • pnpm build && pnpm check && pnpm test
    1. Unit: verify isToolAllowed returns true for cron under default sandbox policy. 2) Pipeline: verify applyToolPolicyPipeline exposes cron but strips browser in sandboxed chat. 3) Manual: with sandbox.mode='all', ask agent from conversation to create a cron job and verify it appears in openclaw cron list.

AI-assisted (Claude + Codex committee consensus, fully tested).

@lupuletic lupuletic requested a review from a team as a code owner March 24, 2026 09:10
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 24, 2026

🔒 Aisle Security Analysis

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

# Severity Title
1 🟠 High Sandbox default policy exposes admin-scoped Gateway cron RPC via allowed cron tool
Vulnerabilities

1. 🟠 Sandbox default policy exposes admin-scoped Gateway cron RPC via allowed cron tool

Property Value
Severity High
CWE CWE-284
Location src/agents/sandbox/constants.ts:13-34

Description

The sandbox tool policy now allows the cron tool by default. Although it is described as “gateway-routed via WebSocket RPC”, this still exposes Gateway cron RPC methods to sandboxed agents.

Why this is a security issue:

  • The cron tool calls callGatewayTool(...) (WebSocket RPC) for actions like cron.add, cron.update, cron.remove, and cron.run.
  • These Gateway methods are classified as admin-scope operations (operator.admin).
  • As a result, a compromised/malicious agent operating in a sandbox session can potentially:
    • create/update/remove cron jobs (persistence)
    • trigger jobs immediately
    • schedule payloads that inject systemEvent into the main session or run agentTurn in named/persistent sessions (depending on cron payload/sessionTarget validation)

Even if the Docker container is not executing the job, the sandbox boundary is still crossed because privileged actions are delegated to the Gateway.

Vulnerable change (policy exposure):

export const DEFAULT_TOOL_ALLOW = [// ...
  "cron",// ...
] as const;

export const DEFAULT_TOOL_DENY = ["browser", "canvas", "nodes", "gateway", ...CHANNEL_IDS] as const;

And the cron tool routes to admin-scoped RPC methods:

return jsonResult(await callGateway("cron.add", gatewayOpts, job));

If the sandbox environment has access to a Gateway token (via config/env/default credentials resolution), this becomes a practical escalation path from “sandbox agent” to “Gateway admin cron control”.

Recommendation

Treat cron as a privileged Gateway-facing tool and do not allow it by default in sandbox unless the sandbox is explicitly trusted.

Recommended options:

  1. Revert default allow and require explicit opt-in per sandbox policy:
// DEFAULT_TOOL_ALLOW: remove "cron"// DEFAULT_TOOL_DENY: include "cron" by default
  1. If cron must be available, enforce least privilege for sandbox usage by restricting cron RPC capabilities server-side (preferred):
  • Introduce separate Gateway methods for “sandbox cron” that only allow safe operations (e.g., list/status only), or
  • Enforce an authorization check in the Gateway cron handlers so that tokens/scopes used by sandbox agents cannot call cron.add/update/remove/run.
  1. Add hard validation to prevent persistence/cross-session effects from sandbox-created jobs:
  • require sessionTarget="isolated" for sandbox-created jobs
  • forbid session:<custom-id> and main targets from sandbox
  • enforce short TTL / auto-deleteAfterRun for sandbox jobs

This ensures sandbox agents cannot convert cron into a persistence or privilege-escalation mechanism.


Analyzed PR: #53565 at commit 837ee7f

Last updated on: 2026-03-29T14:15:36Z

Latest run failed. Keeping previous successful results. Trace ID: 019d3d6159efe01fb503d1bd2c316cae.

Last updated on: 2026-03-30T09:40:18Z

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: XS labels Mar 24, 2026
@lupuletic
Copy link
Copy Markdown
Contributor Author

AI-Assisted PR Checklist

  • Mark as AI-assisted in the PR title or description
  • Note the degree of testing (untested / lightly tested / fully tested)
    • Fully tested: pnpm build, pnpm check, and pnpm test all pass. Two new regression tests added (sandbox tool-policy constants + pipeline filtering).
  • Include prompts or session logs if possible (super helpful!)
    • Auto-maintain pipeline run with Claude + Codex committee analysis, fix, two-round post-fix review, and revision cycle. Codex caught the gateway over-exposure in round 1, leading to a narrower fix (cron-only).
  • Confirm you understand what the code does
    • Moves cron from DEFAULT_TOOL_DENY to DEFAULT_TOOL_ALLOW in sandbox constants, since cron uses callGatewayTool (WebSocket RPC to host gateway) and never executes inside the Docker sandbox. Adds conditional system prompt hint gated on availableTools.has("cron"). Keeps gateway in DENY because it exposes admin actions (restart, config.apply, etc.).
  • If you have access to Codex, run codex review --base origin/main locally and address the findings before asking for review
  • Resolve or reply to bot review conversations after you address them

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 24, 2026

Greptile Summary

This PR correctly removes cron from DEFAULT_TOOL_DENY and adds it to DEFAULT_TOOL_ALLOW in the sandbox constants, fixing a bug where the cron tool was unavailable in sandbox.mode='all' sessions. Since the cron tool is dispatched via callGatewayTool WebSocket RPC and never executes inside the Docker container, denying it at the sandbox policy layer was incorrect and caused the model to hallucinate fake cron calls.

  • constants.ts: Clean change; removes cron from the deny list and adds it to the allow list with an explanatory comment.
  • system-prompt.ts: Adds a cron sandbox hint gated on availableTools.has("cron"), following the same pattern used for other capability hints in the sandbox section.
  • tool-policy.test.ts: Adds two regression tests. The tests are functionally correct but share duplicate assertions — both compute the same resolved policy and both assert isToolAllowed(policy, "cron") === true. The second test's only unique assertion is the gateway check.

Note: The cron tool retains its ownerOnly protection via applyOwnerOnlyToolPolicy, so non-owner senders still cannot access cron even after this sandbox policy change.

Confidence Score: 5/5

  • Safe to merge — the fix is correct, well-tested, and does not introduce regressions; the owner-only protection layer for cron remains intact.
  • The core change (moving cron from deny to allow in the sandbox default policy) is logically sound and matches the documented behavior of the cron tool (gateway-routed, not container-executed). Tests cover the primary and regression cases. The only finding is a minor test duplication (P2 style), which does not affect correctness or production safety.
  • No files require special attention.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/tool-policy.test.ts
Line: 229-247

Comment:
**Duplicate assertions across the two new tests**

The second test (`#50303`) re-asserts `isToolAllowed(policy, "cron") === true` and rebuilds the same `resolved`/`policy` from `resolveSandboxToolPolicyForAgent(undefined, undefined)` that the first test already covers. The only net-new assertion is the `gateway` check. Consider either merging the two tests or removing the duplicate `cron` assertion from the second test to keep the suite DRY.

```suggestion
  it("default sandbox policy allows cron (gateway-routed, not containerized)", () => {
    const resolved = resolveSandboxToolPolicyForAgent(undefined, undefined);
    const policy: SandboxToolPolicy = { allow: resolved.allow, deny: resolved.deny };
    expect(isToolAllowed(policy, "cron")).toBe(true);
    expect(DEFAULT_TOOL_ALLOW).toContain("cron");
    expect(DEFAULT_TOOL_DENY).not.toContain("cron");
    // browser requires container execution — should remain denied
    expect(isToolAllowed(policy, "browser")).toBe(false);
    // gateway exposes admin actions — should remain denied
    expect(isToolAllowed(policy, "gateway")).toBe(false);
  });
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(sandbox): allow cron tool in sandbox..." | Re-trigger Greptile

@lupuletic lupuletic force-pushed the fix/sandbox-cron-allow-50303 branch from edbf832 to 05218ee Compare March 24, 2026 10:26
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 05218eecb6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@fabianwilliams fabianwilliams self-assigned this Mar 24, 2026
Copy link
Copy Markdown
Contributor

@fabianwilliams fabianwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code change is clean and the UX fix makes sense — sandboxed agents hallucinating fake cron calls is clearly wrong. CI is green, tests cover the policy change, and the ownerOnly guard stays intact.

That said, I want to flag the security tradeoff that @aisle-research-bot raised, because I think it deserves a core maintainer opinion before merging.

The tension: Sandbox mode is defense-in-depth. Even though ownerOnly protects cron at the authorization layer, having it denied at the sandbox layer was a second barrier. Removing it means a sandboxed agent with owner context can now:

  • Create persistent scheduled jobs that outlive the session (cron.add)
  • Send webhook deliveries to arbitrary URLs (delivery.mode="webhook")

The author's reasoning is sound — cron is gateway-routed, not containerized, so the sandbox deny list is the wrong enforcement point. But the security bot's suggestion of allowing read-only cron operations (cron.list, cron.status) while keeping mutations denied in sandbox could be a nice middle ground.

@BradGroux — would appreciate your take on whether the ownerOnly guard is sufficient here or if sandbox should maintain its own deny for cron mutations as defense-in-depth.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0baf5c973c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"edit",
"apply_patch",
"image",
"cron",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep cron blocked in non-main sandbox sessions

Adding cron to the default sandbox allowlist lets a sandboxed non-main chat schedule an agentTurn job with sessionTarget:"session:main", which then runs outside the sandbox boundary. The cron runner forwards custom session targets directly into isolated runs (src/gateway/server-cron.ts:288-302), and sandbox mode non-main explicitly skips sandboxing for the main session key (src/agents/sandbox/runtime-status.ts:15-23), so this commit introduces a containment bypass whenever sandbox.mode is non-main.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@BradGroux BradGroux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(sandbox) allow cron tool in sandbox mode (gateway-routed, not containerized)

Commits reviewed: both commits on branch


Context

This PR moves cron from DEFAULT_TOOL_DENY to DEFAULT_TOOL_ALLOW in sandbox constants, with the rationale that cron is gateway-routed (WebSocket RPC to the host) and never executes inside the sandbox container. The Aisle security bot flagged this as CWE-269 (Improper Privilege Management), and @fabianwilliams raised the defense-in-depth question in comments.


Security analysis

There are two reasonable perspectives on this change, and they diverge in a way worth discussing explicitly.

The case for allowing cron:

  • ownerOnly is the correct enforcement layer for cron. It validates that the requesting user is the owner before any mutation.
  • Cron never touches the sandbox container. It is a WebSocket RPC call to the gateway, which runs on the host. The sandbox deny-list was never a real containment boundary for cron; it was just preventing the tool from being called at all.
  • Denying cron in sandbox causes agents to hallucinate fake cron calls or work around the restriction in unpredictable ways, which is arguably worse than letting the tool work as designed.
  • Other gateway-routed tools (sessions_*) are already allowed in sandbox without similar concern.

The case for keeping cron denied:

  • A sandboxed agent running with owner context can invoke cron.add with payload.kind="agentTurn", which triggers runCronIsolatedAgentTurn on the HOST runtime. This is a sandbox-to-host execution path.
  • ownerOnly protects against non-owner users but does not constrain a compromised or hallucinating agent that is already running as the owner. The deny-list provided a second barrier.
  • Cron is uniquely high-impact among gateway-routed tools due to persistence (jobs survive session end), scheduling (delayed execution), and webhook delivery capabilities.

My assessment:
The ownerOnly guard is the right enforcement layer. The sandbox deny-list was defense-in-depth, but cron is fundamentally a host-side tool that happens to be callable from sandbox sessions. Blocking it does not improve security in a meaningful way when the agent already has owner credentials. The UX degradation from blocking (hallucinated workarounds, confusion) is a real cost.

That said, the persistence concern is valid. A compromised agent creating persistent cron jobs is a different threat profile than a one-shot tool call that ends with the session.


Suggestions

1. Consider read-only vs mutation split (Medium)
If there is appetite for a middle ground: allow cron.status, cron.list, and cron.runs in sandbox while blocking cron.add, cron.update, cron.remove, and cron.run. This gives agents visibility into scheduled work without granting mutation capability. However, this requires tool-level action filtering that may not exist in the current sandbox infrastructure, making it a larger change than this PR intends.

2. Prompt note is good (Low)
The added prompt note clarifying that cron is gateway-routed and subject to ownerOnly checks is a useful addition. Agents benefit from understanding why the tool works the way it does.


Test coverage

  • ✅ Tests verify cron is in DEFAULT_TOOL_ALLOW and not in DEFAULT_TOOL_DENY
  • ⚠️ No integration test for sandbox session invoking cron.add with ownership validation
  • ⚠️ No test for mutation-vs-read-only behavior distinction

Summary

This is a reasonable change. The cron tool is gateway-routed and was never actually contained by the sandbox. The ownerOnly guard is the real security boundary. The PR is small, clean, and the test updates match the intent. The defense-in-depth concern is worth noting but does not rise to blocker level given how cron routing actually works.

FYI - @fabianwilliams

@fabianwilliams
Copy link
Copy Markdown
Contributor

@BradGroux Thanks for the thorough analysis — this is exactly the kind of assessment I was hoping for when I tagged you.

I agree with your conclusion: ownerOnly is the correct enforcement layer, and the sandbox deny-list was never a real containment boundary for a gateway-routed tool. The UX cost of blocking (hallucinated cron calls) outweighs the theoretical defense-in-depth benefit.

Your read-only vs mutation split suggestion is interesting — cron.list and cron.status in sandbox with mutations blocked would be a clean middle ground. That feels like a good follow-up PR rather than scope creep here.

No objections from me on merging as-is. The persistence concern is noted and worth revisiting if sandbox threat modeling evolves.

@lupuletic
Copy link
Copy Markdown
Contributor Author

The sandbox escape concern via sessionTarget has been evaluated by maintainers (see fabianwilliams and BradGroux discussion). The ownerOnly tool-level gate is the enforcement boundary, not the sandbox deny-list. Cron is gateway-routed (WebSocket RPC), never containerized, so the deny-list was incorrectly blocking it.

@lupuletic lupuletic force-pushed the fix/sandbox-cron-allow-50303 branch from 0baf5c9 to 837ee7f Compare March 29, 2026 14:12
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 837ee7f96e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@lupuletic lupuletic force-pushed the fix/sandbox-cron-allow-50303 branch from 837ee7f to edf7507 Compare March 30, 2026 06:14
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: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: cron jobs can be created and delivered by CLI, but do not materialize from Telegram/WebChat conversation; exec is also unreliable from chat

3 participants