Temporary mount credentials

Hand a runtime a short-lived mount ticket instead of your API key: issuing, narrowing scope, revocation, and what a ticket can never do.

You need a mount ticket when a machine should mount a workspace but must not hold your account credentials: a customer's sandbox, a short-lived worker VM, a CI job you'd rather not trust with a root API key. A ticket is a short-lived signed file that authorizes exactly one mount; if the runtime leaks it, the blast radius is one workspace, one mode, one TTL. This page explains the mount session behind every mount, the mount ticket that delegates one, and exactly what a ticket can and cannot do.

Skip this page if every machine that mounts is one you trust with your own login or API key. Sessions are issued automatically for those mounts, and none of this needs managing.

Mount sessions

A grant says a runtime may mount. Issuing turns that permission into a mount session: an authentication record scoped to one workspace, one tenant, optionally one runtime id, one grant, one mode, and one TTL. The mount ticket is the signed file that carries that session's credential to the runtime. One runtime id can hold many sessions over its lifetime.

Why short-lived instead of passing the root credential

A customer runtime is an environment you may not fully control. Handing it a long-lived API key means a compromise of the runtime compromises your platform account. A mount session caps the blast radius: bounded in time, bounded to one workspace, bounded by grant mode, and revocable on its own.

Issuing a ticket

const { mount_ticket } = await artifacts.platform.createMountTicket('acme', taskId, {
  workspace: `<your-handle>/task-${taskId}`,
  mode: 'ro',
  ttlSeconds: 3600,
});

ttlSeconds is required. There's no implicit long default; pick the smallest TTL that comfortably outlives the runtime task. If the tenant or a runtime-scoped grant doesn't exist yet, this call creates them.

Not a platform, just running your own sandbox fleet? Issue a ticket against your own workspace directly, with no tenant objects involved; the ticket behaves identically on the runtime side:

const { mount_ticket } = await artifacts.workspaces.createMountTicket('<your-handle>/my-workspace', {
  mode: 'ro',
  ttlSeconds: 3600,
});

The Orchestrator returns { mount_ticket } to your backend. The ticket wraps the mount manifest and includes the cleartext session token; you see that token exactly once at create time, and the Orchestrator stores only a hash. Use the session id inside the manifest for subsequent lookups and revocation.

Your control plane writes the JSON-serialized ticket to the worker as a file before artifacts mount runs, e.g. /run/tonbo/artifacts/mount-ticket.json. Orchestrator does not inject files, relay commands, or supervise the worker.

Scoping: narrow, never expand

A session can only narrow the grant it's signed from, never widen it:

Active grantSession requestSession can mount
tenant-wide rw on AA, rwA at rw
tenant-wide rw on AA, roA at ro
runtime-scoped ro on AA, rwRejected; session mode can't exceed grant mode
runtime-scoped rw on AB, rwRejected; grant is for A only
revoked grant on AA, roRejected; grant is not active

The mount-time check runs against current grant state. If you revoke a grant between signing the session and the actual mount, the mount is rejected even if the session token is otherwise valid.

Revocation

Revoke with the calls in Tenants and grants: revokeAccessGrant(grantId), revokeAccessGrants({ tenantSlug, runtimeId }), or deleteTenant(slug) to tear down everything for a customer. Revocation propagates within about one second; an active mount starts returning errors on the next syscall, and the unmount path is the same as artifacts unmount.

A delegated mount ticket is not refreshable in place. When one approaches its TTL, your backend issues a new ticket and the runtime re-mounts.

What this isn't

  • Not POSIX-based. POSIX permissions inside the mount are separate. Don't rely on them as a security boundary between mutually untrusted runtimes (see Concurrent writes).
  • Not per-path. Grants are workspace-level. There's no per-directory scoping.
  • Not coordinated writes. Two runtimes with rw on the same workspace can write concurrently; what happens then is Concurrent writes's problem, not the grant system's.
  • Not transferable. A session token issued for runtime A can't be used to act as runtime B.

API summary

const runtimeId = taskId;
// Workspace provisioned ahead of time via the CLI:
//   artifacts workspace create task-4711

// One-shot: auto-creates the tenant + a runtime-scoped grant, then signs the ticket.
const { mount_ticket } = await artifacts.platform.createMountTicket('acme', runtimeId, {
  workspace: `<your-handle>/task-${taskId}`,
  mode: 'ro',
  ttlSeconds: 3600,
});

// Your control plane writes mount_ticket to the worker as a file, e.g.
// /run/tonbo/artifacts/mount-ticket.json, before `artifacts mount` runs.