Binions can run a fixed set of approved commands on another host over SSH. Sometimes an automation needs to do something on a machine that has no API — restart a service, read a disk-usage figure, trigger a deploy script. Binions does this through an allow-list of named commands: an operator registers each command up front, and a playbook can only run it by name. A playbook never sends a shell string, so this is deliberately not a remote shell.
Not a remote shell — by design. The set of commands that can run is closed and lives entirely in operator-controlled config. A playbook references a command by its name; it cannot compose, modify, or inject one. A malformed or hostile input has nothing to attack — there is no command text coming from the playbook to tamper with.
| What it does | Runs a pre-approved command on a remote host over SSH and returns its output |
| Playbook verb | webhook.send with protocol: ssh — no new vocabulary |
| What a playbook passes | Only a command_ref — the name of an allow-listed command, never the command itself |
| Where commands are defined | An operator-managed ssh-commands.toml allow-list (host, user, the exact command, auth) |
| Authentication | SSH password (from a file) or a private key — never inline in a playbook |
| Default | Off — with no allow-list file, the SSH provider does nothing |
Remote command execution is powerful and easy to get wrong, so Binions makes the safe shape the only shape. The operator writes an ssh-commands.toml on the daemon’s host. Each entry fixes everything about one command — which host, which user, and the exact command line — under a reference name:
# ssh-commands.toml (operator-managed; referenced from webhookcaller config)
[[commands]]
name = "service-health" # the name playbooks reference
host = "10.0.0.30"
user = "ops"
command = "systemctl is-active nginx" # fixed here; playbooks cannot change it
key_file = "/opt/binions/webhookcaller-service/secrets/ops_ed25519"
[[commands]]
name = "disk-usage"
host = "10.0.0.30"
user = "ops"
command = "df -h /"
password_file = "/opt/binions/webhookcaller-service/secrets/ops.pass"
Each command authenticates with exactly one of a key file or a password file, both kept on disk with restrictive permissions — credentials never appear in a playbook. With no ssh-commands.toml configured, the SSH provider is simply off.
A playbook calls webhook.send with protocol: ssh and a single field: the command_ref naming an allow-listed command. That is all it can supply.
name: check-service-health
on:
event: Fact.Http.Received
filter: { route.eq: health }
steps:
- run: webhook.send
id: probe
with:
protocol: ssh
ssh:
command_ref: service-health
The command runs on the configured host, and its standard output comes back on Fact.Webhook.Delivered — a later step reads it with ${steps.probe.response}. If the command exits non-zero, the call is reported as a failure with the exit code and error output, so your playbook can branch on it. If a playbook names a command_ref that is not in the allow-list, the call is rejected outright and nothing is run.
Keep commands small and specific. Prefer one allow-list entry per task (
restart-nginx,tail-app-log) over a single broad one. Narrow, named commands are easier to review and keep the blast radius of any one entry tiny.
| Event | Meaning |
|---|---|
Fact.Webhook.Delivered | The command ran and exited zero. Carries its standard output. |
Fact.Webhook.Failed | The command exited non-zero, the host was unreachable, authentication failed, or the command_ref was not allow-listed. The reason is included. |