@judaharagao/opencode-ssh 1.0.0 → 1.2.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/README.md CHANGED
@@ -12,6 +12,11 @@ SSH access plugin for OpenCode — secure remote command execution with command
12
12
  - 📤 **File Transfer** — Upload and download files via SCP/SFTP
13
13
  - 🎯 **Command Safety Checker** — Preview command safety before execution
14
14
  - ⚙️ **Configurable Policies** — Add custom blocklist/allowlist patterns
15
+ - 🛤️ **Proxy Support** — `ProxyJump` and `ProxyCommand` from your ssh config
16
+ - 🛡️ **Host Key Verification** — optional `strict_host_key` MITM protection via `known_hosts`
17
+ - ⏳ **Rate Limiting** — per-host command rate limit and cooldown
18
+ - 🔁 **Auto-Reconnect** — dropped sessions reconnect automatically on the next command
19
+ - 🔐 **Credential Hygiene** — passwords are cleared from memory after a successful handshake
15
20
 
16
21
  ## Installation
17
22
 
@@ -48,6 +53,37 @@ Or with configuration:
48
53
  | `audit_enabled` | boolean | `true` | Enable audit logging |
49
54
  | `blocklist_extra` | string[] | `[]` | Additional regex patterns to block |
50
55
  | `allowlist` | string[] | `[]` | Additional allowed patterns (restricted mode) |
56
+ | `ssh_config_path` | string | `~/.ssh/config` | Path to the SSH config file used to resolve host aliases, defaults, proxies, and auto-connect targets |
57
+ | `auto_connect` | boolean | `false` | Connect automatically at startup to every `Host` entry in the ssh config that has a `HostName` (with retry/backoff) |
58
+ | `auto_reconnect` | boolean | `true` | Reconnect a dropped session automatically before the next `ssh.exec`/`ssh.upload`/`ssh.download` (key-authenticated sessions) |
59
+ | `strict_host_key` | boolean | `false` | Reject connections whose host key is not an exact match in `~/.ssh/known_hosts` (MITM protection) |
60
+ | `rate_limit_per_minute` | number | `120` | Max commands allowed per host per minute |
61
+ | `cooldown_seconds` | number | `0` | Minimum delay (seconds) between commands on the same host |
62
+
63
+ ## SSH Config Integration
64
+
65
+ The plugin can reuse your existing `~/.ssh/config` (or a custom path via `ssh_config_path`):
66
+
67
+ - **Host resolution** — `ssh.connect(host="myalias")` resolves the alias to its `HostName`, and automatically applies the `User`, `Port` and `IdentityFile` from the config. `username`, `port` and `auth_method`/`key_path` become optional when configured.
68
+ - **ProxyJump / ProxyCommand** — `ProxyJump` and `ProxyCommand` directives in the ssh config are honored, so connections route through bastion/jump hosts transparently.
69
+ - **Auto-connect** — with `"auto_connect": true`, the plugin connects at startup to every `Host` entry that has a `HostName`, using the configured key (or `~/.ssh/id_rsa`). Unreachable hosts are retried with backoff and skipped without blocking startup.
70
+ - **Missing file alert** — if `ssh_config_path` points to a file that does not exist, the plugin warns on connect instead of silently ignoring the setting.
71
+
72
+ Example ssh config:
73
+
74
+ ```
75
+ Host prod-web
76
+ HostName 10.0.0.10
77
+ User deploy
78
+ Port 2222
79
+ IdentityFile ~/.ssh/prod_key
80
+
81
+ Host prod-db
82
+ HostName 10.0.0.50
83
+ ProxyJump jumpuser@bastion:2200
84
+ ```
85
+
86
+ With `ssh.connect(host="prod-web")` the plugin connects to `deploy@10.0.0.10:2222` using `~/.ssh/prod_key`. `prod-db` is reached tunneled through `bastion`.
51
87
 
52
88
  ## Security Modes
53
89
 
@@ -136,6 +172,12 @@ ssh.audit_log(session_id="prod-server", command_filter="docker")
136
172
 
137
173
  ## Security
138
174
 
175
+ ### Host Key Verification
176
+ With `"strict_host_key": true` the plugin verifies the remote server's host key against `~/.ssh/known_hosts` before completing the handshake:
177
+ - a **matching** key → connection allowed;
178
+ - a **changed** key (possible MITM) → connection refused;
179
+ - an **unknown** host → connection refused (add the host key to `known_hosts` first, e.g. via `ssh-keyscan host >> ~/.ssh/known_hosts`).
180
+
139
181
  ### Destructive Commands (100% Blocked)
140
182
  These commands are **permanently blocked** with no exceptions:
141
183
 
@@ -162,7 +204,8 @@ These commands require your explicit approval each time:
162
204
  ### Credential Safety
163
205
  - Passwords and SSH keys are **never** stored in logs or output
164
206
  - Session info only contains host, username, and port
165
- - Credentials are held in memory only during the session
207
+ - Passwords are cleared from memory right after a successful handshake
208
+ - For this reason password-authenticated sessions do **not** auto-reconnect (re-issue `ssh.connect`); key-authenticated sessions reconnect automatically from the on-disk key
166
209
  - All sessions are destroyed when the plugin is disposed
167
210
 
168
211
  ## Audit Log
@@ -27,6 +27,14 @@ export declare const SshPluginConfig: z.ZodObject<{
27
27
  ssh_config_path: z.ZodOptional<z.ZodString>;
28
28
  /** Auto-connect on plugin boot (requires saved session) */
29
29
  auto_connect: z.ZodDefault<z.ZodBoolean>;
30
+ /** Reconnect a dropped session automatically on the next command */
31
+ auto_reconnect: z.ZodDefault<z.ZodBoolean>;
32
+ /** Reject hosts whose key is not in known_hosts (MITM protection) */
33
+ strict_host_key: z.ZodDefault<z.ZodBoolean>;
34
+ /** Max commands allowed per host per minute (rate limiting) */
35
+ rate_limit_per_minute: z.ZodDefault<z.ZodNumber>;
36
+ /** Minimum delay in seconds between commands on the same host (cooldown) */
37
+ cooldown_seconds: z.ZodDefault<z.ZodNumber>;
30
38
  }, "strict", z.ZodTypeAny, {
31
39
  mode: "full" | "restricted" | "read_only";
32
40
  max_sessions: number;
@@ -35,6 +43,10 @@ export declare const SshPluginConfig: z.ZodObject<{
35
43
  blocklist_extra: string[];
36
44
  allowlist: string[];
37
45
  auto_connect: boolean;
46
+ auto_reconnect: boolean;
47
+ strict_host_key: boolean;
48
+ rate_limit_per_minute: number;
49
+ cooldown_seconds: number;
38
50
  ssh_config_path?: string | undefined;
39
51
  }, {
40
52
  mode?: "full" | "restricted" | "read_only" | undefined;
@@ -45,6 +57,10 @@ export declare const SshPluginConfig: z.ZodObject<{
45
57
  allowlist?: string[] | undefined;
46
58
  ssh_config_path?: string | undefined;
47
59
  auto_connect?: boolean | undefined;
60
+ auto_reconnect?: boolean | undefined;
61
+ strict_host_key?: boolean | undefined;
62
+ rate_limit_per_minute?: number | undefined;
63
+ cooldown_seconds?: number | undefined;
48
64
  }>;
49
65
  export type SshPluginConfigType = z.infer<typeof SshPluginConfig>;
50
66
  export declare const DEFAULT_CONFIG: SshPluginConfigType;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AACH,eAAO,MAAM,YAAY,gDAA8C,CAAA;AACvE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AAEvD;;GAEG;AACH,eAAO,MAAM,eAAe;IAExB,0CAA0C;;IAG1C,sCAAsC;;IAGtC,yCAAyC;;IAGzC,sCAAsC;;IAGtC,2DAA2D;;IAG3D,qEAAqE;;IAGrE,8CAA8C;;IAG9C,2DAA2D;;;;;;;;;;;;;;;;;;;;EAGpD,CAAA;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAEjE,eAAO,MAAM,cAAc,EAAE,mBAQ5B,CAAA"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AACH,eAAO,MAAM,YAAY,gDAA8C,CAAA;AACvE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA;AAEvD;;GAEG;AACH,eAAO,MAAM,eAAe;IAExB,0CAA0C;;IAG1C,sCAAsC;;IAGtC,yCAAyC;;IAGzC,sCAAsC;;IAGtC,2DAA2D;;IAG3D,qEAAqE;;IAGrE,8CAA8C;;IAG9C,2DAA2D;;IAG3D,oEAAoE;;IAGpE,qEAAqE;;IAGrE,+DAA+D;;IAG/D,4EAA4E;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGrE,CAAA;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAA;AAEjE,eAAO,MAAM,cAAc,EAAE,mBAY5B,CAAA"}
@@ -27,6 +27,14 @@ export const SshPluginConfig = z
27
27
  ssh_config_path: z.string().optional(),
28
28
  /** Auto-connect on plugin boot (requires saved session) */
29
29
  auto_connect: z.boolean().default(false),
30
+ /** Reconnect a dropped session automatically on the next command */
31
+ auto_reconnect: z.boolean().default(true),
32
+ /** Reject hosts whose key is not in known_hosts (MITM protection) */
33
+ strict_host_key: z.boolean().default(false),
34
+ /** Max commands allowed per host per minute (rate limiting) */
35
+ rate_limit_per_minute: z.number().int().min(1).max(10000).default(120),
36
+ /** Minimum delay in seconds between commands on the same host (cooldown) */
37
+ cooldown_seconds: z.number().int().min(0).max(3600).default(0),
30
38
  })
31
39
  .strict();
32
40
  export const DEFAULT_CONFIG = {
@@ -37,5 +45,9 @@ export const DEFAULT_CONFIG = {
37
45
  blocklist_extra: [],
38
46
  allowlist: [],
39
47
  auto_connect: false,
48
+ auto_reconnect: true,
49
+ strict_host_key: false,
50
+ rate_limit_per_minute: 120,
51
+ cooldown_seconds: 0,
40
52
  };
41
53
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC,CAAA;AAGvE;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC;KAC7B,MAAM,CAAC;IACN,0CAA0C;IAC1C,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC;IAElC,sCAAsC;IACtC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAExD,yCAAyC;IACzC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAE7D,sCAAsC;IACtC,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAExC,2DAA2D;IAC3D,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAEhD,qEAAqE;IACrE,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAE1C,8CAA8C;IAC9C,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAEtC,2DAA2D;IAC3D,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CACzC,CAAC;KACD,MAAM,EAAE,CAAA;AAIX,MAAM,CAAC,MAAM,cAAc,GAAwB;IACjD,IAAI,EAAE,MAAM;IACZ,YAAY,EAAE,CAAC;IACf,eAAe,EAAE,EAAE;IACnB,aAAa,EAAE,IAAI;IACnB,eAAe,EAAE,EAAE;IACnB,SAAS,EAAE,EAAE;IACb,YAAY,EAAE,KAAK;CACpB,CAAA"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC,CAAA;AAGvE;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC;KAC7B,MAAM,CAAC;IACN,0CAA0C;IAC1C,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC;IAElC,sCAAsC;IACtC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAExD,yCAAyC;IACzC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAE7D,sCAAsC;IACtC,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAExC,2DAA2D;IAC3D,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAEhD,qEAAqE;IACrE,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAE1C,8CAA8C;IAC9C,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAEtC,2DAA2D;IAC3D,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAExC,oEAAoE;IACpE,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAEzC,qEAAqE;IACrE,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAE3C,+DAA+D;IAC/D,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;IAEtE,4EAA4E;IAC5E,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;CAC/D,CAAC;KACD,MAAM,EAAE,CAAA;AAIX,MAAM,CAAC,MAAM,cAAc,GAAwB;IACjD,IAAI,EAAE,MAAM;IACZ,YAAY,EAAE,CAAC;IACf,eAAe,EAAE,EAAE;IACnB,aAAa,EAAE,IAAI;IACnB,eAAe,EAAE,EAAE;IACnB,SAAS,EAAE,EAAE;IACb,YAAY,EAAE,KAAK;IACnB,cAAc,EAAE,IAAI;IACpB,eAAe,EAAE,KAAK;IACtB,qBAAqB,EAAE,GAAG;IAC1B,gBAAgB,EAAE,CAAC;CACpB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;;;;;AAyBjD,wBAGC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;;;;;AA4DjD,wBAGC"}
package/dist/index.js CHANGED
@@ -1,11 +1,41 @@
1
1
  import { createSshTools } from "./tools.js";
2
2
  import { createSshHooks } from "./hooks.js";
3
3
  import { resolveConfig } from "./config/defaults.js";
4
- const SshPlugin = async (_ctx, options) => {
4
+ /**
5
+ * Validate that the host opencode runtime exposes the tool-building API this
6
+ * plugin depends on. If the runtime is too old/incompatible, tools are
7
+ * disabled instead of crashing at call time.
8
+ */
9
+ function validatePluginApi(ctx) {
10
+ const c = ctx;
11
+ if (!c || typeof c !== "object") {
12
+ return { ok: false, reason: "Plugin context is invalid" };
13
+ }
14
+ if (typeof c.tool !== "function") {
15
+ return { ok: false, reason: "ctx.tool is not available — runtime too old for tool-based plugins" };
16
+ }
17
+ const toolNs = c.tool;
18
+ if (!toolNs.schema || typeof toolNs.schema !== "object") {
19
+ return { ok: false, reason: "ctx.tool.schema (Zod-backed schema helpers) is missing — incompatible @opencode-ai/plugin" };
20
+ }
21
+ return { ok: true };
22
+ }
23
+ const SshPlugin = async (ctx, options) => {
5
24
  // ── Resolve configuration ──
6
25
  const config = resolveConfig(options);
26
+ // ── Compatibility check against the host runtime ──
27
+ const api = validatePluginApi(ctx);
28
+ if (!api.ok) {
29
+ console.warn(`[opencode-ssh] Plugin API incompatible: ${api.reason}. SSH tools are disabled.`);
30
+ return {};
31
+ }
7
32
  // ── Create tools and hooks with resolved config ──
8
- const tools = createSshTools(config.mode, config.max_sessions, config.default_timeout, config.blocklist_extra);
33
+ const tools = createSshTools(config.mode, config.max_sessions, config.default_timeout, config.blocklist_extra, config.ssh_config_path, config.auto_connect, {
34
+ strictHostKey: config.strict_host_key,
35
+ rateLimitPerMinute: config.rate_limit_per_minute,
36
+ cooldownSeconds: config.cooldown_seconds,
37
+ autoReconnect: config.auto_reconnect,
38
+ });
9
39
  const hooks = createSshHooks(config.mode, config.blocklist_extra);
10
40
  return {
11
41
  tool: tools,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGpD,MAAM,SAAS,GAAW,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;IAChD,8BAA8B;IAC9B,MAAM,MAAM,GAAG,aAAa,CAAC,OAAkC,CAAC,CAAA;IAEhE,oDAAoD;IACpD,MAAM,KAAK,GAAG,cAAc,CAC1B,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,YAAY,EACnB,MAAM,CAAC,eAAe,EACtB,MAAM,CAAC,eAAe,CACvB,CAAA;IACD,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,eAAe,CAAC,CAAA;IAEjE,OAAO;QACL,IAAI,EAAE,KAAK;QACX,GAAG,KAAK;KACT,CAAA;AACH,CAAC,CAAA;AAED,eAAe;IACb,EAAE,EAAE,cAAc;IAClB,MAAM,EAAE,SAAS;CAClB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAGpD;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,GAAY;IACrC,MAAM,CAAC,GAAG,GAA0C,CAAA;IACpD,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAA;IAC3D,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oEAAoE,EAAE,CAAA;IACpG,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,CAAC,IAA4B,CAAA;IAC7C,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,2FAA2F,EAAE,CAAA;IAC3H,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;AACrB,CAAC;AAED,MAAM,SAAS,GAAW,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;IAC/C,8BAA8B;IAC9B,MAAM,MAAM,GAAwB,aAAa,CAAC,OAAkC,CAAC,CAAA;IAErF,qDAAqD;IACrD,MAAM,GAAG,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAA;IAClC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,2CAA2C,GAAG,CAAC,MAAM,2BAA2B,CAAC,CAAA;QAC9F,OAAO,EAAE,CAAA;IACX,CAAC;IAED,oDAAoD;IACpD,MAAM,KAAK,GAAG,cAAc,CAC1B,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,YAAY,EACnB,MAAM,CAAC,eAAe,EACtB,MAAM,CAAC,eAAe,EACtB,MAAM,CAAC,eAAe,EACtB,MAAM,CAAC,YAAY,EACnB;QACE,aAAa,EAAE,MAAM,CAAC,eAAe;QACrC,kBAAkB,EAAE,MAAM,CAAC,qBAAqB;QAChD,eAAe,EAAE,MAAM,CAAC,gBAAgB;QACxC,aAAa,EAAE,MAAM,CAAC,cAAc;KACrC,CACF,CAAA;IACD,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,eAAe,CAAC,CAAA;IAEjE,OAAO;QACL,IAAI,EAAE,KAAK;QACX,GAAG,KAAK;KACT,CAAA;AACH,CAAC,CAAA;AAED,eAAe;IACb,EAAE,EAAE,cAAc;IAClB,MAAM,EAAE,SAAS;CAClB,CAAA"}
@@ -0,0 +1,60 @@
1
+ export interface SshConfigEntry {
2
+ /** Host patterns from the "Host" line (aliases / globs). */
3
+ patterns: string[];
4
+ /** Real hostname to connect to (HostName). */
5
+ hostName?: string;
6
+ /** User (User). */
7
+ user?: string;
8
+ /** Port (Port). */
9
+ port?: number;
10
+ /** Identity file (IdentityFile). */
11
+ identityFile?: string;
12
+ /** ProxyJump: comma-separated list of user@host:port hops. */
13
+ proxyJump?: string;
14
+ /** ProxyCommand: external command to open the tunnel. */
15
+ proxyCommand?: string;
16
+ }
17
+ export interface LoadedSshConfig {
18
+ entries: SshConfigEntry[];
19
+ path: string;
20
+ /** Whether the configured ssh config file exists on disk. */
21
+ exists: boolean;
22
+ }
23
+ /** Expand a leading `~` to the current user's home directory. */
24
+ export declare function expandHomePath(p: string): string;
25
+ /** Resolve the ssh config file path, using the override or ~/.ssh/config. */
26
+ export declare function resolveSshConfigPath(configuredPath?: string): string;
27
+ /**
28
+ * Parse the content of an OpenSSH config file.
29
+ * Handles `Host` blocks and `Match` blocks, and captures
30
+ * HostName / User / Port / IdentityFile directives.
31
+ */
32
+ export declare function parseSshConfig(content: string): SshConfigEntry[];
33
+ /**
34
+ * Find the first ssh config entry matching a host name.
35
+ * Prefers exact aliases, then falls back to `*`/`?` glob patterns.
36
+ */
37
+ export declare function findHostConfig(entries: SshConfigEntry[], host: string): SshConfigEntry | undefined;
38
+ /** Load and parse the ssh config file. Empty entry list if it does not exist. */
39
+ export declare function loadSshConfig(configuredPath?: string): LoadedSshConfig;
40
+ /**
41
+ * Parse a ProxyJump value ("user@host:port,user2@host2").
42
+ */
43
+ export declare function parseProxyJump(value: string, defaultUser?: string, defaultKeyPath?: string): Array<{
44
+ host: string;
45
+ port: number;
46
+ username?: string;
47
+ keyPath?: string;
48
+ }>;
49
+ /**
50
+ * Concrete machines suitable for auto-connecting: `Host` entries that
51
+ * resolve to a real `HostName`.
52
+ */
53
+ export declare function getAutoConnectHosts(configuredPath?: string): Array<{
54
+ alias: string;
55
+ hostName: string;
56
+ user?: string;
57
+ port?: number;
58
+ identityFile?: string;
59
+ }>;
60
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/ssh/config.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mBAAmB;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,mBAAmB;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CA0DhE;AAaD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAelG;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,eAAe,CAItE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,cAAc,CAAC,EAAE,MAAM,GACtB,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA2B5E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,cAAc,CAAC,EAAE,MAAM,GACtB,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAiBjG"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * SSH config (~/.ssh/config) parser.
3
+ * Reads alias, HostName, User, Port and IdentityFile so ssh.connect
4
+ * can reuse your normal SSH configuration and auto_connect knows what to hook.
5
+ */
6
+ import { readFileSync, existsSync } from "fs";
7
+ import { resolve as pathResolve } from "path";
8
+ /** Expand a leading `~` to the current user's home directory. */
9
+ export function expandHomePath(p) {
10
+ if (p.startsWith("~/")) {
11
+ return pathResolve(process.env.HOME || "", p.slice(2));
12
+ }
13
+ return p;
14
+ }
15
+ /** Resolve the ssh config file path, using the override or ~/.ssh/config. */
16
+ export function resolveSshConfigPath(configuredPath) {
17
+ if (configuredPath && configuredPath.trim()) {
18
+ const p = configuredPath.trim();
19
+ return p.startsWith("~/") ? expandHomePath(p) : pathResolve(p);
20
+ }
21
+ return pathResolve(process.env.HOME || "", ".ssh", "config");
22
+ }
23
+ /**
24
+ * Parse the content of an OpenSSH config file.
25
+ * Handles `Host` blocks and `Match` blocks, and captures
26
+ * HostName / User / Port / IdentityFile directives.
27
+ */
28
+ export function parseSshConfig(content) {
29
+ const entries = [];
30
+ let current = null;
31
+ for (const rawLine of content.split("\n")) {
32
+ const line = rawLine.trim();
33
+ if (!line || line.startsWith("#"))
34
+ continue;
35
+ const match = line.match(/^(\S+)\s+(.+)$/);
36
+ if (!match)
37
+ continue;
38
+ const key = match[1].toLowerCase();
39
+ const value = match[2].trim();
40
+ if (key === "host") {
41
+ const tokens = value.match(/"[^"]*"|\S+/g) || [];
42
+ current = {
43
+ patterns: tokens.map((t) => t.startsWith('"') && t.endsWith('"') ? t.slice(1, -1) : t),
44
+ };
45
+ entries.push(current);
46
+ continue;
47
+ }
48
+ if (key === "match") {
49
+ current = { patterns: [] };
50
+ entries.push(current);
51
+ continue;
52
+ }
53
+ if (!current)
54
+ continue;
55
+ switch (key) {
56
+ case "hostname":
57
+ current.hostName = value;
58
+ break;
59
+ case "user":
60
+ current.user = value;
61
+ break;
62
+ case "port": {
63
+ const num = Number.parseInt(value, 10);
64
+ if (!Number.isNaN(num))
65
+ current.port = num;
66
+ break;
67
+ }
68
+ case "identityfile":
69
+ current.identityFile = value;
70
+ break;
71
+ case "proxyjump":
72
+ current.proxyJump = value;
73
+ break;
74
+ case "proxycommand":
75
+ current.proxyCommand = value;
76
+ break;
77
+ }
78
+ }
79
+ return entries;
80
+ }
81
+ /** Turn an OpenSSH Host pattern (may contain `*` / `?`) into a RegExp. */
82
+ function hostPatternToRegex(pattern) {
83
+ let out = "";
84
+ for (const ch of pattern) {
85
+ if (ch === "*")
86
+ out += ".*";
87
+ else if (ch === "?")
88
+ out += ".";
89
+ else
90
+ out += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
91
+ }
92
+ return new RegExp(`^${out}$`, "i");
93
+ }
94
+ /**
95
+ * Find the first ssh config entry matching a host name.
96
+ * Prefers exact aliases, then falls back to `*`/`?` glob patterns.
97
+ */
98
+ export function findHostConfig(entries, host) {
99
+ const normalized = host.toLowerCase();
100
+ for (const entry of entries) {
101
+ for (const pattern of entry.patterns) {
102
+ if (pattern.toLowerCase() === normalized)
103
+ return entry;
104
+ }
105
+ }
106
+ for (const entry of entries) {
107
+ for (const pattern of entry.patterns) {
108
+ if (pattern.includes("*") || pattern.includes("?")) {
109
+ if (hostPatternToRegex(pattern).test(host))
110
+ return entry;
111
+ }
112
+ }
113
+ }
114
+ return undefined;
115
+ }
116
+ /** Load and parse the ssh config file. Empty entry list if it does not exist. */
117
+ export function loadSshConfig(configuredPath) {
118
+ const path = resolveSshConfigPath(configuredPath);
119
+ if (!existsSync(path))
120
+ return { entries: [], path, exists: false };
121
+ return { entries: parseSshConfig(readFileSync(path, "utf-8")), path, exists: true };
122
+ }
123
+ /**
124
+ * Parse a ProxyJump value ("user@host:port,user2@host2").
125
+ */
126
+ export function parseProxyJump(value, defaultUser, defaultKeyPath) {
127
+ return value
128
+ .split(",")
129
+ .map((hop) => hop.trim())
130
+ .filter(Boolean)
131
+ .map((hop) => {
132
+ let user;
133
+ let rest = hop;
134
+ if (hop.includes("@")) {
135
+ const at = hop.lastIndexOf("@");
136
+ user = hop.slice(0, at);
137
+ rest = hop.slice(at + 1);
138
+ }
139
+ let port = 22;
140
+ let host = rest;
141
+ const colon = rest.match(/^(.*):(\d+)$/);
142
+ if (colon) {
143
+ host = colon[1];
144
+ port = Number(colon[2]);
145
+ }
146
+ return {
147
+ host,
148
+ port,
149
+ username: user || defaultUser,
150
+ keyPath: defaultKeyPath,
151
+ };
152
+ });
153
+ }
154
+ /**
155
+ * Concrete machines suitable for auto-connecting: `Host` entries that
156
+ * resolve to a real `HostName`.
157
+ */
158
+ export function getAutoConnectHosts(configuredPath) {
159
+ const { entries } = loadSshConfig(configuredPath);
160
+ const hosts = [];
161
+ for (const entry of entries) {
162
+ if (entry.patterns.length > 0 && entry.hostName) {
163
+ hosts.push({
164
+ alias: entry.patterns[0],
165
+ hostName: entry.hostName,
166
+ user: entry.user,
167
+ port: entry.port,
168
+ identityFile: entry.identityFile,
169
+ });
170
+ }
171
+ }
172
+ return hosts;
173
+ }
174
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/ssh/config.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,9 @@
1
+ import { type ProxySpec } from "./proxy.js";
1
2
  export interface SshConnectionConfig {
2
3
  host: string;
3
4
  port: number;
4
- username: string;
5
+ /** SSH username, resolved from args or the ssh config. */
6
+ username?: string;
5
7
  authMethod: "password" | "key";
6
8
  password?: string;
7
9
  keyPath?: string;
@@ -10,6 +12,16 @@ export interface SshConnectionConfig {
10
12
  timeout?: number;
11
13
  /** Connection alias for display */
12
14
  alias?: string;
15
+ /** Reject hosts whose key is not (an identical match) in known_hosts. */
16
+ strictHostKey?: boolean;
17
+ /** Custom path to a known_hosts file. */
18
+ knownHostsPath?: string;
19
+ /** SSH config default path, used to locate known_hosts next to it. */
20
+ sshConfigPath?: string;
21
+ /** ProxyJump / ProxyCommand support. */
22
+ proxy?: ProxySpec;
23
+ /** Automatically reconnect a dropped session on the next exec/sftp. */
24
+ autoReconnect?: boolean;
13
25
  }
14
26
  export interface SshConnectionInfo {
15
27
  id: string;
@@ -25,21 +37,28 @@ export declare class SshConnection {
25
37
  private _connected;
26
38
  private _connectedAt?;
27
39
  private _lastActivity?;
28
- private _reconnectAttempts;
29
- private _maxReconnectAttempts;
40
+ private _maxAttempts;
41
+ private _attempts;
42
+ private _proxyCleanup?;
43
+ private _hostKeyVerdict?;
30
44
  constructor(id: string, config: SshConnectionConfig);
31
45
  get id(): string;
32
46
  get config(): SshConnectionConfig;
33
47
  get connected(): boolean;
34
48
  get connectedAt(): Date | undefined;
35
49
  get lastActivity(): Date | undefined;
50
+ /** Result of the last known_hosts check (undefined when disabled). */
51
+ get hostKeyVerdict(): "ok" | "changed" | "unknown" | undefined;
36
52
  /**
37
- * Connect to the SSH server.
53
+ * Connect to the SSH server, with bounded retries.
38
54
  */
39
55
  connect(): Promise<void>;
56
+ private connectWithBackoff;
57
+ private buildConnectConfig;
58
+ private openConnection;
40
59
  /**
41
- * Execute a command on the remote server.
42
- * Returns a stream for real-time output.
60
+ * Execute a command on the remote server. Automatically reconnects a
61
+ * dropped session before running.
43
62
  */
44
63
  exec(command: string, options?: {
45
64
  cwd?: string;
@@ -58,7 +77,8 @@ export declare class SshConnection {
58
77
  timeout?: number;
59
78
  }): NodeJS.ReadableStream | null;
60
79
  /**
61
- * Get an SFTP session for file transfers.
80
+ * Get an SFTP session for file transfers. Automatically reconnects a
81
+ * dropped session before returning.
62
82
  */
63
83
  sftp(): Promise<any>;
64
84
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/ssh/connection.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,GAAG,KAAK,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,mBAAmB,CAAA;IAC3B,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,GAAG,CAAQ;IACnB,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,YAAY,CAAC,CAAM;IAC3B,OAAO,CAAC,aAAa,CAAC,CAAM;IAC5B,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,qBAAqB,CAAI;gBAErB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB;IAMnD,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,IAAI,MAAM,IAAI,mBAAmB,CAEhC;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,WAAW,IAAI,IAAI,GAAG,SAAS,CAElC;IAED,IAAI,YAAY,IAAI,IAAI,GAAG,SAAS,CAEnC;IAED;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA+D9B;;;OAGG;IACG,IAAI,CACR,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IA8D/F;;OAEG;IACH,UAAU,CACR,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3C,MAAM,CAAC,cAAc,GAAG,IAAI;IAgB/B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;IAgB1B;;OAEG;IACH,UAAU,IAAI,IAAI;IAOlB;;OAEG;IACH,OAAO,IAAI,iBAAiB;CAgB7B"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/ssh/connection.ts"],"names":[],"mappings":"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"}