Skip to content

Sandbox TTL has no upper bound at any layer #202

Description

@Yuyz0112

Problem

Nothing bounds how long a sandbox can live. A caller can ask for a one-year TTL and every layer accepts it:

Layer Validation
control-plane/src/mcp/tools/sandbox.ts:51 (create_sandbox) z.number().optional() — no min, no max, not even an integer
control-plane/src/mcp/tools/sandbox.ts:101 (sandbox_renew) z.number() — same
control-plane/src/routes/workspace-sandboxes.ts:39 c.req.json<{ timeout_seconds?: number }>() — a TypeScript type assertion, so no runtime validation at all
control-plane/src/services/sandbox.ts:73 ?? 3600 default only
sandbox-service/src/schemas.ts:36,49,63 z.number().int().positive().optional() — positive, no max
sandbox-service/src/lib/sandbox.ts:115 ?? 3600 default only

The sandbox runtime does have a knob for this — server.max_sandbox_timeout_seconds — but it defaults to unset, which disables the bound. So an unconfigured deployment has no cap anywhere.

Observed on a deployment: out of ~50 live sandboxes, TTLs ranged from 90 minutes to one year, with no pattern beyond whatever each caller happened to pass. Most were idle.

Why it matters

A sandbox reserves node capacity for its entire life whether or not anyone is using it. A long TTL on an abandoned sandbox is a reservation nobody can reclaim until it expires, and there is no mechanism to reclaim it early other than deleting it by hand.

This also blunts the memory-request derivation from a30bb4b. That only applies to newly created sandboxes — the running ones keep the spec they started with — so the longer existing TTLs are, the longer it takes for the reduced reservations to actually show up.

Proposal

A default maximum, configurable, enforced in sandbox-service.

That layer is the right one: every caller funnels through it (the control plane's REST routes and the MCP tools both call it), and it is already where the resource-request derivation lives, so the two policies stay together.

  • Default to something bounded rather than unlimited. Anything from a few days to a month is defensible; the point is that the unconfigured case is bounded.
  • Configurable, including a way to opt out for deployments that genuinely need non-expiring sandboxes.
  • Reject over-limit requests with a clear message rather than clamping silently — a caller that asked for a month and got a day should be told.
  • Apply to renew as well as create, or the bound is trivially bypassed.

Worth doing regardless of the above: control-plane/src/routes/workspace-sandboxes.ts validates none of its body at runtime. timeout_seconds is only the case that prompted this — the same route takes image and resource on the same terms.

Setting server.max_sandbox_timeout_seconds on the runtime is complementary rather than an alternative: it is the only thing that also covers callers reaching the runtime directly, but it can only return a generic error and cannot special-case renew.

Out of scope

Reclaiming sandboxes that are idle before their TTL expires. That needs a notion of activity and is a separate discussion.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions