exec runs under SES lockdown with no Date, Math.random or setTimeout, and nothing says so #17

Closed
opened 2026-08-28 17:09:19 +09:00 by floot · 2 comments
Owner

The mcp__endo__exec compartment is hardened, but the tool description does
not mention it. Agents discover the restrictions by hitting them:

Error: secure mode Calling %SharedDate%.now() throws
Error: setTimeout is not a function

The setTimeout gap has a real cost beyond one wasted call: an agent watching
a workflow reach await-approval cannot sleep-and-poll inside a single exec.
In session mt82q1ns-l8nrge that turned every status check into a separate
turn, and the agent wrote loops that spun uselessly because it had no way to
wait between iterations.

Wanted:

  1. Document the compartment rules in the exec tool description — no Date.now,
    no Math.random, no timers, JSON-serialized return values.
  2. Provide a safe sleep(ms) (or a waitFor/follow helper bounded by the
    tool timeout) so polling a run does not need a round trip per sample.
The `mcp__endo__exec` compartment is hardened, but the tool description does not mention it. Agents discover the restrictions by hitting them: ``` Error: secure mode Calling %SharedDate%.now() throws Error: setTimeout is not a function ``` The `setTimeout` gap has a real cost beyond one wasted call: an agent watching a workflow reach `await-approval` cannot sleep-and-poll inside a single exec. In session `mt82q1ns-l8nrge` that turned every status check into a separate turn, and the agent wrote loops that spun uselessly because it had no way to wait between iterations. **Wanted:** 1. Document the compartment rules in the exec tool description — no `Date.now`, no `Math.random`, no timers, JSON-serialized return values. 2. Provide a safe `sleep(ms)` (or a `waitFor`/`follow` helper bounded by the tool timeout) so polling a run does not need a round trip per sample.
Author
Owner

Half done in 046bfeb, leaving open for the rest.

The machine-admin prompt now states the compartment rules: no Date.now(), no Math.random(), no timers, and that you cannot sleep-and-poll inside one exec, so you checkpoint with status() and look again on a later turn.

Neither of this issue's asks is fully delivered: the rules are in one preset's prompt, not in the exec tool description where every agent would see them, and there is still no way to wait inside a single exec. The second one bites on exactly the case described here - watching a workflow reach await-approval. My workaround this session was to shell out to node -e with a setTimeout, which only works because a child process gets timers the compartment does not.

Half done in 046bfeb, leaving open for the rest. The machine-admin prompt now states the compartment rules: no `Date.now()`, no `Math.random()`, no timers, and that you cannot sleep-and-poll inside one exec, so you checkpoint with `status()` and look again on a later turn. Neither of this issue's asks is fully delivered: the rules are in one preset's prompt, not in the `exec` tool description where every agent would see them, and there is still no way to wait inside a single exec. The second one bites on exactly the case described here - watching a workflow reach `await-approval`. My workaround this session was to shell out to `node -e` with a `setTimeout`, which only works because a child process gets timers the compartment does not.
Author
Owner

Fixed in d5772b0, live at generation 46.

The exec tool description now states the compartment rules where every caller reads them: no Date.now() or new Date(), no Math.random(), no setTimeout/setInterval, and that results are JSON-serialized with BigInts rendered as decimal strings.

The second ask - a way to wait - is sleep(ms), endowed alongside powers, E, harden and console. It grants delay and nothing else, to code that already holds the guest's full authority. Verified live: await sleep(1500) inside a single exec now returns normally, where the same call previously threw setTimeout is not a function. That removes the specific cost named here - watching a workflow reach await-approval no longer burns a turn per poll.

Fixed in d5772b0, live at generation 46. The `exec` tool description now states the compartment rules where every caller reads them: no `Date.now()` or `new Date()`, no `Math.random()`, no `setTimeout`/`setInterval`, and that results are JSON-serialized with BigInts rendered as decimal strings. The second ask - a way to wait - is `sleep(ms)`, endowed alongside `powers`, `E`, `harden` and `console`. It grants delay and nothing else, to code that already holds the guest's full authority. Verified live: `await sleep(1500)` inside a single exec now returns normally, where the same call previously threw `setTimeout is not a function`. That removes the specific cost named here - watching a workflow reach `await-approval` no longer burns a turn per poll.
floot closed this issue 2026-09-01 14:25:01 +09:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
floot/endo#17
No description provided.