AgentSSH is a local, single-binary SSH gateway for AI agents. It keeps SSH credentials and policy enforcement on the human-controlled machine, exposes only a constrained CLI to agents, and records every operation in a tamper-evident audit log.
Two principals, one binary:
- You (the operator) drive everything from one full-screen console —
agentssh tui— to onboard hosts, register credentials, test connectivity, tune policy, and review the audit trail. - The agent uses
agentssh hosts,run,plan run/resume, and read-only status commands. It never sees addresses, keys, or passwords — those stay in your ssh-agent,~/.ssh/, and an encrypted local store.
AgentSSH uses standard SSH from the local machine (its built-in Go SSH client by default) and needs no agent or daemon on remote hosts.
# 1. Install — static binary, no Go required (see "Install" for macOS / arm64).
curl -fsSL https://github.com/Praeviso/AgentSSH/releases/download/v0.13.0/agentssh_v0.13.0_linux_amd64.tar.gz \
| sudo tar xz --strip-components=1 -C /usr/local/bin agentssh_v0.12.0_linux_amd64/agentssh
# 2. Open the console — this is your main entry point:
agentssh tui
# On first run it creates ~/.agentssh/ with a starter inventory.yaml and a
# policy.yaml scaffold. Out of the box every command is denied until you add
# allow rules.
# In the Hosts tab:
# D discover the SSH hosts you can already reach (from ~/.ssh/config + known_hosts),
# select with space, p to probe, enter to import
# a add a host by hand (addr/user, optional identity_file, optional password)
# t test connectivity to the selected host
# enter open a host's detail screen — its Info pane edits fields inline (incl. key/password auth)
# Switch entry tabs with 1/2 or tab: Hosts · Policy.
# 3. Add an explicit allow rule before running anything:
agentssh policy rule add readonly --cmd-regex '^(systemctl status|journalctl|uptime)\b' --action allow --priority 10
# Optional: add higher-priority deny rules for commands that must never run.
agentssh policy rule add catastrophic --cmd-regex '\b(rm\s+-rf|mkfs|dd|shutdown|reboot|init\s+0|userdel)' --action deny --priority 100
# 4. The agent calls agentssh — every command is policy-checked and audited:
agentssh hosts # discover targets (no credentials shown)
export AGENTSSH_SESSION=$(agentssh session new) # one session per task -> grouped in audit
agentssh run web-1 -- systemctl status nginx # allowed -> executed over SSH
agentssh run web-1 -- rm -rf / # denied by policy -> exit 6, never runs
# 5. Review everything back in the console:
agentssh tui # Hosts tab for inventory · Policy tab for global/group rulesThat is the whole loop: you own hosts, policy, and the audit trail through agentssh tui; the agent only ever calls agentssh.
AgentSSH can approve a bounded family of operations for one host and session,
with a default two-hour lifetime. An operator chooses --task (or t in the
Approvals tab) to review and approve service diagnostics/maintenance or a fixed
Compose project's operations. Existing --once, --session, and --host scopes
retain their meaning; unsupported scripts and stdin remain exact.
approval:
enabled: true
task_ttl: 2hThe agent can run a complete saved plan and continue it after approval:
agentssh session new
agentssh plan run web-1 --session <session_id> --file deploy.yaml --wait-approval 30s --json
agentssh plan resume <execution_id> --wait-approval 30s --json
agentssh plan execution <execution_id> # saved progress as JSON
agentssh plan execution <execution_id> --follow --after-seq <n> --timeout 30s
agentssh session grants <session_id> # permissions and expiry as JSONversion: 1
commands:
- argv: [docker, compose, -f, /opt/app/compose.yaml, build, app]
cwd: /opt/app
- argv: [docker, compose, -f, /opt/app/compose.yaml, up, -d, app]
cwd: /opt/appThe TUI groups plans by default: e expands their commands, enter reviews
one plan, and t previews task permissions and exact fallback members. Human
CLI equivalents are approval grant <id> --task and plan grant <id> --task;
operator authentication still applies. session end <id> revokes session and
task grants. Expiry/revocation prevents subsequent commands; it does not undo or
forcibly terminate commands already running.
run --wait-approval 30s can wait and execute an unchanged single request too.
run --argv --cwd /opt/app -- <arguments...> preserves argument boundaries and
sets the remote working directory. policy test --host <host> --session <id>
now checks current grants and stdin identities, using the same authorization
path as execution. Preflight is optional and never substitutes for runtime checks.
Saved plans stop on failure and resume only unstarted steps. Completed steps are
never replayed; a running execution should be observed with plan execution <px_...> --follow --timeout 30s, and an unknown execution must be inspected
before creating a new plan. A failed step also requires effect inspection. verify steps may use
on_failure: continue for independent checks, but the overall execution still
finishes failed when any continued verification fails.
Reviewable deployment plans can be generated before submission:
agentssh plan template compose \
--cwd /opt/app \
--service web \
--compose-file compose.yaml \
--compose-file compose.production.yaml \
--revision REV \
--archive ./release.tar \
--output deploy.yamlThe template command only writes ordinary version: 1 plan YAML; it does not
submit, approve, or execute anything. It validates the local tar/tar.gz archive,
rejects unsafe tar member paths, leading/trailing whitespace, control characters,
unsupported tar types, and the reserved .agentssh-deploy/ subtree, creates a
validated archive copy beside the YAML, preserves every --compose-file in order
by resolving it against --cwd, and fails if --output or the archive copy
already exists. The generated plan uploads that validated copy, asserts its
SHA-256 before extraction, writes existing and missing member lists, rejects
existing member paths and parents that are symlinks before backup and before
extraction, backs up only existing readable regular archive member files with a
NUL-delimited verbatim tar list, records the backup contents, captures current
Compose image/status evidence, extracts the archive, builds and starts only the
selected service, then runs bounded verification/evidence steps. The Compose
status assertion requires every selected service container to be running and, if
a Docker healthcheck exists, healthy. An empty backup can be valid for first
deploys but is not a rollback by itself. It never embeds project-specific
credentials or automatic rollback.
Input-file hashes remain pinned to the original snapshot. plan run --save-payloads can retain stdin payload bytes for resume; retained payloads
are sensitive local state and remain removable only when no active execution or
plan pin references them. Without saved payloads, resume rereads the original
absolute local paths and refuses changed bytes. Arbitrary shell programs,
pipelines, changed Compose projects, destructive options, and changed stdin do
not inherit a task grant.
By default, configuration and runtime state both live under AGENTSSH_HOME
(~/.agentssh). Set AGENTSSH_STATE_DIR to move runtime state only: audit,
pending approvals, responses, session grants, plans, executions, events, and
payloads. Inventory, policy, secrets, and the operator verifier stay under
AGENTSSH_HOME. Use the same AGENTSSH_STATE_DIR for operator TUI/CLI and
agent commands; AgentSSH does not migrate old state automatically. agentssh diagnostics --json reports path existence and local filesystem writability
without creating directories, but it cannot prove access through an outer
sandbox, container mount, or remote filesystem policy.
See the operating skill for the full workflow and the reviewable execution guide for plan states, payload retention, and deployment templates.
agentssh tui is the primary operator interface — one full-screen app with top-level Hosts and Policy tabs (switch with 1/2 or tab, quit with q):
| Tab | What you do |
|---|---|
| Hosts | onboard, inspect, edit, test, and remove hosts; manage credentials |
| Policy | manage the Global rule list and reusable rule groups as cards; open a card to add/edit/remove rules |
Hosts grid keys — ↑↓←→/hjkl move · / filter · a add · D discover · t test · enter/i open · r reload. The grid is a pure navigator; per-host edit and delete live on the host's detail screen (open it with enter/i).
DDiscover — opens an overlay of hosts you can likely already reach, gathered from~/.ssh/configand~/.ssh/known_hosts, annotated with key/known-hosts/in-inventory status.spaceselects,pprobes (a real connection test),enter/iimports the connectable, not-yet-known ones into your inventory.esc/qcloses.aAdd — a form for a new host:name / addr / user / port / tags / ssh_config_alias / identity_file / password.identity_filepoints at a private key for that host.passwordis optional and masked; it is stored encrypted, never ininventory.yaml. Setting a password in the TUI requiresAGENTSSH_MASTER_PASSWORDto be set (bubbletea owns the terminal, so there is no separate master prompt) — otherwise useagentssh secret set.tTest — runs a real connectivity check against the selected host, updates its detected OS metadata, and showsOKor an actionable hint (missing credentials, unknown host key, unreachable, …).- Host detail (
enter/i) — a three-pane screen for the selected host:1Info ·2Sessions ·3Policy (switch withtabor1–3;escreturns to the grid). - Info pane — the field list doubles as the editor:
j/kmove a field cursor,enteredits the focused field in place and saves onenter(esccancels) — no separate form. Editable rows:addr / user / port / alias / auth / tags. Theauthrow is a two-mode edit — key (a private-key path; empty falls back to the default~/.sshkeys the client already scans) or password (masked, stored encrypted; needsAGENTSSH_MASTER_PASSWORD).ttests connectivity;d/xdelete the host (with confirm). - Policy tab — Global and each reusable rule group render as cards with rule counts.
enteropens the selected card;a/e/radd, edit, and remove rules;ncreates a group;ddeletes a group. Rule groups are presets: stamping one onto a host copies its current rules and records the group name as provenance. - Host detail Policy pane — press
enter/ion a host, then3for Policy. The pane shows one unified, borderless rule list with host-tier rows first and global rows below as read-only context.aadds a manual host rule (allow|deny [priority] <regex>),pstamps a rule group,j/kselects rows,rremoves editable host rows,Rremoves all rows stamped from the selected group, andxclears that host's rules.
The remote side is always your responsibility — AgentSSH never touches a server's authorized_keys; it only connects with the credentials you give it and tells you what to fix when a connection fails.
Static binaries (CGO_ENABLED=0, no runtime deps). Pick your platform; each is one command that drops agentssh into /usr/local/bin:
# Linux x86_64
curl -fsSL https://github.com/Praeviso/AgentSSH/releases/download/v0.13.0/agentssh_v0.13.0_linux_amd64.tar.gz \
| sudo tar xz --strip-components=1 -C /usr/local/bin agentssh_v0.12.0_linux_amd64/agentssh
# Linux arm64
curl -fsSL https://github.com/Praeviso/AgentSSH/releases/download/v0.13.0/agentssh_v0.13.0_linux_arm64.tar.gz \
| sudo tar xz --strip-components=1 -C /usr/local/bin agentssh_v0.12.0_linux_arm64/agentssh
# macOS Apple Silicon (arm64)
curl -fsSL https://github.com/Praeviso/AgentSSH/releases/download/v0.13.0/agentssh_v0.13.0_darwin_arm64.tar.gz \
| sudo tar xz --strip-components=1 -C /usr/local/bin agentssh_v0.12.0_darwin_arm64/agentssh
# macOS Intel (amd64)
curl -fsSL https://github.com/Praeviso/AgentSSH/releases/download/v0.13.0/agentssh_v0.13.0_darwin_amd64.tar.gz \
| sudo tar xz --strip-components=1 -C /usr/local/bin agentssh_v0.12.0_darwin_amd64/agentsshVerify: agentssh --version. (Bump v0.13.0 for a different release; checksums are in SHA256SUMS.txt on the Releases page.)
go install github.com/Praeviso/AgentSSH/cmd/agentssh@latest # into $GOBIN
go build -o agentssh ./cmd/agentssh # single binary from a checkoutPut the binary on the local operator machine where SSH already works.
AgentSSH reads ~/.agentssh/ by default. Set AGENTSSH_HOME to use another directory. The first run of agentssh tui creates the directory and seeds inventory.yaml + policy.yaml for you (existing files are never overwritten), so you can skip the manual setup below and just edit what it wrote.
~/.agentssh/
inventory.yaml # hosts (seeded on first `tui`; managed via the TUI or `agentssh inventory`)
policy.yaml # allow/deny rules + output filtering (seeded on first `tui`)
secrets.enc # encrypted SSH passwords (created on first `secret set`)
audit.log # created automatically
session # created automatically
Example inventory.yaml (you normally never hand-edit this — the TUI does):
version: 1
transport: native # default: built-in Go SSH client; "ssh" shells out to system ssh
host_key_policy: strict # or "accept-new" for trust-on-first-use
hosts:
web-1:
addr: 10.0.0.11
user: deploy
identity_file: ~/.ssh/web-1 # optional per-host private key
tags: [web, prod]
groups:
prod: { tags: [prod] }Example policy.yaml:
version: 1
rules:
- name: readonly
priority: 10
match: { cmd_regex: '^(systemctl status|journalctl|uptime)\b' }
action: allow
- name: catastrophic
priority: 100
match: { cmd_regex: '\b(rm\s+-rf|mkfs|dd|shutdown|reboot|init\s+0|userdel)' }
action: deny
host_overrides:
host:web-1:
rules:
- priority: 20
match: { cmd_regex: '^systemctl status\b' }
action: allow
rule_groups:
readonly:
rules:
- priority: 10
match: { cmd_regex: '^(uptime|whoami)\b' }
action: allow
output:
max_bytes: 16384
redact:
- '(?i)(password|passwd|secret|token)\s*[=:]\s*\S+'AgentSSH connects with public-key auth by default and never stores keys of its own — it reuses your ssh-agent, ~/.ssh/config, and ~/.ssh/id_*. Per host you can also:
identity_file— point a host at a specific private key (a path, not a secret; lives ininventory.yaml).- Password — stored encrypted in
~/.agentssh/secrets.enc(age, scrypt passphrase), never ininventory.yamland never in the audit log. Public key is always tried before a password.
The encrypted store is unlocked with a master password from AGENTSSH_MASTER_PASSWORD, or a no-echo TTY prompt for operator commands. For agent-driven run, the master is read from the env only (no prompt); if it is unset, password auth is simply skipped and key auth is used. Register passwords with:
agentssh secret set web-1 # prompts (no echo) and encrypts
agentssh secret ls # lists host names only — never values
agentssh secret rm web-1Security note: with
AGENTSSH_MASTER_PASSWORDin an unattended agent's environment, that process can decrypt every stored password. Prefer key auth for agent-driven hosts; reserve passwords for hosts that truly need them.
These are the only commands an agent needs. They go through inventory resolution, policy, output filtering, and audit:
agentssh hosts # list targets (name + tags only; no credentials)
agentssh hosts --json
export AGENTSSH_SESSION=$(agentssh session new) # declare one session per task (required by run)
agentssh run web-1 -- systemctl status nginx
agentssh run web-1 --json -- uptime
agentssh status <req_id>run requires a declared session — --session <id> or $AGENTSSH_SESSION — so each task maps to one auditable session; without one it exits 2. Mint a fresh id per task with agentssh session new.
On a connection failure, run prints a credential-free hint and exits 9.
Everything the console does is also scriptable. Manage hosts and credentials:
agentssh inventory discover [--probe] [--json] [--import] # find reachable hosts; --probe really connects
agentssh inventory add web-1 --addr 10.0.0.11 --user deploy --identity-file ~/.ssh/web-1 [--password]
agentssh inventory add # interactive form (TUI)
agentssh inventory update web-1 --addr 10.0.0.12 --tags web,prod
agentssh inventory rm web-1 # writes a tamper-evident delete audit record
agentssh inventory ls
agentssh inventory test web-1 # connectivity check + hint
agentssh secret set|ls|rm <host>Inspect and review:
agentssh policy show
agentssh policy rule ls
agentssh policy rule add readonly --cmd-regex '^(systemctl status|journalctl|uptime)\b' --action allow --priority 10
agentssh policy rule add no-reboot --cmd-regex '^(sudo )?reboot\b' --action deny --priority 100
agentssh policy rule update no-reboot --cmd-regex '^(sudo )?(reboot|shutdown)\b' --priority 100
agentssh policy rule rm no-reboot
agentssh policy group ls
agentssh policy group add readonly
agentssh policy group rule add readonly --cmd-regex '^(uptime|whoami)\b' --action allow --priority 10
agentssh policy group rule ls readonly
agentssh policy group rule rm readonly 0
agentssh policy group rm readonly
agentssh policy host ls
agentssh policy host rule add web-1 --cmd-regex '^systemctl status\b' --action allow --priority 20
agentssh policy host rule add web-1 --from-group readonly
agentssh policy host rule ls web-1
agentssh policy host rule rm web-1 0
agentssh policy host group rm web-1 readonly
agentssh policy host rm web-1
agentssh policy test --host web-1 'rm -rf /'
agentssh policy test --host web-1 --json -- 'uptime' 'systemctl restart nginx' # batch pre-check, one call
agentssh audit ls
agentssh audit show <req_id>
agentssh audit verify # confirm the tamper-evident hash chain is intact
agentssh audit repair --truncate-broken # remove a broken audit tail after backing it up
agentssh session ls
agentssh session new # mint a fresh session id for a taskinventory edit / policy edit are still placeholders for opening the raw YAML. Use inventory add/update/rm, policy rule ..., and policy host ... for structured CRUD.
By default AgentSSH uses its built-in Go SSH client (no system ssh binary required). It still reuses ssh-agent, key files, ~/.ssh/config aliases, and ProxyJump, and verifies host keys against ~/.ssh/known_hosts with strict checking — a never-seen host must already be in known_hosts, or set host_key_policy: accept-new for trust-on-first-use. Set transport: ssh (or AGENTSSH_TRANSPORT=ssh) to shell out to the system ssh client instead.
Before stdout/stderr return to the agent, AgentSSH applies policy.output.redact regex replacements and policy.output.max_bytes truncation independently to stdout and stderr. Redacted text becomes «REDACTED». Audit records store the SHA-256 of the filtered bytes that crossed the trust boundary, plus redactions and output_truncated metadata. Raw unfiltered output is not stored.
An Anthropic Agent Skill-style operating manual lives under skills/:
skills/agentssh-usage/SKILL.md— best practices and command reference for driving servers through AgentSSH: the trust boundary, one-session-per-task discipline, policy, bounded output, and audit review.
This is procedural knowledge for agents, not an RPC tool: it teaches the agent how to use agentssh well for whatever the operator asks, while the CLI enforces policy and audit. It is a soft control — it shapes what the agent attempts, but the CLI, not the manual, is the security boundary.
See the project documents for the product and implementation contract:
docs/prds/agentssh.mddocs/architecture/overview.mddocs/architecture/ssh-auth-onboarding.mddocs/DESIGN.mddocs/plans/mvp.md