TILLSECRETS · APPROVALS

An agent can ask. Only you can approve.

Beta

Whoever holds your session can do what you can, and that includes an AI agent running the CLI on your login. With approvals on, the actions that expose or send out a secret wait until you approve them with a passkey, your phone or a confirmed authenticator code. None of them is something the session can supply, so the agent can ask but cannot approve.

01Scope

What waits for you

ActionWhen
secret.revealShowing a value: Reveal in the console, or tilldev secrets get.
secret.resolvePutting values into a command: tilldev secrets exec --env-file. May repeat for a time you choose.
ssh.signSigning with an SSH key held in the vault, through the SSH agent: a login to a server the SSH client proved, or a git or other signature. May repeat for a time you choose.
ssh.sign_onceThe same, when the server is not proven, the agent is forwarded to another machine, or the data is not a login or signature. Always single-use.
destination.allowAdding a host a secret may be sent to, or changing its methods or paths. Also fulfilling a secret request that allows hosts.
grant.createConsenting to an agent spending a secret.
presign.createPresigning a call.
token.createMinting a service token (ts_…).
agent.createMinting an agent token (ta_…).
identity.createLetting a workload sign in without a token: a workload identity.
identity.updateChanging what a workload identity trusts, its keys, token lifetime or host key. A new name alone does not ask.
sync.create, sync.enableAdding a sync target, or resuming a paused one.
tier.lowerMoving a secret or environment from restricted (7+) to below 7.
secret.sealSealing a secret so nobody can reveal it. See Sealed secrets.
release.request, release.approveAsking to reveal or unseal a sealed secret, and another owner approving it.
anomaly.resolveResolving a spend anomaly, so an agent can't silence its own alarm.
canary.viewSeeing which secrets are canaries. Every time.
canary.retire, canary.configureRetiring a canary, or changing where canaries are made.
repos.eraseErasing every TillForge repository in the organization and destroying its repository key. Always single-use.
step_up.offTurning approvals off for the workspace.

Machines are not slowed down. A service token's pull, a broker spend through a grant, and an agent token work as before; the approvals happened when the token, identity, grant or destination was created. The one exception is SSH signing: each signature asks the person who made the token, as it would ask you. A platform API key (tp_…) cannot approve anything, so it gets 403 on these actions.

02Setup

Add a way to approve

Open Approvals in the console sidebar (or tilldev approvals open). First prove it is you, with your password, a link sent to your email, an existing passkey or a confirmed code. Then add a passkey, pair your phone, or confirm the authenticator app you already sign in with.

Why confirm the authenticator
Anyone holding your session could have turned on sign-in 2FA with their own app. A code counts for approvals only after you confirm it with proof the session does not have. Turning 2FA off and on again, or an admin resetting it, clears the confirmation.

Adding or removing a way to approve sends an email to you. Every request, approval, denial and use is in the audit log.

03Phone

Approve from your phone

Under Approvals → Phone, choose Pair a phone and scan the code with Till Authenticator (tap +), or open the link on the phone itself. The code works once, for 10 minutes. The phone makes a signing key that stays in its secure storage; the vault keeps only the public half.

  1. On the approval page, choose Send to my phone. The page shows a two-digit number.
  2. Open Approvals in Till Authenticator.
  3. It shows the request: what it does, the workspace, where it was asked from, and any repeat window you chose on the page.
  4. Pick the number from the page and unlock with Face ID, a fingerprint or your passcode. The page carries on by itself.

Picking another number denies the request at once, so there is no second guess. The phone signs its decision, including the repeat window, and the vault checks that signature. The phone has 2 minutes to answer; send it again if it lapses.

A request you did not send
Something holding your session can send a request to your phone too. Approve only what you sent from an approval page in front of you. If one arrives that you did not send, deny it.

Remove a phone under Approvals → Phone (this needs the same proof as adding one), or unpair it from Till Authenticator. Pairing and removing each send you an email.

04CLI

In a terminal, it waits

A command that needs an approval prints a link, opens it in your browser (unless --no-open), and carries on once you approve.

terminal
$ tilldev secrets exec --env-file .env.refs -- npm run migrate
! Needs your approval: Put DATABASE_URL, STRIPE_KEY into `npm run migrate`

    https://tilldev.dev/acme/approve/5b0c…

Waiting while you approve it in the browser, or on your phone from there. Ctrl+C stops waiting; the request expires in 10m.
# approve with your passkey, and the command carries on

Without a terminal it exits with code 3 and the link straight away, so a script or agent can hand the link to you. The CLI keeps the claim in ~/.tilldev/approvals.json (mode 0600), so running the same command again uses your approval. Running it again before you approve points at the same request rather than opening another.

scripts and agents
# No terminal (CI, an agent's shell): exit code 3 and the link, at once.
tilldev secrets get STRIPE_KEY --env prod --json
echo $?   # 3

# Approve it, then run the same command again. The approval covers exactly that command.
tilldev secrets get STRIPE_KEY --env prod --json

# Or wait anyway, up to 600 seconds.
TILLDEV_APPROVAL_WAIT=300 tilldev secrets get STRIPE_KEY --env prod
manage
tilldev approvals                 # your requests from the last 30 days
tilldev approvals --pending       # only the ones waiting
tilldev approvals open            # the Approvals page: passkeys, phone, authenticator, what may repeat
tilldev approvals deny <id>       # refuse a request, or stop one that may repeat
tilldev approvals policy          # on or off for the workspace
tilldev approvals forget          # delete the approval claims this machine holds
05Repeat

Allow one exact command for a while

Two actions can be allowed to repeat when you approve them. Everything else is single-use and must be used within 5 minutes of approving.

ActionRepeat forWhat repeats
secret.resolve1, 7 or 30 daysThat exact command with the same references and agent key. Useful for a dev server or an MCP server you start many times a day.
ssh.sign15 minutes, 1 or 8 hours, 1 or 7 daysLogins with that key to the same server as the same user, or signatures with that key in the same namespace (git, for example).
What a repeat allows
The server sees a command as text and cannot see what really runs, and it cannot tell which program on your machine asked the SSH agent. For the window you choose, anything on that machine holding your session and the stored claim can do the same thing again. Allow repeats only on a machine you trust, and keep the window short.

Approvals → Allowed to repeat lists them with a use count; Stop, or tilldev approvals deny <id>, ends one at once.

06API

428, then repeat with the approval

A gated call answers 428 STEP_UP_REQUIRED and does nothing. The body carries the approval. Only the caller that asked holds its claim; keep it out of logs. Repeat the same request with x-tilldev-approval: <id>.<claim> once it is approved. The approval is bound to that person, workspace, action and every value in the request, so it cannot be spent on something else. See the API reference.

An agent token cannot list approvals, but it can poll the one it asked for: send the same x-tilldev-approval header with GET /api/step-up/<id>.

428 body
HTTP/1.1 428 Precondition Required
{
  "error": "Needs your approval: Reveal STRIPE_KEY (shop / prod, version 4). Approve it at https://tilldev.dev/acme/approve/5b0c…",
  "code": "STEP_UP_REQUIRED",
  "request_id": "…",
  "approval": {
    "id": "5b0c…",
    "claim": "q3V…",
    "url": "https://tilldev.dev/acme/approve/5b0c…",
    "action": "secret.reveal",
    "summary": "Reveal STRIPE_KEY (shop / prod, version 4)",
    "expires_at": "2026-09-29T12:10:00Z"
  }
}
poll + repeat
# Poll until approved (or denied / expired), then repeat the same request with the header.
curl -s https://tilldev.dev/api/step-up/$ID -H "authorization: Bearer $TOKEN" | jq -r .status
curl -s -X POST https://tilldev.dev/api/secrets/secrets/$SECRET/reveal \
  -H "authorization: Bearer $TOKEN" \
  -H "x-tilldev-approval: $ID.$CLAIM"
07Workspace

On by default

Approvals are on for every workspace. An owner or admin can turn them off under Approvals → Workspace setting or with tilldev approvals policy off. Turning them off needs an approval itself, so a stolen session cannot switch them off first.

Audit
step_up.approved, step_up.denied, step_up.used, step_up.passkey_added, step_up.passkey_removed, step_up.device_added, step_up.device_removed, step_up.totp_confirmed, step_up.policy_on and step_up.policy_off.