SSH Backend
The SSH backend runs tool subprocesses on an existing remote host over OpenSSH. It is remote execution, not a kernel sandbox. The host's existing isolation, whatever it is, is what your agent's tools get. Use it to choose where code runs (a build server, a GPU box, a customer staging VM), not to contain untrusted code.
If you need isolation, use Bubblewrap or Docker Sandbox instead. For the cross-backend config reference, see Runtime Sandbox.
Available since v2026.5.1.
Quick Start
apiVersion: initrunner/v1
kind: Agent
metadata:
name: remote-build-agent
spec:
role: You are a build assistant that runs commands on the build host.
model:
provider: openai
name: gpt-5-mini
tools:
- type: shell
security:
sandbox:
backend: ssh
ssh:
host: my-build-box # alias from ~/.ssh/config, or user@hostname
remote_cwd: /srv/work # optional working directory on the remote hostThat is the minimum. The host alias is resolved through your existing ~/.ssh/config and ssh-agent. No keys live in InitRunner config.
# Sanity-check the connection out of band
ssh -o BatchMode=yes my-build-box true && echo OKConfiguration reference
All SSH-specific fields live under security.sandbox.ssh.
| Field | Type | Default | Description |
|---|---|---|---|
host | str | (required) | Host alias from ~/.ssh/config or user@hostname. |
remote_cwd | str | null | null | Working directory for every remote command. If unset, the SSH login directory is used. |
identity_file | str | null | null | Override IdentityFile. Prefer setting this in ~/.ssh/config instead. |
config_file | str | null | null | Override ~/.ssh/config path (rarely needed). |
connect_timeout | int | 10 | Seconds for the initial connection. |
control_persist | str | "60s" | How long the multiplexed connection stays warm between calls. Any OpenSSH duration string. |
How it works
Every tool call shells out to ssh -- <host> <remote-command> with ControlMaster=auto, so the second and subsequent calls reuse a warm connection. Per-call latency on a fresh socket is roughly 150 to 500 ms; reused, it is in the tens of ms.
The remote command is constructed as:
[cd <remote_cwd> && ] [env VAR=val ...] <argv...>argv and the env mapping that the tool passed in are shell-quoted with shlex.quote. Sensitive env keys (anything matching the same prefix and suffix list other backends use, such as *_KEY, *_TOKEN, OPENAI_API_KEY, AWS_*) are stripped from the remote env before it leaves the local machine.
Authentication
InitRunner does not handle SSH auth. The local ssh process inherits the parent environment unchanged, including SSH_AUTH_SOCK and SSH_AGENT_PID, so:
ssh-agentandssh-addwork as you would expect.~/.ssh/configHostblocks are honored (User,Port,IdentityFile,ProxyJump,ForwardAgent, and so on).- Hardware keys, FIDO/U2F, and OpenSSH certificate auth all work because they work in your shell.
If you set identity_file in YAML, it is threaded through as ssh -i <path>.
What is NOT supported in v1
These fields and concepts do not apply to a real remote filesystem and are explicitly rejected at config load:
| Field | Reason |
|---|---|
bind_mounts | No shared filesystem. v1.1 will add SCP staging. |
allowed_read_paths | Same. |
allowed_write_paths | Same. |
network: bridge | SSH cannot enforce remote network policy. Use none (informational) or host. |
These fields are accepted but inert under SSH (kept so backend: ssh can be added to an existing role without touching unrelated config):
read_only_rootfsdoes nothing; the remote rootfs is whatever it is.memory_limitandcpu_limithave no remote enforcement in v1.docker.*is ignored.
Tools that do not work over SSH in v1
python_execstages a local file and bind-mounts it as/work/_run.py. Without SCP staging, there is no way to deliver the file. The tool fails fast with a v1.1 remediation message. Workaround: install Python on the remote host and useshellwithpython -c "...", or check a script into the remote machine ahead of time.- Anything else that uses
extra_mounts. The backend rejects non-emptyextra_mountsat runtime with a clearSandboxConfigError.
Coming in v1.1
- Stdin-piped
python_exec(no filesystem staging). - SCP-based mount staging for
extra_mounts.
Security posture
This is the bit that bites if you skim. SSH does not:
- isolate the agent's commands from the rest of the remote host's filesystem,
- enforce memory or CPU limits,
- prevent network access,
- contain a malicious or buggy tool.
Use it for trusted-but-remote execution. If your role's tools could be coaxed into running attacker-controlled commands, run those tools through bwrap or docker on a host you do not mind compromising, not via SSH on production infrastructure.
Audit
Every remote call logs a sandbox.exec event with backend=ssh host=<host> argv0=<first-token> rc=<n> duration_ms=<ms>. Identity files, full argv arguments, and command output are not logged.
Query with:
initrunner audit security-events --event-type sandbox.execTroubleshooting
ssh client not found on PATH — install OpenSSH:
apt install openssh-client # Debian/Ubuntu
brew install openssh # macOS
dnf install openssh-clients # Fedorassh probe to '<host>' returned rc=255 — usually auth or hostname. Reproduce out of band:
ssh -o BatchMode=yes -v <host> truessh-agent not running — ssh-add -l should list a key. If it says "Could not open a connection to your authentication agent," start one and add your key:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519ControlMaster socket lingering after a crash — the per-process temp directory under /tmp/initrunner-ssh-* is cleaned up on normal shutdown. If a hard kill leaves one behind, delete the directory or run ssh -O exit <host> once.
Docker Sandbox
Run shell, Python, and script tools inside disposable Docker containers with pinned images, bridge networking, and kernel-level isolation.
Sandbox Comparison
Compare InitRunner's sandbox backends and Docker runtimes across isolation classes (container, userspace kernel, microVM), threat models, and operational tradeoffs.