# SSH (bash) plugin

The SSH (bash) plugin lets a Vaubaan Gateway mission use SSH (bash) without handing the agent a credential. Shell commands on a host over SSH — inspection on a read allow-list, writes priced by class (destructive, package, service, sudo), lines that cannot be classified priced as all of them.

- Version: `2.0.0` (package `@vaubaan/plugin-ssh`)
- Status: stable
- Health: not enough reports yet — [what this means](/plugins/health)
- Source: [plugins/ssh](https://github.com/vaubaan/gateway/tree/main/plugins/ssh), catalog [risk-catalog.yaml](https://github.com/vaubaan/gateway/blob/main/plugins/ssh/src/risk-catalog.yaml) version 4 of 2026-10-14
- How to connect an account: [Connect SSH (bash)](/docs/providers/ssh)
- How it is modelled: [handbook](https://github.com/vaubaan/gateway/blob/main/docs/handbook/providers/ssh.md)

One account is one host reached over SSH with a key pair generated by this gateway and a pinned host key; that host is its single resource, labelled dev, staging or prod by its owner. The one tool runs a shell command line: inspection commands are reads, everything else is a write, and destructive commands, package changes, service control and sudo are permissions of their own.

## Permissions

Each permission is a named risk of the plugin's catalog: a mission holds an explicit list of them, never "all". The severity is computed from the permission's six axes (undo, blast radius, data, cost, visible to, lasts); nothing is decided from it.

| Permission | What it allows | Severity | Why it is a risk |
| --- | --- | --- | --- |
| `ssh.exec-read` | Run a whitelisted read-only command | low | Inspection commands recognised by the read allow-list (\`journalctl\`, \`cat\`, \`ls\`, \`tail\`, \`head\`, \`grep\`, \`pgrep\`, \`ps\`, \`docker ps/inspect/logs\`, \`systemctl status\`, \`df\`, \`free\`, \`uptime\`, \`who\`, \`ss -tlnp\`, \`md5sum\`, \`stat\`) with their known read-only options, alone or piped together. No mutation. Logs and files may still contain secrets, which is why \`exec-read-secret-files\` exists next to it. |
| `ssh.exec-read-secret-files` | Read a credential-bearing file | low | The read allow-list accepts \`cat \<path\>\` without filtering the path, so \`cat /etc/shadow\`, \`cat /root/.env\` or \`stat /home/\<u\>/.ssh/id\_ed25519\` are reads. The classifier recognises credential-bearing paths however they are spelled (\`.ssh/…\`, \`.env\`, private keys, \`/etc/shadow\`, \`/root\`, raw devices), and paths it cannot see (an expansion, \`..\`, a recursive \`grep\`, \`docker inspect\`, a line it cannot classify), and emits an extra slice, so a mission must hold this permission on top of \`exec-read\` to read them. Exfiltration of host credentials ; refused by every preset but \`operator\`, where it asks first. |
| `ssh.exec-write` | Run an arbitrary shell command | medium (high on `prod`; only for `dev`, `staging`, `prod`) | Any simple command outside the read allow-list and any output redirection. File edits, builds, git operations, process launches, scripts : the program may do whatever its user can. A line the classifier cannot see through (command substitution, inline code, another host or container) needs this permission and every class permission below. On a \`dev\` or \`staging\` host the blast radius is the host ; on \`prod\` a write can shift the security posture or cause downtime, hence the override. |
| `ssh.exec-destructive` | Destructive command (recursive delete, disk, reboot) | high (only for `dev`, `staging`, `prod`) | \`rm\` with \`-r\` or \`-f\`, \`mkfs\`, \`dd\`, \`shred\`, \`wipefs\`, partitioning tools, \`reboot\` / \`shutdown\`, \`userdel\`, firewall flushes, Docker prunes. Irreversible at the host level ; emitted together with \`exec-write\`. Presets make it ask first outside \`dev\`. |
| `ssh.exec-package-install` | Install, remove or upgrade packages | medium (only for `dev`, `staging`, `prod`) | System package managers (\`apt\`, \`dpkg\`, \`yum\`, \`dnf\`, \`zypper\`, \`apk\`, \`pacman\`, \`snap\`, \`brew\`, \`flatpak\`, \`rpm\`) in a mutating mode, global \`npm\` / \`pnpm\` / \`yarn\` installs, \`pipx\`. Changes what runs on the host for every service ; supply-chain exposure. Emitted together with \`exec-write\`. |
| `ssh.exec-service-control` | Start, stop or restart a service or process | medium (only for `dev`, `staging`, `prod`) | \`systemctl\` lifecycle verbs, \`service \<x\> restart\`, Docker and Compose lifecycle, \`pm2\`, \`supervisorctl\`, \`nginx -s\`, \`kill\` / \`pkill\`. Availability impact, reversible by starting again. Emitted together with \`exec-write\`. A developer preset allows it on \`dev\` and asks first on \`staging\`. |
| `ssh.exec-sudo` | Privilege escalation (sudo, su, doas) | high (only for `dev`, `staging`, `prod`) | A non-whitelisted command run through \`sudo\`, \`su\`, \`doas\` or \`pkexec\`. Whatever the inner command does, it does as another user, usually root. Emitted together with \`exec-write\` and the inner command's classes. Inspection verbs the whitelist already admits under \`sudo\` (\`sudo journalctl\`, \`sudo systemctl status\`, \`sudo ss -tlnp\`) stay reads. |
| `ssh.uncategorized-write` | Uncategorized SSH write | medium (the catch-all: touched only by a call no other permission covers) | Catch-all for writes without a dedicated entry. Every write of the single \`ssh\_\_exec\` tool is covered by \`exec-write\` ; this entry exists for tools added later (an \`ssh\_\_sftp\_put\`, say) and is never referenced by a preset. |

## Presets

A preset pre-fills the permissions of a mission for one account, with a limit per period, a rate and whether each permission runs freely or asks the mission creator first. The person creating the mission adjusts it; the mission stores the explicit list.

### Read-only

`read-only` — Inspect a host — logs, processes, disk, services, files outside credential paths. No write.

| Permission | What it allows | Limit per period | Rate | Runs |
| --- | --- | --- | --- | --- |
| `ssh.exec-read` | Run a whitelisted read-only command | 2000 | 60 per minute | runs freely |

### Developer

`developer` — One mission per code project — reads everywhere, file and process operations on dev and staging hosts, service restarts on dev; nothing destructive, no packages, no sudo, no prod.

| Permission | What it allows | Limit per period | Rate | Runs |
| --- | --- | --- | --- | --- |
| `ssh.exec-read` | Run a whitelisted read-only command | 5000 | 120 per minute | runs freely |
| `ssh[dev].exec-write` |  | 1000 | 30 per minute | runs freely |
| `ssh[staging].exec-write` |  | 500 | 20 per minute | runs freely |
| `ssh[dev].exec-service-control` |  | 200 | 10 per minute | runs freely |
| `ssh[staging].exec-service-control` |  | 50 | 5 per minute | ask me first |

### Operator

`operator` — Everything on every labelled host; credential-file reads, destructive commands, package changes, service control and sudo asking first outside dev; every prod write asking first.

| Permission | What it allows | Limit per period | Rate | Runs |
| --- | --- | --- | --- | --- |
| `ssh.exec-read` | Run a whitelisted read-only command | 10000 | 120 per minute | runs freely |
| `ssh.exec-read-secret-files` | Read a credential-bearing file | 50 | 5 per minute | ask me first |
| `ssh[dev].exec-write` |  | 2000 | 30 per minute | runs freely |
| `ssh[staging].exec-write` |  | 1000 | 30 per minute | runs freely |
| `ssh[prod].exec-write` |  | 200 | 10 per minute | ask me first |
| `ssh[dev].exec-destructive` |  | 100 | 10 per minute | runs freely |
| `ssh[staging].exec-destructive` |  | 20 | 5 per minute | ask me first |
| `ssh[prod].exec-destructive` |  | 10 | 2 per minute | ask me first |
| `ssh[dev].exec-package-install` |  | 100 | 10 per minute | runs freely |
| `ssh[staging].exec-package-install` |  | 20 | 5 per minute | ask me first |
| `ssh[prod].exec-package-install` |  | 10 | 2 per minute | ask me first |
| `ssh[dev].exec-service-control` |  | 500 | 20 per minute | runs freely |
| `ssh[staging].exec-service-control` |  | 100 | 10 per minute | ask me first |
| `ssh[prod].exec-service-control` |  | 20 | 5 per minute | ask me first |
| `ssh[dev].exec-sudo` |  | 200 | 20 per minute | runs freely |
| `ssh[staging].exec-sudo` |  | 50 | 5 per minute | ask me first |
| `ssh[prod].exec-sudo` |  | 20 | 2 per minute | ask me first |

## Tools

| Tool | What it does |
| --- | --- |
| `ssh__exec` | Run a shell command on a host |

## Health

Health: not enough reports yet. A plugin version is deemed validated by the community with at least 200 reports and 90 % of them successful, over the last 90 days, as reported by the instances whose operators turned community telemetry on. It is a community signal, not a security guarantee.
