Skip to content
Console
Menu

Queues

Workflows

Getting Started

Authentication

KV Store

Lease networking

Egress policy, exposed ports and lease tokens

A lease has two network questions — what the guest may reach, and what may reach the guest — and one answer to both: an explicit policy on the lease, plus short-lived tokens for the data plane.

#Egress policy

spec.network is the outbound policy, and its default is permissive:

FieldTypeWhat it is
egressEgressPolicy`allow`, `deny` or `allowlist`. Default ALLOW: the guest reaches the internet.
allowed_domainsstring[]The hosts reachable when `egress` is ALLOWLIST: exact names, or one leading `*.` wildcard label, so `api.openai.com` and `*.github.com` are both valid.
allowed_cidrsstring[]The public CIDRs reachable when `egress` is ALLOWLIST.

An allowlist is written in names where names are what you know: *.github.com covers the subdomains of one label, and both halves are still evaluated by the Cell rather than by a helper in the guest.

status.network reports the policy the lease is actually running under, and it is the field to read when a connection fails rather than the field you wrote. It carries blocked_cidrs as well — the ranges that are blocked under every policy, including allow. A host that resolves into one of them is unreachable by design, not by misconfiguration.

#Change the policy on a running lease

set_network replaces the outbound policy of a live lease:

Shell
sylphx sandboxes leases set-network orgs/acme/projects/shop/envs/production/leases/lease
Shell
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases/lease:setNetwork" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"network":{}}'
TypeScript
const response = await sylphx.sandboxes.leases.setNetwork({
  name: 'orgs/acme/projects/shop/envs/production/leases/lease',
  network: {},
})

The replacement is whole: the policy you send is the policy the lease has, so a field you leave out goes back to its default rather than staying as it was. Tighten a lease for one step and widen it for the next without recreating the machine, and read the result back from status.network.

Two things about it are deliberate. The lease's generation does not change, so a stream or an action in flight is not invalidated by a network change. And the Cell applies the new policy before the call returns, which means a successful set_network is a statement about the machine rather than a request that will be honoured shortly. The change is recorded as a network_changed event on the lease's stream.

#Expose a guest port

A guest port is exposed either when the lease is created or later:

FieldTypeWhat it is
portint32The guest port to expose. Required.
visibilityPortVisibilityDefault PRIVATE: reaching it takes a lease token or an Access key holding `sandboxes:exec`. A `public` port takes neither.

spec.ports sets them at grant, and open_port and close_port change them on a running lease — both need sandboxes:exec rather than sandboxes:write, because a port is data plane:

Shell
sylphx sandboxes leases open-port orgs/acme/projects/shop/envs/production/leases/lease
Shell
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases/lease:openPort" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port":{"port":1}}'
TypeScript
const response = await sylphx.sandboxes.leases.openPort({
  name: 'orgs/acme/projects/shop/envs/production/leases/lease',
  port: { port: 1 },
})

The opened port comes back in status with its own uri, which is the address to hand to whatever consumes it. Closing is the port number alone:

Shell
sylphx sandboxes leases close-port orgs/acme/projects/shop/envs/production/leases/lease

A server inside the lease that should be reachable only by your own code wants the default visibility and a token. A server that should be reachable by anyone wants public, and wants it knowingly: a public port is a listener on the internet, and the lease's own isolation is the only other thing in front of it.

#Lease tokens

A lease token is the key to the data plane. It is minted per caller and per purpose, so the whole point is that it is narrow:

Shell
sylphx sandboxes leases mint-token orgs/acme/projects/shop/envs/production/leases/lease \
  --scopes guest
Shell
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases/lease:mintToken" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scopes":["guest"]}'
TypeScript
const response = await sylphx.sandboxes.leases.mintToken({
  name: 'orgs/acme/projects/shop/envs/production/leases/lease',
  scopes: ['guest'],
})

scopes is required, and each one admits a different door:

  • guest — the guest daemon at status.endpoints.guest_uri: streaming exec and PTYs, and the guest's own /files.
  • ports — the uri of a private port.
  • cdp — the Chrome DevTools Protocol of a browser lease, at status.endpoints.cdp_uri.
  • computer — computer use: actions and the live stream.

ttl bounds the token: at most 1 hour, and never past the lease's own expire_time. The token answers with the generation it is bound to, which is the lease's generation — so a token minted before a pause or a resume does not open the machine that comes after it. It is returned once and never again, and expire_time says when it stops verifying.

The shape of the whole thing: the API key or Access principal mints tokens, the tokens are what the machine's own callers carry, and the lease's end takes them all with it.