refactor: clarify sandbox cron elevated gating terminology

This commit is contained in:
Christof Salis 2026-01-27 19:31:55 +01:00
parent fed475f9ac
commit 548b44b40d
16 changed files with 129 additions and 49 deletions

View File

@ -134,6 +134,7 @@ Delivery notes:
- If `to` is set, cron auto-delivers the agents final output even if `deliver` is omitted. - If `to` is set, cron auto-delivers the agents final output even if `deliver` is omitted.
- Use `deliver: true` when you want last-route delivery without an explicit `to`. - Use `deliver: true` when you want last-route delivery without an explicit `to`.
- Use `deliver: false` to keep output internal even if a `to` is present. - Use `deliver: false` to keep output internal even if a `to` is present.
- Sandboxed sessions may restrict delivery targets via `agents.defaults.sandbox.cron.delivery` (see [Sandboxing](/gateway/sandboxing) and [Configuration](/gateway/configuration#agentsdefaultssandbox)).
Target format reminders: Target format reminders:
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<id>`, `user:<id>`) to avoid ambiguity. - Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<id>`, `user:<id>`) to avoid ambiguity.

View File

@ -2175,6 +2175,15 @@ Defaults (if enabled):
- optional sandboxed browser (Chromium + CDP, noVNC observer) - optional sandboxed browser (Chromium + CDP, noVNC observer)
- hardening knobs: `network`, `user`, `pidsLimit`, `memory`, `cpus`, `ulimits`, `seccompProfile`, `apparmorProfile` - hardening knobs: `network`, `user`, `pidsLimit`, `memory`, `cpus`, `ulimits`, `seccompProfile`, `apparmorProfile`
Cron policy (only applies when a session is sandboxed and the `cron` tool is allowed):
- `cron.visibility`: `"agent"` (default) scopes cron jobs to the current agent; `"all"` allows cross-agent cron visibility.
- `cron.elevated`: `"off"` (default) never bypasses sandbox cron restrictions; `"on"` bypasses when session elevated is on/ask/full; `"full"` bypasses only when elevated is full.
- `cron.allowMainSessionJobs`: `false` (default) blocks main-session cron jobs and wake events from sandboxed sessions.
- `cron.delivery`: `"last-only"` (default) allows last-route delivery only (no explicit `to`; if `channel` is set it must be `last`); `"off"` disables delivery; `"explicit"` allows explicit delivery targets.
Notes:
- This does not enable the `cron` tool inside the sandbox. Cron is denied by default in sandbox tool policy; allow it via `tools.sandbox.tools` (or per-agent `agents.list[].tools.sandbox.tools`).
Warning: `scope: "shared"` means a shared container and shared workspace. No Warning: `scope: "shared"` means a shared container and shared workspace. No
cross-session isolation. Use `scope: "session"` for per-session isolation. cross-session isolation. Use `scope: "session"` for per-session isolation.
@ -2193,6 +2202,12 @@ For package installs, ensure network egress, a writable root FS, and a root user
scope: "agent", // session | agent | shared (agent is default) scope: "agent", // session | agent | shared (agent is default)
workspaceAccess: "none", // none | ro | rw workspaceAccess: "none", // none | ro | rw
workspaceRoot: "~/.clawdbot/sandboxes", workspaceRoot: "~/.clawdbot/sandboxes",
cron: {
visibility: "agent", // agent | all
elevated: "off", // off | on | full
allowMainSessionJobs: false,
delivery: "last-only" // off | last-only | explicit
},
docker: { docker: {
image: "moltbot-sandbox:bookworm-slim", image: "moltbot-sandbox:bookworm-slim",
containerPrefix: "moltbot-sbx-", containerPrefix: "moltbot-sbx-",

View File

@ -90,6 +90,20 @@ Available groups:
- `group:nodes`: `nodes` - `group:nodes`: `nodes`
- `group:moltbot`: all built-in Moltbot tools (excludes provider plugins) - `group:moltbot`: all built-in Moltbot tools (excludes provider plugins)
### Cron policy inside the sandbox
If the `cron` tool is allowed, sandboxed sessions can still be restricted by
`agents.defaults.sandbox.cron` (and `agents.list[].sandbox.cron`):
- Visibility: `agents.defaults.sandbox.cron.visibility` can scope cron jobs to the current agent.
- Main session gating: `agents.defaults.sandbox.cron.allowMainSessionJobs: false` blocks main-session cron jobs and wake events from sandboxed sessions.
- Delivery gating: `agents.defaults.sandbox.cron.delivery` can disable delivery or restrict it to last-route delivery.
- Elevated gate: `agents.defaults.sandbox.cron.elevated` can use the session elevated level as an escape signal for cron restrictions.
Notes:
- Elevated remains exec-only (it does not grant extra tools), but cron policy can consult the session elevated level.
- Fix-it keys live under `agents.defaults.sandbox.cron.*` / `agents.list[].sandbox.cron.*` (see [Configuration](/gateway/configuration#agentsdefaultssandbox)).
- This is in addition to tool policy; if `cron` is denied by sandbox tool policy, allow it via `tools.sandbox.tools` (or per-agent `agents.list[].tools.sandbox.tools`).
## Elevated: exec-only “run on host” ## Elevated: exec-only “run on host”
Elevated does **not** grant extra tools; it only affects `exec`. Elevated does **not** grant extra tools; it only affects `exec`.

View File

@ -145,6 +145,24 @@ globally or per-agent, sandboxing doesnt bring it back.
`/exec` directives only apply for authorized senders and persist per session; to hard-disable `/exec` directives only apply for authorized senders and persist per session; to hard-disable
`exec`, use tool policy deny (see [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)). `exec`, use tool policy deny (see [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)).
## Cron policy (sandboxed sessions)
If the `cron` tool is allowed in a sandboxed session, Clawdbot can still apply
additional restrictions to cron access and delivery via:
- `agents.defaults.sandbox.cron`
- `agents.list[].sandbox.cron` (per-agent override)
This is useful when you want sandboxed agents to schedule reminders but prevent:
- cross-agent cron visibility (`visibility: "agent"`)
- main-session jobs and wake events (`allowMainSessionJobs: false`)
- explicit delivery targets (`delivery: "last-only"` or `"off"`)
Note: this does not enable cron in the sandbox. If `cron` is denied by sandbox tool policy,
allow it via `tools.sandbox.tools` (or per-agent `agents.list[].tools.sandbox.tools`).
See [Configuration](/gateway/configuration#agentsdefaultssandbox) for the exact keys and defaults.
If you see “blocked” errors, also check [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated).
Debugging: Debugging:
- Use `moltbot sandbox explain` to inspect effective sandbox mode, tool policy, and fix-it config keys. - Use `moltbot sandbox explain` to inspect effective sandbox mode, tool policy, and fix-it config keys.
- See [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) for the “why is this blocked?” mental model. - See [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) for the “why is this blocked?” mental model.

View File

@ -178,6 +178,43 @@ For debugging “why is this blocked?”, see [Sandbox vs Tool Policy vs Elevate
--- ---
### Example 4: Sandbox Cron Policy Per Agent
This is useful when you want sandboxed agents to schedule cron jobs, but keep them
scoped (and limit delivery):
```json
{
"agents": {
"defaults": {
"sandbox": {
"mode": "all",
"cron": {
"visibility": "agent",
"elevated": "off",
"allowMainSessionJobs": false,
"delivery": "last-only"
}
}
},
"list": [
{
"id": "admin",
"sandbox": {
"cron": {
"visibility": "all",
"elevated": "full",
"delivery": "explicit"
}
}
}
]
}
}
```
---
## Configuration Precedence ## Configuration Precedence
When both global (`agents.defaults.*`) and agent-specific (`agents.list[].*`) configs exist: When both global (`agents.defaults.*`) and agent-specific (`agents.list[].*`) configs exist:
@ -189,12 +226,14 @@ agents.list[].sandbox.mode > agents.defaults.sandbox.mode
agents.list[].sandbox.scope > agents.defaults.sandbox.scope agents.list[].sandbox.scope > agents.defaults.sandbox.scope
agents.list[].sandbox.workspaceRoot > agents.defaults.sandbox.workspaceRoot agents.list[].sandbox.workspaceRoot > agents.defaults.sandbox.workspaceRoot
agents.list[].sandbox.workspaceAccess > agents.defaults.sandbox.workspaceAccess agents.list[].sandbox.workspaceAccess > agents.defaults.sandbox.workspaceAccess
agents.list[].sandbox.cron.* > agents.defaults.sandbox.cron.*
agents.list[].sandbox.docker.* > agents.defaults.sandbox.docker.* agents.list[].sandbox.docker.* > agents.defaults.sandbox.docker.*
agents.list[].sandbox.browser.* > agents.defaults.sandbox.browser.* agents.list[].sandbox.browser.* > agents.defaults.sandbox.browser.*
agents.list[].sandbox.prune.* > agents.defaults.sandbox.prune.* agents.list[].sandbox.prune.* > agents.defaults.sandbox.prune.*
``` ```
**Notes:** **Notes:**
- `agents.list[].sandbox.cron.*` overrides `agents.defaults.sandbox.cron.*` for that agent.
- `agents.list[].sandbox.{docker,browser,prune}.*` overrides `agents.defaults.sandbox.{docker,browser,prune}.*` for that agent (ignored when sandbox scope resolves to `"shared"`). - `agents.list[].sandbox.{docker,browser,prune}.*` overrides `agents.defaults.sandbox.{docker,browser,prune}.*` for that agent (ignored when sandbox scope resolves to `"shared"`).
### Tool Restrictions ### Tool Restrictions

View File

@ -361,6 +361,7 @@ Core actions:
Notes: Notes:
- `add` expects a full cron job object (same schema as `cron.add` RPC). - `add` expects a full cron job object (same schema as `cron.add` RPC).
- `update` uses `{ id, patch }`. - `update` uses `{ id, patch }`.
- In sandboxed sessions, cron access and delivery may be further restricted by `agents.defaults.sandbox.cron` (and per-agent overrides via `agents.list[].sandbox.cron`).
### `gateway` ### `gateway`
Restart or apply updates to the running Gateway process (in-place). Restart or apply updates to the running Gateway process (in-place).

View File

@ -131,19 +131,19 @@ describe("sandbox config merges", () => {
const resolved = resolveSandboxCronConfig({ const resolved = resolveSandboxCronConfig({
globalCron: { globalCron: {
visibility: "all", visibility: "all",
escape: "elevated", elevated: "on",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "explicit", delivery: "explicit",
}, },
agentCron: { agentCron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
}, },
}); });
expect(resolved).toEqual({ expect(resolved).toEqual({
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "explicit", delivery: "explicit",
}); });

View File

@ -132,7 +132,7 @@ export function resolveSandboxCronConfig(params: {
return { return {
visibility: visibility:
agentCron?.visibility ?? globalCron?.visibility ?? DEFAULT_SANDBOX_CRON_POLICY.visibility, agentCron?.visibility ?? globalCron?.visibility ?? DEFAULT_SANDBOX_CRON_POLICY.visibility,
escape: agentCron?.escape ?? globalCron?.escape ?? DEFAULT_SANDBOX_CRON_POLICY.escape, elevated: agentCron?.elevated ?? globalCron?.elevated ?? DEFAULT_SANDBOX_CRON_POLICY.elevated,
allowMainSessionJobs: allowMainSessionJobs:
agentCron?.allowMainSessionJobs ?? agentCron?.allowMainSessionJobs ??
globalCron?.allowMainSessionJobs ?? globalCron?.allowMainSessionJobs ??

View File

@ -39,7 +39,7 @@ export const DEFAULT_TOOL_DENY = [
export const DEFAULT_SANDBOX_CRON_POLICY = { export const DEFAULT_SANDBOX_CRON_POLICY = {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: false, allowMainSessionJobs: false,
delivery: "last-only", delivery: "last-only",
} as const; } as const;

View File

@ -50,7 +50,7 @@ export type SandboxScope = "session" | "agent" | "shared";
export type SandboxCronPolicy = { export type SandboxCronPolicy = {
visibility: "agent" | "all"; visibility: "agent" | "all";
escape: "off" | "elevated" | "elevated-full"; elevated: "off" | "on" | "full";
allowMainSessionJobs: boolean; allowMainSessionJobs: boolean;
delivery: "off" | "last-only" | "explicit"; delivery: "off" | "last-only" | "explicit";
}; };

View File

@ -5,7 +5,7 @@ const resolveSandboxRuntimeStatusMock = vi.fn(() => ({ sandboxed: false, agentId
const resolveSandboxConfigForAgentMock = vi.fn(() => ({ const resolveSandboxConfigForAgentMock = vi.fn(() => ({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: false, allowMainSessionJobs: false,
delivery: "last-only", delivery: "last-only",
}, },
@ -43,7 +43,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: false, allowMainSessionJobs: false,
delivery: "last-only", delivery: "last-only",
}, },
@ -285,7 +285,7 @@ describe("cron tool", () => {
expect(call.params).toEqual({ includeDisabled: false, actorAgentId: "agent-123" }); expect(call.params).toEqual({ includeDisabled: false, actorAgentId: "agent-123" });
}); });
it("skips actor scoping when sandbox escape is elevated", async () => { it("skips actor scoping when sandbox cron elevated gate is on", async () => {
resolveSandboxRuntimeStatusMock.mockReturnValue({ resolveSandboxRuntimeStatusMock.mockReturnValue({
sandboxed: true, sandboxed: true,
agentId: "agent-123", agentId: "agent-123",
@ -293,7 +293,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "elevated", elevated: "on",
allowMainSessionJobs: false, allowMainSessionJobs: false,
delivery: "last-only", delivery: "last-only",
}, },
@ -311,7 +311,7 @@ describe("cron tool", () => {
expect(call.params).toEqual({ includeDisabled: false }); expect(call.params).toEqual({ includeDisabled: false });
}); });
it("keeps actor scoping when escape is elevated-full but elevated is on", async () => { it("keeps actor scoping when sandbox cron elevated gate is full but elevated is on", async () => {
resolveSandboxRuntimeStatusMock.mockReturnValue({ resolveSandboxRuntimeStatusMock.mockReturnValue({
sandboxed: true, sandboxed: true,
agentId: "agent-123", agentId: "agent-123",
@ -319,7 +319,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "elevated-full", elevated: "full",
allowMainSessionJobs: false, allowMainSessionJobs: false,
delivery: "last-only", delivery: "last-only",
}, },
@ -337,7 +337,7 @@ describe("cron tool", () => {
expect(call.params).toEqual({ includeDisabled: false, actorAgentId: "agent-123" }); expect(call.params).toEqual({ includeDisabled: false, actorAgentId: "agent-123" });
}); });
it("skips actor scoping when escape is elevated-full and elevated is full", async () => { it("skips actor scoping when sandbox cron elevated gate is full and elevated is full", async () => {
resolveSandboxRuntimeStatusMock.mockReturnValue({ resolveSandboxRuntimeStatusMock.mockReturnValue({
sandboxed: true, sandboxed: true,
agentId: "agent-123", agentId: "agent-123",
@ -345,7 +345,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "elevated-full", elevated: "full",
allowMainSessionJobs: false, allowMainSessionJobs: false,
delivery: "last-only", delivery: "last-only",
}, },
@ -392,7 +392,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "last-only", delivery: "last-only",
}, },
@ -424,7 +424,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "last-only", delivery: "last-only",
}, },
@ -529,7 +529,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "all", visibility: "all",
escape: "off", elevated: "off",
allowMainSessionJobs: false, allowMainSessionJobs: false,
delivery: "last-only", delivery: "last-only",
}, },
@ -586,7 +586,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "off", delivery: "off",
}, },
@ -615,7 +615,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "explicit", delivery: "explicit",
}, },
@ -653,7 +653,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "last-only", delivery: "last-only",
}, },
@ -690,7 +690,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "off", delivery: "off",
}, },
@ -730,7 +730,7 @@ describe("cron tool", () => {
resolveSandboxConfigForAgentMock.mockReturnValue({ resolveSandboxConfigForAgentMock.mockReturnValue({
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "last-only", delivery: "last-only",
}, },

View File

@ -55,7 +55,7 @@ type CronSandboxAccess = {
sessionKey?: string; sessionKey?: string;
policy: { policy: {
visibility: "agent" | "all"; visibility: "agent" | "all";
escape: "off" | "elevated" | "elevated-full"; elevated: "off" | "on" | "full";
allowMainSessionJobs: boolean; allowMainSessionJobs: boolean;
delivery: "off" | "last-only" | "explicit"; delivery: "off" | "last-only" | "explicit";
}; };
@ -117,7 +117,7 @@ function resolveCronSandboxAccess(params: {
sessionKey: undefined, sessionKey: undefined,
policy: { policy: {
visibility: "all", visibility: "all",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "explicit", delivery: "explicit",
}, },
@ -138,16 +138,12 @@ function resolveCronSandboxAccess(params: {
const elevatedOn = elevatedLevel !== "off"; const elevatedOn = elevatedLevel !== "off";
const elevatedFull = elevatedLevel === "full"; const elevatedFull = elevatedLevel === "full";
const escapeAllowed = const elevatedAllowed =
policy.escape === "elevated" policy.elevated === "on" ? elevatedOn : policy.elevated === "full" ? elevatedFull : false;
? elevatedOn
: policy.escape === "elevated-full"
? elevatedFull
: false;
return { return {
sandboxed: runtime.sandboxed, sandboxed: runtime.sandboxed,
restricted: runtime.sandboxed && !escapeAllowed, restricted: runtime.sandboxed && !elevatedAllowed,
agentId: normalizedAgentId, agentId: normalizedAgentId,
sessionKey: rawSessionKey, sessionKey: rawSessionKey,
policy, policy,

View File

@ -10,7 +10,7 @@ describe("sandbox cron config", () => {
sandbox: { sandbox: {
cron: { cron: {
visibility: "agent", visibility: "agent",
escape: "elevated", elevated: "on",
allowMainSessionJobs: false, allowMainSessionJobs: false,
delivery: "last-only", delivery: "last-only",
}, },
@ -22,7 +22,7 @@ describe("sandbox cron config", () => {
sandbox: { sandbox: {
cron: { cron: {
visibility: "all", visibility: "all",
escape: "off", elevated: "off",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "explicit", delivery: "explicit",
}, },
@ -48,7 +48,7 @@ describe("sandbox cron config", () => {
sandbox: { sandbox: {
cron: { cron: {
visibility: "everyone", visibility: "everyone",
escape: "nope", elevated: "nope",
delivery: "sometimes", delivery: "sometimes",
}, },
}, },
@ -58,7 +58,7 @@ describe("sandbox cron config", () => {
expect(res.ok).toBe(false); expect(res.ok).toBe(false);
}); });
it("accepts elevated-full escape and allowMainSessionJobs", async () => { it("accepts full elevated gate and allowMainSessionJobs", async () => {
vi.resetModules(); vi.resetModules();
const { validateConfigObject } = await import("./config.js"); const { validateConfigObject } = await import("./config.js");
const res = validateConfigObject({ const res = validateConfigObject({
@ -67,7 +67,7 @@ describe("sandbox cron config", () => {
sandbox: { sandbox: {
cron: { cron: {
visibility: "all", visibility: "all",
escape: "elevated-full", elevated: "full",
allowMainSessionJobs: true, allowMainSessionJobs: true,
delivery: "explicit", delivery: "explicit",
}, },
@ -78,7 +78,7 @@ describe("sandbox cron config", () => {
expect(res.ok).toBe(true); expect(res.ok).toBe(true);
if (res.ok) { if (res.ok) {
expect(res.config.agents?.defaults?.sandbox?.cron?.escape).toBe("elevated-full"); expect(res.config.agents?.defaults?.sandbox?.cron?.elevated).toBe("full");
expect(res.config.agents?.defaults?.sandbox?.cron?.allowMainSessionJobs).toBe(true); expect(res.config.agents?.defaults?.sandbox?.cron?.allowMainSessionJobs).toBe(true);
} }
}); });

View File

@ -75,7 +75,7 @@ export type SandboxPruneSettings = {
}; };
export type SandboxCronVisibility = "agent" | "all"; export type SandboxCronVisibility = "agent" | "all";
export type SandboxCronEscape = "off" | "elevated" | "elevated-full"; export type SandboxCronElevated = "off" | "on" | "full";
export type SandboxCronDelivery = "off" | "last-only" | "explicit"; export type SandboxCronDelivery = "off" | "last-only" | "explicit";
export type SandboxCronSettings = { export type SandboxCronSettings = {
@ -86,12 +86,12 @@ export type SandboxCronSettings = {
*/ */
visibility?: SandboxCronVisibility; visibility?: SandboxCronVisibility;
/** /**
* Escape hatch for sandboxed cron access. * Elevated gate for sandboxed cron restrictions.
* - "off": never escape * - "off": never bypass sandbox cron restrictions
* - "elevated": allow escape when session elevated != off * - "on": bypass when the session elevated level is on/ask/full
* - "elevated-full": allow escape only when elevated=full * - "full": bypass only when the session elevated level is full
*/ */
escape?: SandboxCronEscape; elevated?: SandboxCronElevated;
/** /**
* Allow main-session cron jobs from sandboxed sessions. * Allow main-session cron jobs from sandboxed sessions.
* Default: false. * Default: false.

View File

@ -161,9 +161,7 @@ export const AgentDefaultsSchema = z
cron: z cron: z
.object({ .object({
visibility: z.union([z.literal("agent"), z.literal("all")]).optional(), visibility: z.union([z.literal("agent"), z.literal("all")]).optional(),
escape: z elevated: z.union([z.literal("off"), z.literal("on"), z.literal("full")]).optional(),
.union([z.literal("off"), z.literal("elevated"), z.literal("elevated-full")])
.optional(),
allowMainSessionJobs: z.boolean().optional(), allowMainSessionJobs: z.boolean().optional(),
delivery: z delivery: z
.union([z.literal("off"), z.literal("last-only"), z.literal("explicit")]) .union([z.literal("off"), z.literal("last-only"), z.literal("explicit")])

View File

@ -237,9 +237,7 @@ export const AgentSandboxSchema = z
cron: z cron: z
.object({ .object({
visibility: z.union([z.literal("agent"), z.literal("all")]).optional(), visibility: z.union([z.literal("agent"), z.literal("all")]).optional(),
escape: z elevated: z.union([z.literal("off"), z.literal("on"), z.literal("full")]).optional(),
.union([z.literal("off"), z.literal("elevated"), z.literal("elevated-full")])
.optional(),
allowMainSessionJobs: z.boolean().optional(), allowMainSessionJobs: z.boolean().optional(),
delivery: z delivery: z
.union([z.literal("off"), z.literal("last-only"), z.literal("explicit")]) .union([z.literal("off"), z.literal("last-only"), z.literal("explicit")])