refactor: clarify sandbox cron elevated gating terminology
This commit is contained in:
parent
fed475f9ac
commit
548b44b40d
@ -134,6 +134,7 @@ Delivery notes:
|
|||||||
- If `to` is set, cron auto-delivers the agent’s final output even if `deliver` is omitted.
|
- If `to` is set, cron auto-delivers the agent’s 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.
|
||||||
|
|||||||
@ -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-",
|
||||||
|
|||||||
@ -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`.
|
||||||
|
|||||||
@ -145,6 +145,24 @@ globally or per-agent, sandboxing doesn’t 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.
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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).
|
||||||
|
|||||||
@ -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",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 ??
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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";
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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")])
|
||||||
|
|||||||
@ -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")])
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user