maisecrets / SSH
Ask before the agent changes a server
With ssh_consent on (it is off by default), maisecrets asks before the agent changes a server. An agent with your SSH keys can run any command on any host it reaches, also when no secret is in the command. A permission rule on “ssh” matches only the start of a command, so “cd x && ssh …” passes it.
The problem is the mode you work in. In bypass permissions mode, and in auto mode, Claude Code asks you nothing: an agent with your SSH keys runs every command on every host it reaches. You want it to read logs and status on its own, but not to restart a service or change a file without you. ssh_consent is a check of its own, in a maisecrets hook: it lets the reads run and asks before each write.
cd … && or timeout.What happens with ssh_consent on
ssh web1 uptime runs: a read
ssh web1 'cat /etc/app.conf' asks: the content of a file
cd deploy && ssh web1 'systemctl restart app' asks: a write
- A read runs. A remote command from a short list that prints only metadata about the host:
uptime,df -h,free,uname,ls,systemctl is-active. - Every write asks. Any other command, a login shell,
sudo, a copy, a tunnel. A yes allows that one command. maisecrets reads the ssh call also behindcd … &&,timeout,nohup,envandsudo, and forscp,sftp,rsyncandmoshtoo. - A host you trust for a while: type
maisecrets: allow ssh web1as your own prompt. Writes to that host run without a question for 8 hours, in that session. - Your own lab hosts can be autonomous:
/maisecrets:settings ssh_autonomous_hosts add ops1. Writes there never ask; every other host still asks.
Turn it on with /maisecrets:settings ssh_consent on as your own prompt. It is off by default, and a machine policy can set it.
Three lists of hosts
| List | What it allows | How long | How you set it |
|---|---|---|---|
| A host you trust for a while | writes to that host run without a question | 8 hours, in that session | maisecrets: allow ssh web1, typed by you |
| Autonomous hosts | writes never ask, in every session | until you remove the host | /maisecrets:settings ssh_autonomous_hosts add ops1 |
| The sandbox allowlist of Claude Code | the only hosts that may get a value on stdin | until you change it | sandbox.network.allowedDomains in ~/.claude/settings.json |
Every other host asks for each write. A host is as the ssh call writes it: root@web1 and web1:2222 are other hosts than web1. A group in ssh_host_groups names several hosts at once.
A value for a server goes on stdin
Sometimes the agent must search a log on your server for a value that it knows only as a placeholder. maisecrets lets the value go to your own servers on one condition: the command runs in the Bash sandbox of Claude Code, where the proxy admits only the hosts you allow. Then the value goes on stdin, and the command text holds only the placeholder:
printf '%s' ⟦EMAIL_c3⟧ | ssh aux01 'grep -F -f - /var/log/mail.log'
maisecrets refuses a value inside the arguments of ssh, a remote side that runs its program from stdin (bash, sh -s, | sh), and an own proxy or jump host.
Limits
- The list of reads is a heuristic, not a guarantee. A form that maisecrets cannot read, such as a host built at run time (
ssh $HOST) or an option that sends the connection elsewhere, asks every time. - The sandbox limits where the connection goes, not what the remote side does with a value.
- In Codex, the route with a value refuses: Codex has no host allowlist.
All forms and options: README.