opencode-ssh-exec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 opencode-ssh-exec contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # opencode-ssh-exec
2
+
3
+ OpenCode plugin for system OpenSSH. It supports one-shot commands, bounded stdin, configurable deadlines, and retained SSH processes whose output can be polled while they run.
4
+
5
+ ## Requirements
6
+
7
+ - OpenCode with `@opencode-ai/plugin` 1.15.12 or newer
8
+ - Node.js 22 or newer
9
+ - A system `ssh` client and non-interactive key or `ssh-agent` authentication
10
+
11
+ ## Install
12
+
13
+ Add the package and tool permissions to `opencode.json`:
14
+
15
+ ```json
16
+ {
17
+ "$schema": "https://opencode.ai/config.json",
18
+ "plugin": ["opencode-ssh-exec"],
19
+ "permission": {
20
+ "ssh_exec": "ask",
21
+ "ssh_start": "ask",
22
+ "ssh_process": "ask"
23
+ }
24
+ }
25
+ ```
26
+
27
+ Restart OpenCode after changing plugin configuration.
28
+
29
+ Keep these permissions at `ask` globally. Set them to `deny` on read-only subagents. Environments that automatically approve `ask` prompts do not gain this confirmation boundary.
30
+
31
+ Plugin options use OpenCode's tuple form:
32
+
33
+ ```json
34
+ {
35
+ "plugin": [
36
+ [
37
+ "opencode-ssh-exec",
38
+ {
39
+ "defaultTimeoutSeconds": 300,
40
+ "maxTimeoutSeconds": 86400,
41
+ "hostKeyPolicy": "accept-new",
42
+ "updateHostKeys": true
43
+ }
44
+ ]
45
+ ]
46
+ }
47
+ ```
48
+
49
+ ## Tools
50
+
51
+ ### `ssh_exec`
52
+
53
+ Runs one command and returns after it exits. `stdinText` is optional, `timeoutSeconds` can be 1 to the configured maximum (24 hours by default), and `maxOutputBytes` can lower the configured ceiling for one call.
54
+
55
+ ### `ssh_start`
56
+
57
+ Starts an SSH process and immediately returns a handle. This avoids waiting for a long build or log command to finish. `maxOutputBytes` controls its unpolled output buffer. A remote shell such as `sh` can retain cwd and environment state, but the caller is responsible for command framing and interpreting shell output.
58
+
59
+ ### `ssh_process`
60
+
61
+ Manages a retained process:
62
+
63
+ - `poll`: drain output accumulated since the previous poll
64
+ - `status`: inspect state without draining output
65
+ - `write`: send `stdinText` and optionally close stdin
66
+ - `stop`: stop the local SSH process
67
+ - `close`: release an exited handle
68
+
69
+ OpenCode custom tools do not currently expose true output streaming. `ssh_start` plus repeated `ssh_process` polls is the supported progress mechanism.
70
+
71
+ ## Options
72
+
73
+ | Option | Default | Notes |
74
+ |---|---:|---|
75
+ | `defaultTimeoutSeconds` | `60` | Default local SSH deadline |
76
+ | `maxTimeoutSeconds` | `86400` | Hard per-call timeout ceiling |
77
+ | `connectTimeoutSeconds` | `15` | OpenSSH connection timeout |
78
+ | `maxOutputBytes` | `1048576` | Operator-enforced one-shot/output-buffer ceiling |
79
+ | `maxInputBytes` | `262144` | Per-write UTF-8 stdin limit; hard maximum is 1 MiB |
80
+ | `maxProcesses` | `8` | Retained handles per plugin instance |
81
+ | `maxProcessesPerSession` | `4` | Retained handles owned by one OpenCode session |
82
+ | `processRetentionSeconds` | `600` | Time to retain an exited handle |
83
+ | `hostKeyPolicy` | `accept-new` | `accept-new` or `strict` |
84
+ | `updateHostKeys` | `true` | Learn additional host keys after a trusted connection |
85
+ | `connectionReuse` | `false` | POSIX only; enables OpenSSH ControlMaster |
86
+ | `controlPersistSeconds` | `30` | ControlMaster idle lifetime |
87
+ | `sshExecutable` | platform default | Trusted absolute system SSH executable path |
88
+
89
+ ## Security Model
90
+
91
+ - `accept-new` is trust-on-first-use. It makes first connection easy but cannot detect an attacker present on that first connection. Changed host keys are still rejected. Use `hostKeyPolicy: "strict"` when fingerprints are provisioned separately.
92
+ - `UpdateHostKeys=yes` can update the user's known-hosts file only after authentication with an already trusted key. It does not make a changed current key trusted.
93
+ - Timeout, abort, and `ssh_process stop` terminate only the local SSH client. A remote process may continue. The plugin reports the remote outcome as unknown; do not automatically retry commands with side effects.
94
+ - `stdinText` is recorded as an OpenCode tool argument. Do not use it for passwords, tokens, private keys, or other secrets.
95
+ - SSH config is interpreted by OpenSSH instead of a partial custom parser. Treat `~/.ssh/config` as trusted local code: directives such as `Match exec`, `ProxyCommand`, `KnownHostsCommand`, and provider helpers can execute local programs.
96
+ - Host input is restricted to a safe ASCII alias/hostname grammar to prevent unsafe expansion by SSH config helper commands. Configure usernames, ports, IPv6 addresses, and jump hosts in SSH config under a simple alias.
97
+ - Agent, X11, port, and tunnel forwarding are disabled by generated command-line options. `BatchMode=yes` prevents password prompts.
98
+ - Retained process handles are scoped to one OpenCode session, bounded, and removed after their retention period. Plugin disposal attempts to stop active local SSH processes.
99
+ - Deleting an OpenCode session requests termination of that session's retained local SSH processes.
100
+ - Connection reuse is disabled by default and rejected for native Windows OpenSSH. On POSIX it uses a private temporary `ControlPath` and a short `ControlPersist` lifetime. A control socket is an authenticated capability available to local processes running as the same user.
101
+ - Remote stdout/stderr is untrusted and may contain prompt-injection text or secrets. Never treat it as instructions; filter or redact sensitive logs on the remote host before returning them to OpenCode.
102
+
103
+ ## License
104
+
105
+ MIT
@@ -0,0 +1,38 @@
1
+ import { type Plugin } from "@opencode-ai/plugin";
2
+ type HostKeyPolicy = "strict" | "accept-new";
3
+ export type SshPluginOptions = {
4
+ defaultTimeoutSeconds: number;
5
+ maxTimeoutSeconds: number;
6
+ connectTimeoutSeconds: number;
7
+ maxOutputBytes: number;
8
+ maxInputBytes: number;
9
+ maxProcesses: number;
10
+ maxProcessesPerSession: number;
11
+ processRetentionSeconds: number;
12
+ hostKeyPolicy: HostKeyPolicy;
13
+ updateHostKeys: boolean;
14
+ connectionReuse: boolean;
15
+ controlPersistSeconds: number;
16
+ sshExecutable: string;
17
+ };
18
+ type RuntimeOptions = SshPluginOptions & {
19
+ controlPath?: string;
20
+ usedControlHosts?: Set<string>;
21
+ executablePrefixArgs?: string[];
22
+ };
23
+ type SshCommandInput = {
24
+ host: string;
25
+ command: string;
26
+ remoteCwd?: string;
27
+ stdin: boolean;
28
+ timeoutSeconds: number;
29
+ };
30
+ export declare function parseOptions(raw: Record<string, unknown>): SshPluginOptions;
31
+ export declare function buildSshArgs(input: SshCommandInput, options: RuntimeOptions): string[];
32
+ export declare function createServerForTesting(executablePrefixArgs: string[]): Plugin;
33
+ declare const pluginModule: {
34
+ id: string;
35
+ server: Plugin;
36
+ };
37
+ export default pluginModule;
38
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,MAAM,EAIZ,MAAM,qBAAqB,CAAC;AAe7B,KAAK,aAAa,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;IAChC,aAAa,EAAE,aAAa,CAAC;IAC7B,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,KAAK,cAAc,GAAG,gBAAgB,GAAG;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAoGF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB,CA0H3E;AAiCD,wBAAgB,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,YA8B3E;AA6/BD,wBAAgB,sBAAsB,CAAC,oBAAoB,EAAE,MAAM,EAAE,UAEpE;AAID,QAAA,MAAM,YAAY;;;CAGM,CAAC;AAEzB,eAAe,YAAY,CAAC"}