Your SSH key, used but never copied out
BetaAn SSH key usually sits in ~/.ssh, often with no passphrase, readable by anything running as you. tilldev ssh agent is an SSH agent whose keys stay in TillSecrets. ssh, git and an AI session ask it for signatures as they would any agent. The vault signs, and each use waits for your approval or is allowed for a time you choose. The private key is never written to your machine.
Make a key in the vault, or move one in
keygen makes an ed25519 key (or RSA with --type rsa) inside the vault and prints the public half for a server's authorized_keys or your Git host. import stores a key you already have: ed25519, RSA or ECDSA, in OpenSSH or PEM format. For a key with a passphrase, ssh-keygen asks for it and opens a copy in a private temporary folder, which is deleted straight after. --remove deletes the file only once the vault holds the same key.
# Make a key in the vault. The private half is made there and never reaches this machine.
$ tilldev ssh keygen infra/prod/DEPLOY_KEY
OK Made secretref://infra/prod/DEPLOY_KEY — the private key was made in the vault and never reached this machine
type ssh-ed25519
fingerprint SHA256:q0Hc…
public key (for ~/.ssh/authorized_keys on a server, or your Git host):
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA… secretref://infra/prod/DEPLOY_KEY
# Or move a key you already have. A key with a passphrase asks for it once.
$ tilldev ssh import infra/prod/DEPLOY_KEY --file ~/.ssh/id_ed25519 --removeSigning needs the same access as revealing the key: admin, or owner for a restricted secret (sensitivity 7 and up). Keygen and import add the key to the agent unless you pass --no-add.
Run it and point ssh at it
config --write adds an IdentityAgent block at the top of ~/.ssh/config, so ssh uses the agent without changing your shell. Running it again replaces the block. install starts the agent at login with launchd on macOS or systemd on Linux.
tilldev ssh config --write # point ssh at the agent (add --host github.com to limit it)
tilldev ssh install # run the agent now and at every login
# Or run it in the foreground and watch it:
$ tilldev ssh agent
OK SSH agent on ~/.tilldev/ssh-agent.sock (1 key)
* SHA256:q0Hc… secretref://infra/prod/DEPLOY_KEYThe socket is readable only by you (mode 0600, in a 0700 folder). On Windows the agent listens on the named pipe \\.\pipe\tilldev-ssh-agent; start it from a scheduled task at log-on.
tilldev ssh ls # keys the agent offers, with fingerprints
tilldev ssh add <project>/<env>/<KEY>
tilldev ssh rm <project>/<env>/<KEY> # stop offering it; the key stays in the vault
tilldev ssh keygen <ref> --rotate # a new key under the same name
tilldev ssh uninstall # stop starting it at loginEach use asks you, or is allowed for a while
When ssh asks for a signature, the agent sends it to the vault, which reads what is being signed and asks you to approve exactly that. Your browser opens the approval page; approve with a passkey, your phone or a confirmed authenticator code.
$ ssh deploy@build-01.example.com
# in the agent's log, and your browser opens the approval page:
12:04:31 Needs your approval: Sign in to build-01.example.com (SHA256:7Yh…) as deploy with DEPLOY_KEY (infra/prod)
https://tilldev.dev/acme/approve/5b0c…
12:04:39 Signed with secretref://infra/prod/DEPLOY_KEY (approved)| What is signed | Asks for | Can repeat |
|---|---|---|
| A login to a server your SSH client proved (OpenSSH 8.9 and later) | ssh.sign | 15 minutes to 7 days, for that key, server and user |
A git commit or tag, or another ssh-keygen -Y sign signature | ssh.sign | 15 minutes to 7 days, for that key and namespace |
| A login where the client did not prove the server | ssh.sign_once | No. One signature, of those exact bytes |
| Anything asked through an agent forwarded to another machine | ssh.sign_once | No |
| Data that is neither a login nor a signature | ssh.sign_once | No |
The server is named from its host key's own signature, checked in the vault, and from your known_hosts for a readable name. A login for a different key, or to a server other than the one the connection proved, is refused outright.
A server waits about two minutes for a login. If you approve after it gives up, run the command again: for a proven server the approval you gave still applies. A single-use approval covers one exact signature, so the retry asks again.
Sign commits and tags
tilldev ssh git sets git to sign with the vault key through the agent, for this repository or with --global for all of them. --sign-all signs every commit and tag. Allow ssh.sign for the git namespace for a working day and commits go straight through.
tilldev ssh git infra/prod/DEPLOY_KEY --global --sign-all
git commit -m "Ship it" # signed through the agent
tilldev ssh ls --public # the line to add at your Git host as a signing key
# On TillForge, so the commits show as verified:
tilldev forge signing-key add --key "$(tilldev ssh ls --public | head -n 1)" --title "vault key"Let an agent use the key
An AI session on your machine uses the agent like ssh does, through SSH_AUTH_SOCK or your ssh config. It never holds the key, and each use still waits for you. To keep the session off your own login, run a separate agent with an agent token whose patterns include the key. Its approvals go to the person who made the token, and each signature counts against the token's daily budget.
# Where the AI session runs. Its token's patterns must include the key.
export TILLDEV_AGENT_TOKEN="$(cat deploy-bot.token)"
tilldev ssh agent --key infra/prod/DEPLOY_KEY --socket ~/.tilldev/deploy-bot.sock --no-open &
export SSH_AUTH_SOCK=~/.tilldev/deploy-bot.sock
ssh deploy@build-01.example.com # the approval goes to the person who made the tokenWhat it does not do
- No keys from outside.
ssh-addis refused; the agent offers only vault keys. Usetilldev ssh import. - RSA with SHA-2 only. A server that asks for an
ssh-rsa(SHA-1) signature is refused. - Online. Every signature is made in the vault, so the agent needs a connection.
- On the record. Each signature is in the broker ledger with the server or namespace, and each approval is in the audit log.