---
title: Secrets
description: 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
```

```ts
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](/docs/reference/persistence) 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.

```ts
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

```ts
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](/docs/reference/browser#the-browser-profile-persists) | yes                    | a login you want every future machine to inherit     |
| A secret                                                                  | no                     | a credential that must not end up in a captured disk |
