Skip to content

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

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

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 scanned this PR and did not find any security vulnerabilities.

Aisle supplements but does not replace security review.


Analyzed PR: #53565 at commit 6c168f5

Last updated on: 2026-04-25T20:51:21Z

@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

Comment thread src/agents/tool-policy.test.ts
@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".

Comment thread src/agents/sandbox/constants.ts
@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".

@@ -18,6 +18,7 @@ export const DEFAULT_TOOL_ALLOW = [
"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
Member

@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".

Comment thread src/agents/sandbox/constants.ts
@lupuletic lupuletic force-pushed the fix/sandbox-cron-allow-50303 branch 2 times, most recently from edf7507 to 4105804 Compare April 5, 2026 13:53
@fabianwilliams fabianwilliams force-pushed the fix/sandbox-cron-allow-50303 branch from 4105804 to 6c168f5 Compare April 25, 2026 20:48
@fabianwilliams
Copy link
Copy Markdown
Contributor

Rebased onto current upstream/main. Resolved one conflict in src/agents/system-prompt.ts:

  • Upstream removed the browserNoVncUrl field entirely from sandboxInfo in the gap. Kept only this PR's actual contribution (the cron availability message), dropped the now-dead noVNC line so the rebased commit doesn't reference a removed field.

Verified availableTools is in scope at the use site (declared earlier in buildAgentSystemPrompt).

Force-pushed (with-lease) to your branch — maintainerCanModify was on. Watching CI; will squash-merge when green.

Per @pSteiny's directive: fix-and-merge velocity.

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.

Author lupuletic confirmed merge-as-is after security thread resolution. Rebased onto current main, dropped reference to removed browserNoVncUrl field. Approving + queuing auto-merge per @pSteiny's fix-and-merge directive.

@fabianwilliams
Copy link
Copy Markdown
Contributor

Status update: rebased onto current upstream/main, all conflicts resolved, PR-specific checks green (33+ ✓). Approved.

However, blocked by 3 pre-existing main breakages unrelated to this PR's diff:

  • checks-node-agentic-plugin-sdk — failure in src/plugin-sdk/facade-loader.test.ts > plugin-sdk facade loader > uses native Jiti import for Windows dist facade loads
    • expected { interopDefault: true, …(3) } to deeply equal ObjectContaining {"tryNative": true}
    • Looks like the test expects tryNative: true in the createJiti call but the implementation now passes interopDefault: true (likely from 9e9aa4722a fix(plugins): load mirrored runtime deps through ESM-safe aliases — test/impl drift)
  • checks-node-agentic-plugins — same root cause likely
  • checks-node-channels — separate, haven't dug in

This PR doesn't touch src/plugin-sdk/ (verified via git diff upstream/main..HEAD -- src/plugin-sdk/ — empty). Recent main CI runs show a pattern of cancellations, suggesting main isn't consistently green right now.

Pinging @pSteiny / plugin-sdk owners to take a look at the facade-loader test/impl drift. Once main goes green, this PR will rebase cleanly and can merge. Holding here rather than admin-merging since the failures are real (just not ours).

@vincentkoc vincentkoc added the stale Marked as stale due to inactivity label Apr 26, 2026
@vincentkoc
Copy link
Copy Markdown
Member

This assigned pull request has been marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 27, 2026
@openclaw-barnacle
Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 27, 2026
@barnacle-openclaw
Copy link
Copy Markdown

Closing due to inactivity.
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
That channel is the escape hatch for high-quality PRs that get auto-closed.

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 stale Marked as stale due to inactivity

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

4 participants