Skip to main content

Add an SSH connection

Adding a connection is three things: pin the host, seal the key, and declare the capabilities. The perma CLI does all of it in one command; the REST API does it in a few calls.

1. Pin the host's fingerprint

The connection refuses to connect unless the server presents this exact key, so grab it first:

FP=$(ssh-keyscan -t ed25519 db1.example.com | ssh-keygen -lf - | awk '{print $2}')
echo "$FP" # SHA256:9Ije0/h/UFCjrsPr+a6o/dPtQhd0p9AbIQOW10Q7wJo

2. Create the connection

One command creates the connection, seals the key, and grants it to an agent:

perma connection add-ssh db1 \
--host db1.example.com --user deploy \
--host-key-sha256 "$FP" \
--key-file ~/.ssh/db1_ed25519 \
--sftp-root /var/data \
--exec 'uptime:uptime' \
--exec 'restart:systemctl,restart,{service}' \
--agent codex-cli
  • --exec 'action:program,arg,arg' defines one exec capability. A {name} argument becomes an allow-listed placeholder the agent may fill.
  • --sftp-root exposes sftp.get / sftp.put / sftp.list, all scoped under that directory.
  • Add --require-approval to gate the exec commands on a human. (Writes and sftp.put are higher-risk and escalate automatically regardless.)
  • For a local or dev host with no fingerprint, --insecure skips verification. Do not use it against a host reached over an untrusted network.

What you get

The connection now exposes these capabilities (namespaced under its id):

CapabilityWhat it does
db1.uptimeruns uptime on the host
db1.restartruns systemctl restart <service> (approval-gated)
db1.sftp.listlists a directory under /var/data
db1.sftp.getreads a file under /var/data
db1.sftp.putwrites a file under /var/data (higher-risk)

Use it from an agent

Point an MCP client (Claude, ChatGPT, Cursor) at your gateway and the capabilities appear as tools named db1.uptime, db1.sftp.get, and so on. The agent calls them like any other tool:

  • db1.uptime with {} returns the real uptime line from the host.
  • db1.sftp.get with { "path": "reports/q3.csv" } returns the file's bytes (base64), scoped under the root.
  • db1.restart with { "service": "nginx" } returns awaiting approval and prompts you on your device; it runs only once you approve.

The key never leaves the gateway, and every call lands in the signed audit log.

Notes

  • The key you seal must be unencrypted (this build authenticates with no passphrase). Generate a dedicated one with ssh-keygen -t ed25519 -f ~/.ssh/db1_ed25519 -N '' and authorise it on the host.
  • Password auth is supported with --password-env VAR (the value is read from that environment variable and sealed, never placed on a command line).
  • Check what is configured any time with perma connection list.