Skip to content

Latest commit

 

History

92 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AgentSSH

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.

Quick Start

# 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 rules

That is the whole loop: you own hosts, policy, and the audit trail through agentssh tui; the agent only ever calls agentssh.

Task approvals and executable plans

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: 2h

The 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 JSON
version: 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/app

The 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.yaml

The 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.

The console (agentssh tui)

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).

  • D Discover — opens an overlay of hosts you can likely already reach, gathered from ~/.ssh/config and ~/.ssh/known_hosts, annotated with key/known-hosts/in-inventory status. space selects, p probes (a real connection test), enter/i imports the connectable, not-yet-known ones into your inventory. esc/q closes.
  • a Add — a form for a new host: name / addr / user / port / tags / ssh_config_alias / identity_file / password. identity_file points at a private key for that host. password is optional and masked; it is stored encrypted, never in inventory.yaml. Setting a password in the TUI requires AGENTSSH_MASTER_PASSWORD to be set (bubbletea owns the terminal, so there is no separate master prompt) — otherwise use agentssh secret set.
  • t Test — runs a real connectivity check against the selected host, updates its detected OS metadata, and shows OK or an actionable hint (missing credentials, unknown host key, unreachable, …).
  • Host detail (enter/i) — a three-pane screen for the selected host: 1 Info · 2 Sessions · 3 Policy (switch with tab or 1–3; esc returns to the grid).
  • Info pane — the field list doubles as the editor: j/k move a field cursor, enter edits the focused field in place and saves on enter (esc cancels) — no separate form. Editable rows: addr / user / port / alias / auth / tags. The auth row is a two-mode edit — key (a private-key path; empty falls back to the default ~/.ssh keys the client already scans) or password (masked, stored encrypted; needs AGENTSSH_MASTER_PASSWORD). t tests connectivity; d/x delete the host (with confirm).
  • Policy tab — Global and each reusable rule group render as cards with rule counts. enter opens the selected card; a/e/r add, edit, and remove rules; n creates a group; d deletes 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/i on a host, then 3 for Policy. The pane shows one unified, borderless rule list with host-tier rows first and global rows below as read-only context. a adds a manual host rule (allow|deny [priority] <regex>), p stamps a rule group, j/k selects rows, r removes editable host rows, R removes all rows stamped from the selected group, and x clears 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.

Install

Prebuilt binary (recommended — no Go needed)

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/agentssh

Verify: agentssh --version. (Bump v0.13.0 for a different release; checksums are in SHA256SUMS.txt on the Releases page.)

From source (needs Go matching the go.mod directive)

go install github.com/Praeviso/AgentSSH/cmd/agentssh@latest   # into $GOBIN
go build -o agentssh ./cmd/agentssh                           # single binary from a checkout

Put the binary on the local operator machine where SSH already works.

Configure

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+'

Credentials

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 in inventory.yaml).
  • Password — stored encrypted in ~/.agentssh/secrets.enc (age, scrypt passphrase), never in inventory.yaml and 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-1

Security note: with AGENTSSH_MASTER_PASSWORD in 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.

What the agent calls

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.

CLI for humans

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 task

inventory edit / policy edit are still placeholders for opening the raw YAML. Use inventory add/update/rm, policy rule ..., and policy host ... for structured CRUD.

Transport

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.

Output filtering

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.

Skills

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.md
  • docs/architecture/overview.md
  • docs/architecture/ssh-auth-onboarding.md
  • docs/DESIGN.md
  • docs/plans/mvp.md

About

SSH Gateway for AI Agents

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages