Skip to content
Raster
Esc
navigateopen⌘Jpreview
On this page

Secrets

Values delivered into tmpfs inside a machine, and never readable back out.

GET    /v1/machines/{id}/secrets          names, versions and delivery state
PUT    /v1/machines/{id}/secrets          store or replace one
DELETE /v1/machines/{id}/secrets/{name}   revoke one
GET    /v1/secrets                        every secret name in the organization
await machine.secrets.set("GITHUB_TOKEN", token);

A stored value is never returned

There is no read route, and the response shape has no field a value could travel in. That is not an omission to be fixed later: a product that can hand a secret back is a product whose secrets are in every response log, error report and browser cache that ever touched one.

What you can read is the name, the version — bumped on every replacement, so you can tell a change from a no-op — and delivered_at.

Names are shell-shaped

A name must match ^[A-Z_][A-Z0-9_]*$: an uppercase environment variable name. Accepting a name a guest cannot export would store a secret that silently never arrives.

Values are up to 64 KB and are stored encrypted.

Delivery

Secrets land at /run/computer/secrets.env inside the machine, mode 0600. /run is a tmpfs on every Linux the product ships, which is the entire point:

A disk capture cannot contain what was never on the disk.

So a snapshot, fork or template does not carry a secret. A machine that restarts takes delivery again from storage.

A login shell picks them up through a loader at /etc/profile.d/10-computer-secrets.sh. That file is on disk and holds no secret — three lines that source the tmpfs file if it is readable. Putting the values there instead would put them in every snapshot the machine ever takes.

const { stdout } = await machine.terminal.exec(["/bin/sh", "-lc", "echo $GITHUB_TOKEN"]);

delivered_at is null when a secret is stored and not yet delivered, which is the normal state for one set on a machine that is not running — it lands on the next boot. A running machine takes delivery within seconds.

Revoking

await machine.secrets.delete("GITHUB_TOKEN");

Removes it from storage and from the running machine. It cannot remove a copy the guest already made: a process that read the value and wrote it elsewhere still holds it. Revoking at the source is this product’s half; rotating at the issuer is yours.

Deployment requirement

Secrets need an encryption keyring configured on the deployment. Without one the API refuses with unsupported_operation rather than storing a value in a form it could be read back from.

Rotation is appending a key version, never replacing one: the newest key seals new values and every older key stays available to open what it sealed. Dropping an old key does not rotate anything — it makes every value sealed under it permanently unreadable.

Secrets versus the browser profile

The two are deliberately opposite, and picking the wrong one is the common mistake:

Carried by a snapshot? Use for
A browser session yes a login you want every future machine to inherit
A secret no a credential that must not end up in a captured disk

Was this page helpful?