Automation Engine
ZestSSH includes a full automation engine that lets external apps trigger SSH commands, snippets, batch operations, and workflows without opening a terminal session. The engine connects to your saved servers headlessly, runs the command, captures stdout/stderr, and returns the result — all in the background.
Automation is a Pro feature. All automation actions require a valid API key.
How It Works
Section titled “How It Works”The automation engine (AutomationService) establishes a temporary SSH connection to the target server, executes a command via SSHClient.execute() (no PTY allocated), collects stdout and stderr, waits for the exit code, and disconnects. Each execution is isolated: a fresh connection is opened and closed for every action.
Connection credentials are resolved from ZestSSH’s saved connections database. The engine looks up the connection by exact ID first, then by exact label (case-insensitive), and finally by partial label match. Identity credentials (password, private key, passphrase) are loaded from FlutterSecureStorage at execution time.
Host key verification follows the same known-hosts database as interactive sessions. New hosts are auto-trusted during automation (there is no UI to prompt), but changed host keys are rejected as a potential MITM attack.
Automation Actions
Section titled “Automation Actions”Execute
Section titled “Execute”Run a raw shell command on a saved connection.
- Deep link:
zestssh://execute?connection=prod-web&command=uptime&key=API_KEY - Intent:
com.affluentlabs.zestssh.EXECUTEwith extrasconnection_id,command,key - Parameters:
connection(label or ID),command,timeout(seconds, default 30),key
Snippet
Section titled “Snippet”Run a saved snippet by name. Snippet variables can be passed as var_ prefixed parameters.
- Deep link:
zestssh://snippet?connection=prod-web&name=Disk+Usage&key=API_KEY - Intent:
com.affluentlabs.zestssh.SNIPPETwith extrasconnection_id,snippet_name,key - Variables: pass
var_HOST=example.comto set theHOSTvariable in the snippet template
The engine looks up the snippet by label (case-insensitive). If variables are provided, {{key}} placeholders in the snippet command are replaced with the corresponding values.
Execute the same command across multiple connections simultaneously or sequentially.
- Deep link:
zestssh://batch?connections=web1,web2,db1&command=uptime¶llel=true&key=API_KEY - Parameters:
connections(comma-separated labels/IDs),command,timeout,parallel(default true),key
When parallel=true (default), all connections are executed concurrently via Future.wait. When false, they execute sequentially in list order. The combined result includes per-connection output sections separated by headers.
Workflow
Section titled “Workflow”Trigger a saved multi-step workflow by name or ID.
- Deep link:
zestssh://workflow?name=Deploy+Production&key=API_KEY - Intent:
com.affluentlabs.zestssh.WORKFLOWwith extrasworkflow_nameorworkflow_id,key
See the Workflows documentation for details on workflow structure and execution.
Connect
Section titled “Connect”Verify that a connection is reachable without running a command.
- Deep link:
zestssh://connect?host=prod-web&key=API_KEY - Intent:
com.affluentlabs.zestssh.CONNECTwith extrasconnection_id,key
Internally, the engine runs echo "ZestSSH: connected successfully" to verify the connection. The result returns success or failure.
API Key Security
Section titled “API Key Security”Every automation request must include a valid API key. Keys are 48-character alphanumeric strings generated from Random.secure(), providing approximately 285 bits of entropy.
Key validation uses constant-time SHA-256 comparison: the input key and each stored key are independently hashed with SHA-256, and the resulting byte arrays are compared using XOR with OR accumulation. This prevents timing side-channel attacks that could leak information about valid keys.
Rate limiting protects against brute-force attempts: after 5 consecutive failed validation attempts, all API key validation is locked out for 1 minute. The failed-attempt counter and lockout timestamp are persisted to FlutterSecureStorage, so restarting the app does not reset the lockout.
Expired keys are treated as invalid and count toward the failed-attempt counter.
See API Keys for full details on key management.
Output Filters
Section titled “Output Filters”Execute and batch actions support optional output filters applied in this order:
- Grep (
grepparameter): keep only lines matching a pattern (case-insensitive regex). Patterns longer than 256 characters are treated as literal string matches to prevent ReDoS. Invalid regex patterns fall back to literal matching. - Head (
headparameter): take only the first N lines. - Tail (
tailparameter): take only the last N lines.
Filters are applied after the command completes, before returning results to the caller.
Example: zestssh://execute?connection=web1&command=journalctl+-n+100&grep=error&tail=10&key=API_KEY — runs journalctl -n 100, filters to lines containing “error”, then returns only the last 10 matching lines.
Webhook Callbacks
Section titled “Webhook Callbacks”Execute and batch actions support a callback parameter with an HTTPS URL. After the command completes, ZestSSH POSTs a JSON payload to the webhook:
{ "connection": "prod-web", "command": "uptime", "stdout": "14:23:01 up 42 days...", "stderr": "", "exit_code": 0, "elapsed_ms": 1523, "timestamp": "2026-01-15T14:23:02Z"}Webhook calls are fire-and-forget: they run asynchronously and failures are logged but do not affect the automation result. Only HTTPS URLs are accepted; HTTP URLs are rejected for security.
Automation History
Section titled “Automation History”All automation runs are logged to a local database table with timestamp, action type, connection, command, stdout, stderr, exit code, success status, and elapsed time. The log is capped at 500 entries (oldest entries are trimmed automatically).
View the log from Settings > Automation > History.
Result Return
Section titled “Result Return”On Android, automation results are returned to the calling app via the intent result mechanism. The result includes:
stdout: the command output (or error message, after any filters are applied).exit_code: the process exit code (0 for success).
This allows apps like Tasker to read the command output and make decisions based on the exit code.
Security Gates
Section titled “Security Gates”Before any automation action is processed, the engine checks three gates in order:
- Pro subscription: automation requires an active Pro or Bundle entitlement. Non-Pro users receive an error.
- Automation enabled: the user can toggle automation on/off in Settings. Disabled automation returns an error.
- API key validation: the provided key must be valid, non-expired, and pass rate limiting. Invalid keys return an error.
Only after all three gates pass does the engine proceed to resolve the connection and execute the action.
Deep Link Security
Section titled “Deep Link Security”Deep link URIs are validated before processing:
- Maximum URI length: 2048 bytes.
- Maximum per-parameter length: 256 characters.
- The
zestssh://scheme is required. - Connection identifiers are checked for control characters.
- Connection-type deep links (
zestssh://connect) require explicit user confirmation via a callback before proceeding.