envend
guide

Sharing a port, linking a file

Two ways to let somebody who is not you see something inside a sandbox. Neither of them opens a port on the sandbox. That is the whole design, and it is worth being precise about, because "it has a public URL" and "it has a public port" sound like the same sentence and are not.

envend share omega-k4zt 4000        # https://omega-k4zt-4000.envend.dev
envend link  omega-k4zt out/report.html
                                    # https://omega-k4zt.envend.dev/f/out/report.html?exp=…&sig=…

Both are also MCP tools — env_share and env_link — so an agent can do either without you.

A sandbox has no public port

There is no create-time alternative. env_create has no ports argument, no backend publishes a port, and a sandbox nobody has shared has no public route to it of any kind. There used to be a second way — one provider only, plain HTTP on a high port, no TLS, no revocation, and reachable from the moment the machine booted — and it was deleted rather than deprecated, because a worse version of this sitting alongside it was a trap rather than a choice.

What actually happens

A sandbox's ssh server, its file server and its local agent are all envd, and all sit behind a tunnel that answers exactly two peers: you, and the gateway. The gateway is on that list because it put itself there when the sandbox was created, using one long-lived key it keeps beside its OIDC signing key. So a request for a shared port arrives at the gateway, not at the sandbox:

browser ──https──▶ the gateway
                       │  is this hostname a share?
                       ▼
                   the gateway's own tunnel, key-pinned
                       ▼
                   sandbox:4000

TLS terminates at the gateway. Nothing is opened on the machine and nothing new starts inside it. Three consequences worth knowing:

  • Unsharing is instant and total. It removes a row in the gateway. There is nothing to restart, nothing cached, and no window in which somebody who already has the URL still gets through.
  • A sleeping sandbox still works. The gateway asks the provider to start it and holds the request, for up to a minute. The first page load after an idle spell is slow; the rest are not.
  • A share is not a login. Anyone with the URL reaches the app. The tunnel protects the sandbox, not the app: nothing else on the machine is reachable, and what you shared is shared. The app's own authentication is the only gate in front of it.

Do not share a port serving anything you would not put on the open internet.

Share a declared service, not something you started by hand

What comes back after a wake has to be listening after the wake. On a backend that rebuilds everything outside /work, a server you started in an ssh session is gone and the URL answers nothing. Declare it instead — services on env_create, or in the profile — and envd brings it back at every boot without being asked. See Declared services.

File links

A file link is the same path with a different endpoint at the far end: the gateway asks the sandbox's own agent for one file out of /work and streams it out. This is how you hand a file to a person — a coverage report, a rendered page, a screenshot, a PDF. They open the URL on any device, with no envend account, no key and nothing to install.

The path has to be under /work, and the check happens after symlinks are resolved rather than before, because a symlink at /work/out pointing at / contains no .. and never leaves /work as a string.

Links expire — an hour by default, seven days at most — and prefer short: a link is cheap to make again. They are signed, so the sandbox, the path and the expiry cannot be edited by whoever holds one, but until it expires anyone holding it can open the file. Treat it like a link to a private document.

envend revoke-links <name> rotates the per-sandbox signing secret, which invalidates every link ever issued for that sandbox at once. There is no list to sweep, because a link is verified rather than looked up, so revocation cannot miss one. Links made afterwards work normally. Shared ports are unaffected — unshare is the equivalent for those.

Two limits

  • A sandbox created before the gateway had its own key cannot be shared. A sandbox's list of admitted peers is baked into its machine config at create and cannot be edited afterwards. env_share says so, and says the only fix: destroy it and create it again. /work goes with it, so copy anything worth keeping out first.
  • Destroying a sandbox kills every share and every link immediately, for everyone, including people who already have the URL.

Operator setup

Sharing needs two DNS records and one certificate, none of which the gateway can do for itself. Until they exist, every share hostname simply fails to resolve.

  1. A wildcard CNAME for *.envend.dev pointing at the gateway. A specific record always wins over a wildcard, so the API hostname is unaffected — and the gateway excludes it by name as well, so a sandbox called gateway cannot shadow it.
  2. A wildcard certificate for *.envend.dev. It can only be issued against a DNS-01 challenge, so one more _acme-challenge record is needed; the hash in its value is generated per certificate, so take it from the certificate command's own output rather than from any document.
  3. Nothing else. ENVEND_PUBLIC_DOMAIN already defaults to envend.dev; set it only if shares live under a different domain, and remember the wildcard DNS and the wildcard certificate have to name the same one.

The success case for a check is a 404: TLS terminated, the request reached the gateway, and the gateway correctly answered that nothing is shared at that hostname.