Menu
Platform
AI
App store purchases
Database
Flags
Jobs and cron
Localization
Monitoring
Notifications
Payments
Queues
Sandboxes
Webhooks
Getting Started
Authentication
KV Store
Deploy & Infrastructure
Reference
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:
| Field | Type | What it is |
|---|---|---|
egress | EgressPolicy | `allow`, `deny` or `allowlist`. Default ALLOW: the guest reaches the internet. |
allowed_domains | string[] | 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_cidrs | string[] | 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:
sylphx sandboxes leases set-network orgs/acme/projects/shop/envs/production/leases/leasecurl -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":{}}'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:
| Field | Type | What it is |
|---|---|---|
port | int32 | The guest port to expose. Required. |
visibility | PortVisibility | Default 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:
sylphx sandboxes leases open-port orgs/acme/projects/shop/envs/production/leases/leasecurl -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}}'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:
sylphx sandboxes leases close-port orgs/acme/projects/shop/envs/production/leases/leaseA 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:
sylphx sandboxes leases mint-token orgs/acme/projects/shop/envs/production/leases/lease \
--scopes guestcurl -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"]}'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 atstatus.endpoints.guest_uri: streamingexecand PTYs, and the guest's own/files.ports— theuriof a private port.cdp— the Chrome DevTools Protocol of a browser lease, atstatus.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.