envend
reference

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 /work and 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:1988 and 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-token and envd git-credential.

Every variable a machine is booted with

VariableWhat it carries
AUTHORIZED_KEYSThe one public key that opens the sandbox's ssh server.
ENVEND_ENV_NAMEWhat 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_KEYThe sandbox's own tunnel listener key, which is what keeps an address the gateway handed out working across a restart.
ENVEND_TAILCAT_ALLOWThe peers that listener will answer, comma separated. Baked in at create and not editable afterwards.
ENVEND_TAILCAT_CONTROL_KEYThe 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_TUNNELThe address it dials. Not a URL, and not on the public internet.
ENVEND_SERVICESThe declared services, as JSON. Present only when there are some.
ENVEND_IDENTITIESThe declared identities, as JSON. Present only when there are some.
ENVEND_WORKSPACEThe declared workspace, as JSON. Present only when there is one.
ENVEND_WORKSPACE_USERWho 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 /activityEvery 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 /tokenMint an audience-bound OIDC token for this sandbox. The production path for workload identity; the token never leaves the machine.
POST /github-tokenAsk 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.

RouteToken?What it is
POST /mcpyesThe tool surface, streamable HTTP.
GET /envs/:name/keyyesA sandbox's ssh private key.
GET /envs/:name/tailcat-keyyesThe client tunnel key for that sandbox.
GET /envs/:name/tailcat-addressyesIts tunnel address — which opens nothing without a key, but is behind the token because nothing here is outside it.
POST /envs/:name/wakeyesStart a stopped sandbox. It spends money, so it is not on the list of things a stranger can do.
GET /healthznoA platform health check has no credential to offer, and the answer is two bytes.
GET /topologynoThe living system diagram. Documentation.
GET /, /docs, /docs/<slug>, /site/*noThis site. Documentation, and the stylesheet it needs.
OIDC discovery under /mint/.well-known/noFederation means somebody else fetching it, and it publishes only public keys.
anything else, including the 404yesAn 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".