A secret nobody reads again
BetaMost secrets never need to be read by a person. Your app needs the database password; you don't. Seal it and nobody can reveal it, owners included, while everything that uses it keeps working. If someone ever does need to see it, two owners have to agree.
What a seal changes
| Path | Once sealed |
|---|---|
| Reveal (console, tilldev secrets get, API) | Refused for everyone. A one-time reveal takes two owners. |
| tilldev secrets exec, tilldev mcp run, the local bridge | Refused, with the reason sealed. They would put the value on a machine. |
| The broker: grants, presigned calls, catalog actions, SSH signing | Work as before. The value is used inside TillDev and never returned. |
| Pull with a service token | Only a token pinned to its environment gets it, as for a restricted secret. Others see it withheld. |
| Sync targets | Keep syncing. |
| CI | Protected branches only, and only an owner can bind it to a repository. |
| Broker destinations | Only an owner can add one. |
| Set, generate, rotate, roll back, delete | Work as before. |
| Tier | Cannot go below restricted until it is unsealed. |
Seal a secret
In the console, open the secret's row under Secrets → Projects and choose Seal, or use the CLI. Only owners can seal, and sealing waits for your approval (secret.seal). Dynamic secrets are not sealed; they have no stored value to reveal.
# Seal it. Owners only; it waits for your approval.
tilldev secrets seal DATABASE_URL --env prod
# Give it a value nobody has ever seen: made in the vault, never shown.
tilldev secrets gen DATABASE_PASSWORD --env prod --type password --length 40
# From now on:
tilldev secrets get DATABASE_URL --env prod
# x DATABASE_URL is sealed, so nobody can reveal it, owners included. ...
# Ask: tilldev secrets releases ask DATABASE_URL --env prod --reveal --reason "…"tilldev secrets gen so the vault makes it and no person or chat ever holds it.Two owners to release it
- An owner asks, with a reason, to reveal it once or to unseal it. Asking waits for their approval (
release.request). - The other owners get an email. The request waits under Secrets → Releases for 24 hours.
- A different owner approves (
release.approve, which waits for their approval too) or denies it. The asker can withdraw it. - An approved reveal lets the asker reveal the current version once, within an hour, and the secret stays sealed. An approved unseal lifts the seal at once.
# Owner A asks, with a reason the other owners will see.
tilldev secrets releases ask DATABASE_URL --env prod --reveal --reason "restore drill"
# Owner B sees it waiting, and approves or denies.
tilldev secrets releases
tilldev secrets releases approve <id> # or: deny <id>
# Owner A reveals it, once, within the hour.
tilldev secrets get DATABASE_URL --env prod
# Lifting the seal for good works the same way.
tilldev secrets unseal DATABASE_URL --env prod --reason "moving to a dynamic backend"An owner can never approve their own request, and one secret has at most one waiting request of each kind. If the asker stops being an owner, their request can no longer be approved. A workspace with one owner can seal a secret but cannot release it until it adds a second owner. Admins can see requests but cannot ask or decide.
What a seal does not stop
A seal closes the direct ways to read a value: the reveal button, secrets get, and the commands that would put it on a machine. Those are what a phished session or an agent with your login reaches for first.
A program that pulls the value at boot holds it in memory, as it would without a seal. Use the broker where you can, so the program never holds it at all.
API
POST /api/secrets/secrets/{id}/seal
GET /api/secrets/releases?open=1[&secret_id=…]
POST /api/secrets/releases {"secret_id": "…", "action": "reveal" | "unseal", "reason": "…"}
GET /api/secrets/releases/{id}
POST /api/secrets/releases/{id}/approve
POST /api/secrets/releases/{id}/deny
POST /api/secrets/releases/{id}/cancelA reveal of a sealed secret answers 403 with reason: "sealed". Asking answers 409 with not_sealed, needs_second_owner or pending (with the waiting request). Lowering a sealed secret's tier answers 409 with sealed. Listing an environment's secrets shows each one's sealed_at. See the API reference.
secret.seal, secret.unseal, release.request, release.approve, release.deny and release.cancel. A reveal through a release is a secret.read that names the release and the owner who approved it.