@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.
@@ -2,6 +2,9 @@ import { Client } from "ssh2";
2
2
  import { readFileSync, existsSync } from "fs";
3
3
  import { resolve as pathResolve } from "path";
4
4
  import { escapeShellArg } from "./sanitizer.js";
5
+ import { verifyHostKey } from "./known_hosts.js";
6
+ import { prepareProxy } from "./proxy.js";
7
+ const DEFAULT_KNOWN_HOSTS = () => pathResolve(process.env.HOME || "", ".ssh", "known_hosts");
5
8
  export class SshConnection {
6
9
  client;
7
10
  _config;
@@ -9,12 +12,15 @@ export class SshConnection {
9
12
  _connected = false;
10
13
  _connectedAt;
11
14
  _lastActivity;
12
- _reconnectAttempts = 0;
13
- _maxReconnectAttempts = 3;
15
+ _maxAttempts = 3;
16
+ _attempts = 0;
17
+ _proxyCleanup;
18
+ _hostKeyVerdict;
14
19
  constructor(id, config) {
15
20
  this._id = id;
16
21
  this._config = config;
17
- this.client = new Client();
22
+ this._config.strictHostKey = this._config.strictHostKey ?? false;
23
+ this._config.autoReconnect = this._config.autoReconnect ?? true;
18
24
  }
19
25
  get id() {
20
26
  return this._id;
@@ -31,71 +37,124 @@ export class SshConnection {
31
37
  get lastActivity() {
32
38
  return this._lastActivity;
33
39
  }
40
+ /** Result of the last known_hosts check (undefined when disabled). */
41
+ get hostKeyVerdict() {
42
+ return this._hostKeyVerdict;
43
+ }
34
44
  /**
35
- * Connect to the SSH server.
45
+ * Connect to the SSH server, with bounded retries.
36
46
  */
37
47
  async connect() {
38
- return new Promise((resolve, reject) => {
39
- const connectConfig = {
48
+ if (!this._config.username) {
49
+ throw new Error("A username is required to connect (provide one or set User in the ssh config).");
50
+ }
51
+ this._attempts = 0;
52
+ await this.connectWithBackoff();
53
+ // Credentials are only needed for the handshake; drop them from memory
54
+ // once the connection is established so they are not retained.
55
+ this._config.password = undefined;
56
+ }
57
+ async connectWithBackoff() {
58
+ try {
59
+ const cfg = await this.buildConnectConfig();
60
+ await this.openConnection(cfg);
61
+ }
62
+ catch (err) {
63
+ this._attempts++;
64
+ if (this._attempts < this._maxAttempts) {
65
+ await new Promise((r) => setTimeout(r, 1000 * this._attempts));
66
+ return this.connectWithBackoff();
67
+ }
68
+ throw err;
69
+ }
70
+ }
71
+ async buildConnectConfig() {
72
+ const connectConfig = {
73
+ host: this._config.host,
74
+ port: this._config.port,
75
+ username: this._config.username,
76
+ readyTimeout: this._config.timeout || 10000,
77
+ keepaliveInterval: 10000,
78
+ keepaliveCountMax: 3,
79
+ };
80
+ // ── Host key verification (MITM protection) ──
81
+ if (this._config.strictHostKey) {
82
+ const knownHostsPath = this._config.knownHostsPath || DEFAULT_KNOWN_HOSTS();
83
+ connectConfig.hostVerifier = (key) => {
84
+ const verdict = verifyHostKey(this._config.host, key, knownHostsPath, this._config.port);
85
+ this._hostKeyVerdict = verdict;
86
+ // A changed key is always rejected. An unknown key is only accepted
87
+ // in non-strict mode; here strict mode requires it to be present.
88
+ return verdict === "ok";
89
+ };
90
+ }
91
+ // ── Proxy tunnel (ProxyJump / ProxyCommand) ──
92
+ if (this._config.proxy) {
93
+ const prepared = await prepareProxy(this._config.proxy, {
40
94
  host: this._config.host,
41
95
  port: this._config.port,
42
96
  username: this._config.username,
43
- readyTimeout: this._config.timeout || 10000,
44
- keepaliveInterval: 10000,
45
- keepaliveCountMax: 3,
46
- };
47
- // Configure authentication
48
- if (this._config.authMethod === "password") {
97
+ keyPath: this._config.keyPath,
98
+ passphrase: this._config.passphrase,
99
+ });
100
+ this._proxyCleanup = prepared.cleanup;
101
+ connectConfig.sock = prepared.sock;
102
+ }
103
+ // ── Authentication ──
104
+ if (this._config.authMethod === "password") {
105
+ if (this._config.password) {
49
106
  connectConfig.password = this._config.password;
50
107
  }
51
- else {
52
- // Key-based authentication
53
- const keyPath = this._config.keyPath
54
- ? pathResolve(this._config.keyPath.replace("~", process.env.HOME || ""))
55
- : pathResolve(process.env.HOME || "", ".ssh", "id_rsa");
56
- if (!existsSync(keyPath)) {
57
- reject(new Error(`SSH key not found: ${keyPath}`));
58
- return;
59
- }
60
- connectConfig.privateKey = readFileSync(keyPath, "utf-8");
61
- if (this._config.passphrase) {
62
- connectConfig.passphrase = this._config.passphrase;
63
- }
108
+ }
109
+ else {
110
+ const keyPath = this._config.keyPath
111
+ ? pathResolve(this._config.keyPath.replace("~", process.env.HOME || ""))
112
+ : pathResolve(process.env.HOME || "", ".ssh", "id_rsa");
113
+ if (!existsSync(keyPath)) {
114
+ throw new Error(`SSH key not found: ${keyPath}`);
115
+ }
116
+ connectConfig.privateKey = readFileSync(keyPath, "utf-8");
117
+ if (this._config.passphrase) {
118
+ connectConfig.passphrase = this._config.passphrase;
64
119
  }
65
- this.client.on("ready", () => {
120
+ }
121
+ return connectConfig;
122
+ }
123
+ async openConnection(cfg) {
124
+ // A fresh Client per attempt so a failed handshake never leaves the
125
+ // previous transport in an unusable state.
126
+ const client = new Client();
127
+ this.client = client;
128
+ return new Promise((resolve, reject) => {
129
+ client.on("ready", () => {
66
130
  this._connected = true;
67
131
  this._connectedAt = new Date();
68
132
  this._lastActivity = new Date();
69
- this._reconnectAttempts = 0;
70
133
  resolve();
71
134
  });
72
- this.client.on("error", (err) => {
135
+ client.on("error", (err) => {
73
136
  this._connected = false;
74
- if (this._reconnectAttempts < this._maxReconnectAttempts) {
75
- this._reconnectAttempts++;
76
- // Attempt reconnect after delay
77
- setTimeout(() => {
78
- this.connect().catch(() => { });
79
- }, 1000 * this._reconnectAttempts);
80
- }
81
137
  reject(err);
82
138
  });
83
- this.client.on("close", () => {
139
+ client.on("close", () => {
84
140
  this._connected = false;
85
141
  });
86
- this.client.on("end", () => {
142
+ client.on("end", () => {
87
143
  this._connected = false;
88
144
  });
89
- this.client.connect(connectConfig);
145
+ client.connect(cfg);
90
146
  });
91
147
  }
92
148
  /**
93
- * Execute a command on the remote server.
94
- * Returns a stream for real-time output.
149
+ * Execute a command on the remote server. Automatically reconnects a
150
+ * dropped session before running.
95
151
  */
96
152
  async exec(command, options) {
97
153
  if (!this._connected) {
98
- throw new Error("SSH connection is not active");
154
+ if (this._config.autoReconnect === false) {
155
+ throw new Error("SSH connection is not active");
156
+ }
157
+ await this.connect();
99
158
  }
100
159
  this._lastActivity = new Date();
101
160
  const wrappedCommand = options?.cwd
@@ -112,7 +171,6 @@ export class SshConnection {
112
171
  let stdout = "";
113
172
  let stderr = "";
114
173
  let killed = false;
115
- // Set timeout
116
174
  if (timeoutMs > 0) {
117
175
  timeoutId = setTimeout(() => {
118
176
  killed = true;
@@ -162,12 +220,17 @@ export class SshConnection {
162
220
  return null; // Placeholder - actual streaming handled in executor.ts
163
221
  }
164
222
  /**
165
- * Get an SFTP session for file transfers.
223
+ * Get an SFTP session for file transfers. Automatically reconnects a
224
+ * dropped session before returning.
166
225
  */
167
226
  async sftp() {
168
227
  if (!this._connected) {
169
- throw new Error("SSH connection is not active");
228
+ if (this._config.autoReconnect === false) {
229
+ throw new Error("SSH connection is not active");
230
+ }
231
+ await this.connect();
170
232
  }
233
+ this._lastActivity = new Date();
171
234
  return new Promise((resolve, reject) => {
172
235
  this.client.sftp((err, sftp) => {
173
236
  if (err) {
@@ -182,10 +245,12 @@ export class SshConnection {
182
245
  * Disconnect from the SSH server.
183
246
  */
184
247
  disconnect() {
185
- if (this._connected) {
248
+ if (this.client) {
186
249
  this.client.end();
187
- this._connected = false;
188
250
  }
251
+ this._connected = false;
252
+ this._proxyCleanup?.();
253
+ this._proxyCleanup = undefined;
189
254
  }
190
255
  /**
191
256
  * Get connection info for display.
@@ -1 +1 @@
1
- {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/ssh/connection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/ssh/connection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export type HostKeyVerdict = "ok" | "changed" | "unknown";
2
+ interface KnownHostEntry {
3
+ /** Unix shell-style pattern for the host (may contain * / ? and be comma separated). */
4
+ original: string;
5
+ /** Base64-encoded SSH public key blob (matches what ssh2 passes to hostVerifier). */
6
+ key: Buffer;
7
+ /** True if the hostname portion is stored hashed (`|1|...|`). */
8
+ hashed: boolean;
9
+ }
10
+ /** Strip `[host]:port` brackets and the default-22 port suffix for comparisons. */
11
+ export declare function normalizeHostName(host: string, port?: number): string;
12
+ /** Load and parse a known_hosts file (defaults to ~/.ssh/known_hosts). */
13
+ export declare function loadKnownHosts(configuredPath?: string): KnownHostEntry[];
14
+ /**
15
+ * Verify a server host key against known_hosts.
16
+ *
17
+ * @param host hostname/IP the user asked to connect to
18
+ * @param serverKey raw server public key blob (as delivered to ssh2 hostVerifier)
19
+ * @param configuredPath optional path to a custom known_hosts file
20
+ * @returns "ok" when a matching, identical key exists; "changed" when a matching
21
+ * entry holds a different key; "unknown" when no entry matches the host.
22
+ */
23
+ export declare function verifyHostKey(host: string, serverKey: Buffer, configuredPath?: string, port?: number): HostKeyVerdict;
24
+ export {};
25
+ //# sourceMappingURL=known_hosts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known_hosts.d.ts","sourceRoot":"","sources":["../../src/ssh/known_hosts.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,CAAA;AAIzD,UAAU,cAAc;IACtB,wFAAwF;IACxF,QAAQ,EAAE,MAAM,CAAA;IAChB,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAA;IACX,iEAAiE;IACjE,MAAM,EAAE,OAAO,CAAA;CAChB;AAoCD,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAcrE;AAuED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAIxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE,MAAM,EACvB,IAAI,CAAC,EAAE,MAAM,GACZ,cAAc,CAShB"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * known_hosts support — verifies the remote server's host key against
3
+ * ~/.ssh/known_hosts so connections cannot be silently redirected (MITM).
4
+ */
5
+ import { readFileSync, existsSync } from "fs";
6
+ import { createHash } from "crypto";
7
+ import { resolve as pathResolve } from "path";
8
+ const DEFAULT_KNOWN_HOSTS = () => pathResolve(process.env.HOME || "", ".ssh", "known_hosts");
9
+ function parseKnownHosts(content) {
10
+ const entries = [];
11
+ for (const rawLine of content.split("\n")) {
12
+ const line = rawLine.trim();
13
+ if (!line || line.startsWith("#"))
14
+ continue;
15
+ // Fields: marker? hostpart(s) keytype base64key [comment...]
16
+ const parts = line.split(/\s+/);
17
+ if (parts.length < 3)
18
+ continue;
19
+ // Skip optional "marker" (@cert-authority, @revoked)
20
+ let idx = 0;
21
+ if (parts[0].startsWith("@"))
22
+ idx = 1;
23
+ const hostPart = parts[idx];
24
+ const keyPart = parts[idx + 2]; // parts[idx+1] is the key type
25
+ if (!keyPart)
26
+ continue;
27
+ let key;
28
+ try {
29
+ key = Buffer.from(keyPart, "base64");
30
+ }
31
+ catch {
32
+ continue;
33
+ }
34
+ if (!key || key.length === 0)
35
+ continue;
36
+ const hashed = hostPart.startsWith("|");
37
+ entries.push({ original: hostPart, key, hashed });
38
+ }
39
+ return entries;
40
+ }
41
+ /** Strip `[host]:port` brackets and the default-22 port suffix for comparisons. */
42
+ export function normalizeHostName(host, port) {
43
+ let h = host;
44
+ const bracket = h.match(/^\[(.+)\]/);
45
+ if (bracket) {
46
+ h = bracket[1];
47
+ return h;
48
+ }
49
+ // `hostname,hostname2` handled at compare time; here just strip port if present
50
+ const withPort = h.match(/^(.+):(\d+)$/);
51
+ if (withPort) {
52
+ const p = Number(withPort[2]);
53
+ if (port === undefined || p === port)
54
+ h = withPort[1];
55
+ }
56
+ return h;
57
+ }
58
+ /**
59
+ * Normalize a known_hosts pattern into a bare host string, extracting the
60
+ * host portion out of `[host]:port` / `host:port` forms when the stored port
61
+ * matches the requested one.
62
+ */
63
+ function normalizePatternHost(pattern, port) {
64
+ let p = pattern;
65
+ const bracket = p.match(/^\[(.+)\](?::(\d+))?$/);
66
+ if (bracket) {
67
+ const pPort = bracket[2] ? Number(bracket[2]) : undefined;
68
+ if (port !== undefined && pPort !== undefined && pPort !== port)
69
+ return "";
70
+ return bracket[1];
71
+ }
72
+ const withPort = p.match(/^(.+):(\d+)$/);
73
+ if (withPort) {
74
+ const pPort = Number(withPort[2]);
75
+ if (port !== undefined && pPort !== port)
76
+ return "";
77
+ return withPort[1];
78
+ }
79
+ return p;
80
+ }
81
+ function hostPatternMatches(pattern, host, port) {
82
+ // known_hosts supports `|1|base64salt|base64hash`
83
+ if (pattern.startsWith("|")) {
84
+ const parts = pattern.split("|");
85
+ if (parts.length !== 4)
86
+ return false;
87
+ const [, magic, saltB64, hashB64] = parts;
88
+ if (magic !== "1")
89
+ return false;
90
+ try {
91
+ const salt = Buffer.from(saltB64, "base64");
92
+ const expected = Buffer.from(hashB64, "base64");
93
+ // OpenSSH stores SHA1(salt || lowercased-host)
94
+ const digest = createHash("sha1").update(salt).update(host.toLowerCase(), "utf8").digest();
95
+ return digest.equals(expected);
96
+ }
97
+ catch {
98
+ return false;
99
+ }
100
+ }
101
+ // `[host]:port`, `host:port` and comma-separated lists of patterns.
102
+ const target = host.toLowerCase();
103
+ for (const raw of pattern.split(",")) {
104
+ const part = normalizePatternHost(raw.trim(), port);
105
+ if (!part)
106
+ continue;
107
+ const p = part.toLowerCase();
108
+ if (p === target)
109
+ return true;
110
+ if (!p.includes("*") && !p.includes("?"))
111
+ continue;
112
+ let re = "";
113
+ for (const ch of p) {
114
+ if (ch === "*")
115
+ re += ".*";
116
+ else if (ch === "?")
117
+ re += ".";
118
+ else
119
+ re += ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
120
+ }
121
+ try {
122
+ if (new RegExp(`^${re}$`).test(target))
123
+ return true;
124
+ }
125
+ catch {
126
+ /* ignore invalid patterns */
127
+ }
128
+ }
129
+ return false;
130
+ }
131
+ function entriesForHost(entries, host, port) {
132
+ const normalized = normalizeHostName(host, port);
133
+ return entries.filter((e) => hostPatternMatches(e.original, normalized, port));
134
+ }
135
+ /** Load and parse a known_hosts file (defaults to ~/.ssh/known_hosts). */
136
+ export function loadKnownHosts(configuredPath) {
137
+ const path = configuredPath || DEFAULT_KNOWN_HOSTS();
138
+ if (!existsSync(path))
139
+ return [];
140
+ return parseKnownHosts(readFileSync(path, "utf-8"));
141
+ }
142
+ /**
143
+ * Verify a server host key against known_hosts.
144
+ *
145
+ * @param host hostname/IP the user asked to connect to
146
+ * @param serverKey raw server public key blob (as delivered to ssh2 hostVerifier)
147
+ * @param configuredPath optional path to a custom known_hosts file
148
+ * @returns "ok" when a matching, identical key exists; "changed" when a matching
149
+ * entry holds a different key; "unknown" when no entry matches the host.
150
+ */
151
+ export function verifyHostKey(host, serverKey, configuredPath, port) {
152
+ if (!serverKey || serverKey.length === 0)
153
+ return "unknown";
154
+ const entries = entriesForHost(loadKnownHosts(configuredPath), host, port ?? 22);
155
+ if (entries.length === 0)
156
+ return "unknown";
157
+ const match = entries.some((e) => e.key.equals(serverKey));
158
+ return match ? "ok" : "changed";
159
+ }
160
+ //# sourceMappingURL=known_hosts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known_hosts.js","sourceRoot":"","sources":["../../src/ssh/known_hosts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { Duplex } from "stream";
2
+ export interface JumpHost {
3
+ host: string;
4
+ port: number;
5
+ username?: string;
6
+ keyPath?: string;
7
+ }
8
+ export interface ProxySpec {
9
+ jumps?: JumpHost[];
10
+ command?: string;
11
+ }
12
+ export interface ProxyTarget {
13
+ host: string;
14
+ port: number;
15
+ username?: string;
16
+ keyPath?: string;
17
+ passphrase?: string;
18
+ }
19
+ export interface PreparedProxy {
20
+ /** Stream the main connection should use instead of a fresh TCP socket. */
21
+ sock: Duplex;
22
+ /** Release intermediate resources (jump clients / child processes). */
23
+ cleanup(): void;
24
+ }
25
+ /**
26
+ * Build a tunnel to `target` through the given proxy. The returned stream is
27
+ * passed as the `sock` of the target connection.
28
+ */
29
+ export declare function prepareProxy(proxy: ProxySpec, target: ProxyTarget): Promise<PreparedProxy>;
30
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/ssh/proxy.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAG/B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAA;IACZ,uEAAuE;IACvE,OAAO,IAAI,IAAI,CAAA;CAChB;AAmDD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAgFhG"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Proxy support for SSH connections:
3
+ * - ProxyJump: hop through one or more jump hosts (OpenSSH ProxyJump).
4
+ * - ProxyCommand: use an external command to open the tunnel
5
+ * (OpenSSH ProxyCommand), e.g. `ssh -W %h:%p bastion`.
6
+ */
7
+ import { Client } from "ssh2";
8
+ import { spawn } from "child_process";
9
+ import { Duplex } from "stream";
10
+ import { readFileSync, existsSync } from "fs";
11
+ function loadPrivateKey(keyPath) {
12
+ if (!keyPath)
13
+ return undefined;
14
+ const path = keyPath;
15
+ if (!existsSync(path))
16
+ return undefined;
17
+ return readFileSync(path);
18
+ }
19
+ function connectClient(opts, sock) {
20
+ const privateKey = loadPrivateKey(opts.keyPath);
21
+ return new Promise((resolve, reject) => {
22
+ const client = new Client();
23
+ const timer = setTimeout(() => {
24
+ client.destroy();
25
+ reject(new Error(`Proxy connection to ${opts.username}@${opts.host}:${opts.port} timed out`));
26
+ }, opts.timeoutMs);
27
+ client
28
+ .on("ready", () => {
29
+ clearTimeout(timer);
30
+ resolve(client);
31
+ })
32
+ .on("error", (err) => {
33
+ clearTimeout(timer);
34
+ reject(err);
35
+ })
36
+ .connect({
37
+ host: opts.host,
38
+ port: opts.port,
39
+ username: opts.username,
40
+ readyTimeout: opts.timeoutMs,
41
+ ...(sock ? { sock } : {}),
42
+ ...(privateKey ? { privateKey } : {}),
43
+ ...(opts.passphrase ? { passphrase: opts.passphrase } : {}),
44
+ });
45
+ });
46
+ }
47
+ function forwardOut(client, srcIP, srcPort, dstIP, dstPort) {
48
+ return new Promise((resolve, reject) => {
49
+ client.forwardOut(srcIP, srcPort, dstIP, dstPort, (err, stream) => {
50
+ if (err)
51
+ reject(err);
52
+ else
53
+ resolve(stream);
54
+ });
55
+ });
56
+ }
57
+ /**
58
+ * Build a tunnel to `target` through the given proxy. The returned stream is
59
+ * passed as the `sock` of the target connection.
60
+ */
61
+ export async function prepareProxy(proxy, target) {
62
+ const timeoutMs = 10_000;
63
+ // ── ProxyCommand: spawn and use its stdio as the socket ──
64
+ if (proxy.command && proxy.command.trim()) {
65
+ const cmd = proxy.command
66
+ .replaceAll("%h", target.host)
67
+ .replaceAll("%p", String(target.port))
68
+ .replaceAll("%r", target.username || process.env.USER || "");
69
+ const child = spawn("sh", ["-c", cmd], { stdio: ["pipe", "pipe", "inherit"] });
70
+ const tunnel = new Duplex({
71
+ read() {
72
+ // Data is pushed manually from child.stdout below.
73
+ },
74
+ write(chunk, _enc, cb) {
75
+ child.stdin?.write(chunk, cb);
76
+ },
77
+ final(cb) {
78
+ child.stdin?.end(cb);
79
+ },
80
+ });
81
+ child.stdout?.on("data", (chunk) => tunnel.push(chunk));
82
+ child.stdout?.on("end", () => tunnel.push(null));
83
+ child.stdout?.on("error", (err) => tunnel.destroy(err));
84
+ child.on("error", (err) => tunnel.destroy(err));
85
+ return {
86
+ sock: tunnel,
87
+ cleanup: () => {
88
+ if (!child.killed)
89
+ child.kill();
90
+ },
91
+ };
92
+ }
93
+ // ── ProxyJump: chain clients through hops, last hop receives the target ──
94
+ const jumps = proxy.jumps || [];
95
+ if (jumps.length === 0) {
96
+ throw new Error("No proxy strategy configured");
97
+ }
98
+ const clients = [];
99
+ let sock;
100
+ try {
101
+ for (let i = 0; i < jumps.length; i++) {
102
+ const hop = jumps[i];
103
+ const next = jumps[i + 1];
104
+ const destHost = next ? next.host : target.host;
105
+ const destPort = next ? next.port : target.port;
106
+ const destUser = next ? next.username || hop.username || target.username : target.username;
107
+ const username = hop.username || target.username || process.env.USER || "root";
108
+ const keyPath = hop.keyPath || target.keyPath;
109
+ // The next-hop client talks over the previous hop's forwarded stream
110
+ const client = await connectClient({ host: hop.host, port: hop.port, username, keyPath, timeoutMs }, sock);
111
+ clients.push(client);
112
+ // Open the outbound channel to the next destination
113
+ sock = await forwardOut(client, "127.0.0.1", 0, destHost, destPort);
114
+ void destUser;
115
+ }
116
+ if (!sock)
117
+ throw new Error("Failed to build proxy tunnel");
118
+ const finalSock = sock;
119
+ return {
120
+ sock: finalSock,
121
+ cleanup: () => {
122
+ for (const c of clients)
123
+ c.end();
124
+ },
125
+ };
126
+ }
127
+ catch (err) {
128
+ for (const c of clients)
129
+ c.end();
130
+ throw err;
131
+ }
132
+ }
133
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/ssh/proxy.ts"],"names":[],"mappings":"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"}