The envd contract
envd is our agent: one static binary, pid 1 inside every sandbox.
It reads its whole world out of its own process environment at boot. What it reads is agreed in
exactly one place, and this page is that agreement.
A connector's only job here is to deliver this map into the machine, and to record
the declaration somewhere list/0 can read it back. One connector puts it in the
machine's config; one writes a file its init system reads; one passes flags and keeps a file on
the gateway's disk. Same map, three deliveries.
What envd is, inside the machine
- pid 1, and the supervisor for every declared service — starting them at boot, restarting them when they die, and bringing them back after a wake.
- The ssh server, on 2222 inside the machine, published nowhere.
- The file server, NFSv3, on 2049 inside the machine, serving
/workand published nowhere. It implements its own file handles, because the off-the-shelf handler mints a random handle per path into a bounded in-memory cache — which would mean every handle a client held came back stale after a sleep, and a mount that was dead until somebody remounted it by hand. - The tunnel client, dialling the gateway, and the tunnel server that answers you.
- A small local API on
127.0.0.1:1988and over the tunnel:/env(what this machine is and how its workspace is doing),/exec(the gateway's route for running a command),/files/<path>(one file out of/work, behind a file link) and/hooks/log. - The identity broker:
envd token,envd aws,envd doppler-tokenandenvd git-credential.
Every variable a machine is booted with
| Variable | What it carries |
|---|---|
AUTHORIZED_KEYS | The one public key that opens the sandbox's ssh server. |
ENVEND_ENV_NAME | What the machine calls itself in its own logs. A label, not a credential: the gateway never takes a sandbox's word for its name. |
ENVEND_TAILCAT_KEY | The sandbox's own tunnel listener key, which is what keeps an address the gateway handed out working across a restart. |
ENVEND_TAILCAT_ALLOW | The peers that listener will answer, comma separated. Baked in at create and not editable afterwards. |
ENVEND_TAILCAT_CONTROL_KEY | The separate key the machine dials the gateway with. This is the sandbox's whole identity to the gateway: there is no bootstrap secret, and holding this key is the proof. |
ENVEND_GATEWAY_TUNNEL | The address it dials. Not a URL, and not on the public internet. |
ENVEND_SERVICES | The declared services, as JSON. Present only when there are some. |
ENVEND_IDENTITIES | The declared identities, as JSON. Present only when there are some. |
ENVEND_WORKSPACE | The declared workspace, as JSON. Present only when there is one. |
ENVEND_WORKSPACE_USER | Who the sandbox runs everything as — its hooks, its ssh sessions, every exec, and the owner of what its file share creates. Present only when a profile named one or the image declared one. |
Three more are read by envd and written by the image wrapper rather than by a
connector: ENVEND_IMAGE_CMD, ENVEND_IMAGE_WORKDIR and
ENVEND_IMAGE_USER — a wrapped image's original entrypoint, working directory and
user. envd supervises the first as a service and runs the sandbox's work as the
third, which is what keeps a USER node image belonging to node after
the wrap has forced root on pid 1.
This map must be present at every boot, not only the first. Declared
services come back after a sleep because envd reads them out of its own
environment every time it starts. A connector that delivered them through a run-once
mechanism would produce sandboxes whose services came back exactly once.
The declaration, and reading it back
Three of those variables are also what a sandbox is — what it runs, who it may be
elsewhere, what it is for. A listing has to be able to say all three after a gateway restart, so
a connector records them alongside the machine and reads them back when it lists. The strings
are kept in encoded form on purpose: it is the same string the machine got, so a
listing reports what envd is actually acting on rather than what the gateway
remembers deciding.
The hook log endpoint
Everything envend logs and env_logs show comes from one endpoint on
the sandbox's own agent:
GET /hooks/log?since=<byte offset>&follow=1
Plain text. Without follow it answers what exists and sets three headers: the
workspace state, the offset where the body ended, and the offset where this boot's output
starts. With follow it stays open, sends bytes as they are appended, and ends
itself with a verdict line — === workspace: ready, or === workspace: failed
— <first line of the error> — so a reader watching a build gets its answer on the
stream it was already reading. HEAD is the cheap question, and never follows.
An offset nobody names means this boot rather than zero. The log lives on the durable disk and is appended to for the life of it, so a reader starting at zero after a wake would replay last week's clone before showing the start hook that is running now.
The three calls a sandbox makes to the gateway
They go to a second listener, bound to loopback and reachable only over the sandbox's own tunnel. There is no bearer token on any of them and no environment name in any request: the tunnel key is the identity, and a sandbox can therefore only ever ask about itself.
POST /activity | Every thirty seconds: is anybody using this machine? ssh sessions, file traffic, load — plus how the workspace is doing, which is why a listing costs no extra call. |
|---|---|
POST /token | Mint an audience-bound OIDC token for this sandbox. The production path for workload identity; the token never leaves the machine. |
POST /github-token | Ask for a GitHub installation token. The gateway completes this one itself, and answers with a token GitHub has already narrowed to the repositories this sandbox declared. |
These used to be ordinary HTTPS calls to the gateway's public URL, carrying a per-sandbox secret baked into the machine. Both the second network dependency and the secret are gone.
The gateway's own public surface
Everything below needs the gateway's bearer token except the documents that are public by design: five of the gateway's own, and this site.
| Route | Token? | What it is |
|---|---|---|
POST /mcp | yes | The tool surface, streamable HTTP. |
GET /envs/:name/key | yes | A sandbox's ssh private key. |
GET /envs/:name/tailcat-key | yes | The client tunnel key for that sandbox. |
GET /envs/:name/tailcat-address | yes | Its tunnel address — which opens nothing without a key, but is behind the token because nothing here is outside it. |
POST /envs/:name/wake | yes | Start a stopped sandbox. It spends money, so it is not on the list of things a stranger can do. |
GET /healthz | no | A platform health check has no credential to offer, and the answer is two bytes. |
GET /topology | no | The living system diagram. Documentation. |
GET /, /docs, /docs/<slug>, /site/* | no | This site. Documentation, and the stylesheet it needs. |
OIDC discovery under /mint/.well-known/ | no | Federation means somebody else fetching it, and it publishes only public keys. |
| anything else, including the 404 | yes | An unauthenticated caller should not get to map the surface by watching which paths exist. |
Every exemption is matched as an exact path, not by prefix, so exempting the published keys
does not open /mint wholesale. Missing configuration fails closed: a release with
no tokens configured refuses every guarded request with a message saying exactly what is wrong.
The failure mode of a misconfigured deploy is "nobody can use it", never "anybody can".