@judaharagao/opencode-ssh 1.0.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 +21 -0
- package/README.md +184 -0
- package/dist/config/defaults.d.ts +11 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +23 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/schema.d.ts +51 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +41 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/display/formatter.d.ts +41 -0
- package/dist/display/formatter.d.ts.map +1 -0
- package/dist/display/formatter.js +179 -0
- package/dist/display/formatter.js.map +1 -0
- package/dist/display/stream.d.ts +38 -0
- package/dist/display/stream.d.ts.map +1 -0
- package/dist/display/stream.js +102 -0
- package/dist/display/stream.js.map +1 -0
- package/dist/hooks.d.ts +13 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +151 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/security/allowlist.d.ts +23 -0
- package/dist/security/allowlist.d.ts.map +1 -0
- package/dist/security/allowlist.js +105 -0
- package/dist/security/allowlist.js.map +1 -0
- package/dist/security/blocklist.d.ts +35 -0
- package/dist/security/blocklist.d.ts.map +1 -0
- package/dist/security/blocklist.js +351 -0
- package/dist/security/blocklist.js.map +1 -0
- package/dist/security/policy.d.ts +40 -0
- package/dist/security/policy.d.ts.map +1 -0
- package/dist/security/policy.js +133 -0
- package/dist/security/policy.js.map +1 -0
- package/dist/security/validator.d.ts +32 -0
- package/dist/security/validator.d.ts.map +1 -0
- package/dist/security/validator.js +152 -0
- package/dist/security/validator.js.map +1 -0
- package/dist/ssh/audit.d.ts +47 -0
- package/dist/ssh/audit.d.ts.map +1 -0
- package/dist/ssh/audit.js +126 -0
- package/dist/ssh/audit.js.map +1 -0
- package/dist/ssh/connection.d.ts +73 -0
- package/dist/ssh/connection.d.ts.map +1 -0
- package/dist/ssh/connection.js +210 -0
- package/dist/ssh/connection.js.map +1 -0
- package/dist/ssh/executor.d.ts +49 -0
- package/dist/ssh/executor.d.ts.map +1 -0
- package/dist/ssh/executor.js +275 -0
- package/dist/ssh/executor.js.map +1 -0
- package/dist/ssh/manager.d.ts +43 -0
- package/dist/ssh/manager.d.ts.map +1 -0
- package/dist/ssh/manager.js +98 -0
- package/dist/ssh/manager.js.map +1 -0
- package/dist/ssh/sanitizer.d.ts +41 -0
- package/dist/ssh/sanitizer.d.ts.map +1 -0
- package/dist/ssh/sanitizer.js +108 -0
- package/dist/ssh/sanitizer.js.map +1 -0
- package/dist/tools.d.ts +7 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +584 -0
- package/dist/tools.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SshConnection } from "./connection.js";
|
|
2
|
+
import { type ValidationResult } from "../security/validator.js";
|
|
3
|
+
import type { SecurityMode } from "../config/schema.js";
|
|
4
|
+
export interface ExecOptions {
|
|
5
|
+
cwd?: string;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
sudo?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ExecResult {
|
|
10
|
+
/** Command that was executed */
|
|
11
|
+
command: string;
|
|
12
|
+
/** Standard output */
|
|
13
|
+
stdout: string;
|
|
14
|
+
/** Standard error output */
|
|
15
|
+
stderr: string;
|
|
16
|
+
/** Exit code */
|
|
17
|
+
exitCode: number | null;
|
|
18
|
+
/** Signal that killed the process (if any) */
|
|
19
|
+
signal?: string;
|
|
20
|
+
/** Duration in milliseconds */
|
|
21
|
+
duration: number;
|
|
22
|
+
/** Session ID */
|
|
23
|
+
sessionId: string;
|
|
24
|
+
/** Validation result */
|
|
25
|
+
validation: ValidationResult;
|
|
26
|
+
}
|
|
27
|
+
export interface ExecStreamCallbacks {
|
|
28
|
+
onStdout?: (data: string) => void;
|
|
29
|
+
onStderr?: (data: string) => void;
|
|
30
|
+
onExit?: (code: number | null) => void;
|
|
31
|
+
onError?: (error: Error) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Execute a command on an SSH connection with security validation.
|
|
35
|
+
*
|
|
36
|
+
* This is the main execution function that:
|
|
37
|
+
* 1. Validates the command against the security policy
|
|
38
|
+
* 2. Sanitizes the input
|
|
39
|
+
* 3. Executes the command
|
|
40
|
+
* 4. Logs the result to audit
|
|
41
|
+
* 5. Returns structured output
|
|
42
|
+
*/
|
|
43
|
+
export declare function executeCommand(connection: SshConnection, command: string, mode: SecurityMode, extraBlocklist: string[], sessionId: string, options?: ExecOptions, projectDir?: string): Promise<ExecResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Execute a command with real-time streaming output.
|
|
46
|
+
* Calls the callbacks as data arrives.
|
|
47
|
+
*/
|
|
48
|
+
export declare function executeCommandStream(connection: SshConnection, command: string, mode: SecurityMode, extraBlocklist: string[], sessionId: string, options?: ExecOptions, projectDir?: string, callbacks?: ExecStreamCallbacks): Promise<ExecResult>;
|
|
49
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/ssh/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AACpD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAGjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,wBAAwB;IACxB,UAAU,EAAE,gBAAgB,CAAA;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,aAAa,EACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,YAAY,EAClB,cAAc,EAAE,MAAM,EAAE,EACxB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,WAAgB,EACzB,UAAU,GAAE,MAAW,GACtB,OAAO,CAAC,UAAU,CAAC,CAgKrB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,aAAa,EACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,YAAY,EAClB,cAAc,EAAE,MAAM,EAAE,EACxB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,WAAgB,EACzB,UAAU,GAAE,MAAW,EACvB,SAAS,GAAE,mBAAwB,GAClC,OAAO,CAAC,UAAU,CAAC,CA4HrB"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { validateCommand } from "../security/validator.js";
|
|
2
|
+
import { sanitizeCommand } from "./sanitizer.js";
|
|
3
|
+
import { logCommand } from "./audit.js";
|
|
4
|
+
/**
|
|
5
|
+
* Execute a command on an SSH connection with security validation.
|
|
6
|
+
*
|
|
7
|
+
* This is the main execution function that:
|
|
8
|
+
* 1. Validates the command against the security policy
|
|
9
|
+
* 2. Sanitizes the input
|
|
10
|
+
* 3. Executes the command
|
|
11
|
+
* 4. Logs the result to audit
|
|
12
|
+
* 5. Returns structured output
|
|
13
|
+
*/
|
|
14
|
+
export async function executeCommand(connection, command, mode, extraBlocklist, sessionId, options = {}, projectDir = "") {
|
|
15
|
+
const startTime = Date.now();
|
|
16
|
+
// ── Step 1: Input sanitization ──
|
|
17
|
+
const sanitization = sanitizeCommand(command);
|
|
18
|
+
if (!sanitization.clean) {
|
|
19
|
+
const result = {
|
|
20
|
+
command,
|
|
21
|
+
stdout: "",
|
|
22
|
+
stderr: sanitization.reason || "Command rejected by sanitizer",
|
|
23
|
+
exitCode: 1,
|
|
24
|
+
duration: Date.now() - startTime,
|
|
25
|
+
sessionId,
|
|
26
|
+
validation: {
|
|
27
|
+
safe: false,
|
|
28
|
+
level: "destructive",
|
|
29
|
+
reason: sanitization.reason,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
// Log to audit
|
|
33
|
+
if (projectDir) {
|
|
34
|
+
logCommand(projectDir, {
|
|
35
|
+
sessionId,
|
|
36
|
+
command,
|
|
37
|
+
result: "blocked",
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
duration: result.duration,
|
|
40
|
+
reason: sanitization.reason,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
// ── Step 2: Security validation ──
|
|
46
|
+
const validation = validateCommand(command, mode, extraBlocklist);
|
|
47
|
+
// Destructive: ALWAYS blocked, no exception
|
|
48
|
+
if (validation.level === "destructive") {
|
|
49
|
+
const result = {
|
|
50
|
+
command,
|
|
51
|
+
stdout: "",
|
|
52
|
+
stderr: validation.reason || "Command is destructively blocked",
|
|
53
|
+
exitCode: 1,
|
|
54
|
+
duration: Date.now() - startTime,
|
|
55
|
+
sessionId,
|
|
56
|
+
validation,
|
|
57
|
+
};
|
|
58
|
+
if (projectDir) {
|
|
59
|
+
logCommand(projectDir, {
|
|
60
|
+
sessionId,
|
|
61
|
+
command,
|
|
62
|
+
result: "blocked",
|
|
63
|
+
exitCode: 1,
|
|
64
|
+
duration: result.duration,
|
|
65
|
+
reason: validation.reason,
|
|
66
|
+
matchedRule: validation.matched_rule,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
// Risky: The tool.ts caller handles the permission prompt via ctx.ask()
|
|
72
|
+
// If we reach here, the permission was already granted by the hook system.
|
|
73
|
+
// We just log that it was a risky command that was approved.
|
|
74
|
+
// Not in allowlist: blocked in restricted/read_only mode
|
|
75
|
+
if (validation.level === "not_in_allowlist") {
|
|
76
|
+
const result = {
|
|
77
|
+
command,
|
|
78
|
+
stdout: "",
|
|
79
|
+
stderr: validation.reason || "Command not in allowlist",
|
|
80
|
+
exitCode: 1,
|
|
81
|
+
duration: Date.now() - startTime,
|
|
82
|
+
sessionId,
|
|
83
|
+
validation,
|
|
84
|
+
};
|
|
85
|
+
if (projectDir) {
|
|
86
|
+
logCommand(projectDir, {
|
|
87
|
+
sessionId,
|
|
88
|
+
command,
|
|
89
|
+
result: "blocked",
|
|
90
|
+
exitCode: 1,
|
|
91
|
+
duration: result.duration,
|
|
92
|
+
reason: validation.reason,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
// ── Step 3: Prepare command ──
|
|
98
|
+
let execCommand = command;
|
|
99
|
+
if (options.sudo) {
|
|
100
|
+
execCommand = `sudo ${command}`;
|
|
101
|
+
}
|
|
102
|
+
// ── Step 4: Execute ──
|
|
103
|
+
try {
|
|
104
|
+
const execResult = await connection.exec(execCommand, {
|
|
105
|
+
cwd: options.cwd,
|
|
106
|
+
timeout: options.timeout,
|
|
107
|
+
});
|
|
108
|
+
const duration = Date.now() - startTime;
|
|
109
|
+
const result = {
|
|
110
|
+
command: execCommand,
|
|
111
|
+
stdout: execResult.stdout,
|
|
112
|
+
stderr: execResult.stderr,
|
|
113
|
+
exitCode: execResult.code,
|
|
114
|
+
signal: execResult.signal,
|
|
115
|
+
duration,
|
|
116
|
+
sessionId,
|
|
117
|
+
validation,
|
|
118
|
+
};
|
|
119
|
+
// ── Step 5: Audit log ──
|
|
120
|
+
if (projectDir) {
|
|
121
|
+
logCommand(projectDir, {
|
|
122
|
+
sessionId,
|
|
123
|
+
command: execCommand,
|
|
124
|
+
result: validation.level === "risky" ? "approved" : "success",
|
|
125
|
+
exitCode: execResult.code ?? 1,
|
|
126
|
+
duration,
|
|
127
|
+
matchedRule: validation.matched_rule,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
const duration = Date.now() - startTime;
|
|
134
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
135
|
+
const result = {
|
|
136
|
+
command: execCommand,
|
|
137
|
+
stdout: "",
|
|
138
|
+
stderr: errMsg,
|
|
139
|
+
exitCode: 1,
|
|
140
|
+
duration,
|
|
141
|
+
sessionId,
|
|
142
|
+
validation,
|
|
143
|
+
};
|
|
144
|
+
if (projectDir) {
|
|
145
|
+
logCommand(projectDir, {
|
|
146
|
+
sessionId,
|
|
147
|
+
command: execCommand,
|
|
148
|
+
result: "error",
|
|
149
|
+
exitCode: 1,
|
|
150
|
+
duration,
|
|
151
|
+
reason: errMsg,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Execute a command with real-time streaming output.
|
|
159
|
+
* Calls the callbacks as data arrives.
|
|
160
|
+
*/
|
|
161
|
+
export async function executeCommandStream(connection, command, mode, extraBlocklist, sessionId, options = {}, projectDir = "", callbacks = {}) {
|
|
162
|
+
const startTime = Date.now();
|
|
163
|
+
// ── Step 1 & 2: Validate and sanitize (same as non-streaming) ──
|
|
164
|
+
const sanitization = sanitizeCommand(command);
|
|
165
|
+
if (!sanitization.clean) {
|
|
166
|
+
callbacks.onError?.(new Error(sanitization.reason));
|
|
167
|
+
return {
|
|
168
|
+
command,
|
|
169
|
+
stdout: "",
|
|
170
|
+
stderr: sanitization.reason || "Command rejected",
|
|
171
|
+
exitCode: 1,
|
|
172
|
+
duration: Date.now() - startTime,
|
|
173
|
+
sessionId,
|
|
174
|
+
validation: {
|
|
175
|
+
safe: false,
|
|
176
|
+
level: "destructive",
|
|
177
|
+
reason: sanitization.reason,
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const validation = validateCommand(command, mode, extraBlocklist);
|
|
182
|
+
if (validation.level === "destructive") {
|
|
183
|
+
callbacks.onError?.(new Error(validation.reason));
|
|
184
|
+
return {
|
|
185
|
+
command,
|
|
186
|
+
stdout: "",
|
|
187
|
+
stderr: validation.reason || "Command blocked",
|
|
188
|
+
exitCode: 1,
|
|
189
|
+
duration: Date.now() - startTime,
|
|
190
|
+
sessionId,
|
|
191
|
+
validation,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (validation.level === "not_in_allowlist") {
|
|
195
|
+
callbacks.onError?.(new Error(validation.reason));
|
|
196
|
+
return {
|
|
197
|
+
command,
|
|
198
|
+
stdout: "",
|
|
199
|
+
stderr: validation.reason || "Not in allowlist",
|
|
200
|
+
exitCode: 1,
|
|
201
|
+
duration: Date.now() - startTime,
|
|
202
|
+
sessionId,
|
|
203
|
+
validation,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// ── Step 3: Execute with streaming ──
|
|
207
|
+
let execCommand = command;
|
|
208
|
+
if (options.sudo) {
|
|
209
|
+
execCommand = `sudo ${command}`;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
// Use the exec method which returns stdout/stderr
|
|
213
|
+
// For true streaming, we'd need access to the raw ssh2 stream
|
|
214
|
+
// This implementation collects output and calls callbacks
|
|
215
|
+
const execResult = await connection.exec(execCommand, {
|
|
216
|
+
cwd: options.cwd,
|
|
217
|
+
timeout: options.timeout,
|
|
218
|
+
});
|
|
219
|
+
// Simulate streaming by calling callbacks with the collected output
|
|
220
|
+
if (execResult.stdout) {
|
|
221
|
+
callbacks.onStdout?.(execResult.stdout);
|
|
222
|
+
}
|
|
223
|
+
if (execResult.stderr) {
|
|
224
|
+
callbacks.onStderr?.(execResult.stderr);
|
|
225
|
+
}
|
|
226
|
+
callbacks.onExit?.(execResult.code);
|
|
227
|
+
const duration = Date.now() - startTime;
|
|
228
|
+
if (projectDir) {
|
|
229
|
+
logCommand(projectDir, {
|
|
230
|
+
sessionId,
|
|
231
|
+
command: execCommand,
|
|
232
|
+
result: validation.level === "risky" ? "approved" : "success",
|
|
233
|
+
exitCode: execResult.code ?? 1,
|
|
234
|
+
duration,
|
|
235
|
+
matchedRule: validation.matched_rule,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
command: execCommand,
|
|
240
|
+
stdout: execResult.stdout,
|
|
241
|
+
stderr: execResult.stderr,
|
|
242
|
+
exitCode: execResult.code,
|
|
243
|
+
signal: execResult.signal,
|
|
244
|
+
duration,
|
|
245
|
+
sessionId,
|
|
246
|
+
validation,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
const duration = Date.now() - startTime;
|
|
251
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
252
|
+
callbacks.onError?.(error instanceof Error ? error : new Error(errMsg));
|
|
253
|
+
callbacks.onExit?.(1);
|
|
254
|
+
if (projectDir) {
|
|
255
|
+
logCommand(projectDir, {
|
|
256
|
+
sessionId,
|
|
257
|
+
command: execCommand,
|
|
258
|
+
result: "error",
|
|
259
|
+
exitCode: 1,
|
|
260
|
+
duration,
|
|
261
|
+
reason: errMsg,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
command: execCommand,
|
|
266
|
+
stdout: "",
|
|
267
|
+
stderr: errMsg,
|
|
268
|
+
exitCode: 1,
|
|
269
|
+
duration,
|
|
270
|
+
sessionId,
|
|
271
|
+
validation,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/ssh/executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { SshConnection, type SshConnectionConfig, type SshConnectionInfo } from "./connection.js";
|
|
2
|
+
export interface SessionManagerOptions {
|
|
3
|
+
maxSessions: number;
|
|
4
|
+
}
|
|
5
|
+
export declare class SshSessionManager {
|
|
6
|
+
private sessions;
|
|
7
|
+
private aliases;
|
|
8
|
+
private options;
|
|
9
|
+
constructor(options?: SessionManagerOptions);
|
|
10
|
+
/**
|
|
11
|
+
* Create a new SSH session.
|
|
12
|
+
*/
|
|
13
|
+
createSession(config: SshConnectionConfig): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Get a session by ID or alias.
|
|
16
|
+
*/
|
|
17
|
+
getSession(identifier: string): SshConnection | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Close a session by ID.
|
|
20
|
+
*/
|
|
21
|
+
closeSession(id: string): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Close all sessions.
|
|
24
|
+
*/
|
|
25
|
+
closeAll(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* List all active sessions.
|
|
28
|
+
*/
|
|
29
|
+
listSessions(): SshConnectionInfo[];
|
|
30
|
+
/**
|
|
31
|
+
* Get the count of active sessions.
|
|
32
|
+
*/
|
|
33
|
+
get sessionCount(): number;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a session exists.
|
|
36
|
+
*/
|
|
37
|
+
hasSession(id: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Generate a unique session ID.
|
|
40
|
+
*/
|
|
41
|
+
private generateId;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/ssh/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAGjG,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,OAAO,CAAuB;gBAE1B,OAAO,GAAE,qBAA0C;IAI/D;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAwBjE;;OAEG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAczD;;OAEG;IACG,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAehD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;IACH,YAAY,IAAI,iBAAiB,EAAE;IAQnC;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI/B;;OAEG;IACH,OAAO,CAAC,UAAU;CAGnB"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { SshConnection } from "./connection.js";
|
|
2
|
+
import { randomBytes } from "crypto";
|
|
3
|
+
export class SshSessionManager {
|
|
4
|
+
sessions = new Map();
|
|
5
|
+
aliases = new Map(); // alias → session id
|
|
6
|
+
options;
|
|
7
|
+
constructor(options = { maxSessions: 5 }) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create a new SSH session.
|
|
12
|
+
*/
|
|
13
|
+
async createSession(config) {
|
|
14
|
+
// Check max sessions limit
|
|
15
|
+
if (this.sessions.size >= this.options.maxSessions) {
|
|
16
|
+
// Close the oldest session
|
|
17
|
+
const oldestId = this.sessions.keys().next().value;
|
|
18
|
+
if (oldestId) {
|
|
19
|
+
await this.closeSession(oldestId);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const id = this.generateId();
|
|
23
|
+
const connection = new SshConnection(id, config);
|
|
24
|
+
await connection.connect();
|
|
25
|
+
this.sessions.set(id, connection);
|
|
26
|
+
if (config.alias) {
|
|
27
|
+
this.aliases.set(config.alias.toLowerCase(), id);
|
|
28
|
+
}
|
|
29
|
+
return id;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get a session by ID or alias.
|
|
33
|
+
*/
|
|
34
|
+
getSession(identifier) {
|
|
35
|
+
// Try direct ID first
|
|
36
|
+
const byId = this.sessions.get(identifier);
|
|
37
|
+
if (byId)
|
|
38
|
+
return byId;
|
|
39
|
+
// Try alias
|
|
40
|
+
const aliasId = this.aliases.get(identifier.toLowerCase());
|
|
41
|
+
if (aliasId) {
|
|
42
|
+
return this.sessions.get(aliasId);
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Close a session by ID.
|
|
48
|
+
*/
|
|
49
|
+
async closeSession(id) {
|
|
50
|
+
const connection = this.sessions.get(id);
|
|
51
|
+
if (!connection)
|
|
52
|
+
return false;
|
|
53
|
+
connection.disconnect();
|
|
54
|
+
// Remove alias if exists
|
|
55
|
+
if (connection.config.alias) {
|
|
56
|
+
this.aliases.delete(connection.config.alias.toLowerCase());
|
|
57
|
+
}
|
|
58
|
+
this.sessions.delete(id);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Close all sessions.
|
|
63
|
+
*/
|
|
64
|
+
async closeAll() {
|
|
65
|
+
for (const [id] of this.sessions) {
|
|
66
|
+
await this.closeSession(id);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* List all active sessions.
|
|
71
|
+
*/
|
|
72
|
+
listSessions() {
|
|
73
|
+
const list = [];
|
|
74
|
+
for (const connection of this.sessions.values()) {
|
|
75
|
+
list.push(connection.getInfo());
|
|
76
|
+
}
|
|
77
|
+
return list;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get the count of active sessions.
|
|
81
|
+
*/
|
|
82
|
+
get sessionCount() {
|
|
83
|
+
return this.sessions.size;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if a session exists.
|
|
87
|
+
*/
|
|
88
|
+
hasSession(id) {
|
|
89
|
+
return this.getSession(id) !== undefined;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Generate a unique session ID.
|
|
93
|
+
*/
|
|
94
|
+
generateId() {
|
|
95
|
+
return `ssh-${randomBytes(8).toString("hex")}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/ssh/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input sanitizer for SSH commands.
|
|
3
|
+
* Prevents command injection, special character attacks, and encoding tricks.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Sanitize a command string before execution.
|
|
7
|
+
*
|
|
8
|
+
* This does NOT change the command - it detects and rejects
|
|
9
|
+
* commands that contain injection attempts.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sanitizeCommand(command: string): {
|
|
12
|
+
clean: boolean;
|
|
13
|
+
reason?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Sanitize a hostname string.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sanitizeHost(host: string): {
|
|
19
|
+
clean: boolean;
|
|
20
|
+
reason?: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Sanitize a username string.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sanitizeUsername(username: string): {
|
|
26
|
+
clean: boolean;
|
|
27
|
+
reason?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Sanitize a file path for SCP operations.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sanitizePath(filePath: string): {
|
|
33
|
+
clean: boolean;
|
|
34
|
+
reason?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Escape a string for safe use in shell commands.
|
|
38
|
+
* Useful when building compound commands.
|
|
39
|
+
*/
|
|
40
|
+
export declare function escapeShellArg(arg: string): string;
|
|
41
|
+
//# sourceMappingURL=sanitizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizer.d.ts","sourceRoot":"","sources":["../../src/ssh/sanitizer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA0BpF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAgB9E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAetF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAmBlF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGlD"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input sanitizer for SSH commands.
|
|
3
|
+
* Prevents command injection, special character attacks, and encoding tricks.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Dangerous characters/sequences that could be used for injection.
|
|
7
|
+
*/
|
|
8
|
+
const INJECTION_PATTERNS = [
|
|
9
|
+
// Null byte injection
|
|
10
|
+
/\x00/,
|
|
11
|
+
// Command chaining via backticks (when not already in a string context)
|
|
12
|
+
/`[^`]*`/,
|
|
13
|
+
// ANSI escape sequences (terminal injection)
|
|
14
|
+
/\x1b\[[0-9;]*[a-zA-Z]/,
|
|
15
|
+
// Unicode direction override characters (bidirectional attacks)
|
|
16
|
+
/[\u202a-\u202e\u2066-\u2069]/,
|
|
17
|
+
// Unicode homoglyphs for common ASCII (basic detection)
|
|
18
|
+
// These are often used for visual spoofing
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Sanitize a command string before execution.
|
|
22
|
+
*
|
|
23
|
+
* This does NOT change the command - it detects and rejects
|
|
24
|
+
* commands that contain injection attempts.
|
|
25
|
+
*/
|
|
26
|
+
export function sanitizeCommand(command) {
|
|
27
|
+
if (!command || command.trim().length === 0) {
|
|
28
|
+
return { clean: false, reason: "Empty command" };
|
|
29
|
+
}
|
|
30
|
+
// Check for null bytes
|
|
31
|
+
if (command.includes("\x00")) {
|
|
32
|
+
return { clean: false, reason: "Command contains null bytes (possible injection)" };
|
|
33
|
+
}
|
|
34
|
+
// Check for ANSI escape sequences
|
|
35
|
+
if (/\x1b\[[0-9;]*[a-zA-Z]/.test(command)) {
|
|
36
|
+
return { clean: false, reason: "Command contains ANSI escape sequences (terminal injection attempt)" };
|
|
37
|
+
}
|
|
38
|
+
// Check for Unicode direction overrides
|
|
39
|
+
if (/[\u202a-\u202e\u2066-\u2069]/.test(command)) {
|
|
40
|
+
return { clean: false, reason: "Command contains Unicode direction override characters (BIDI attack attempt)" };
|
|
41
|
+
}
|
|
42
|
+
// Check for extremely long commands (possible buffer overflow attempt)
|
|
43
|
+
if (command.length > 10000) {
|
|
44
|
+
return { clean: false, reason: "Command exceeds maximum length (10,000 chars)" };
|
|
45
|
+
}
|
|
46
|
+
return { clean: true };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Sanitize a hostname string.
|
|
50
|
+
*/
|
|
51
|
+
export function sanitizeHost(host) {
|
|
52
|
+
if (!host || host.trim().length === 0) {
|
|
53
|
+
return { clean: false, reason: "Empty hostname" };
|
|
54
|
+
}
|
|
55
|
+
// Hostnames should only contain alphanumeric, hyphens, dots, and colons (for IPv6)
|
|
56
|
+
if (!/^[a-zA-Z0-9.\-:\[\]]+$/.test(host)) {
|
|
57
|
+
return { clean: false, reason: "Hostname contains invalid characters" };
|
|
58
|
+
}
|
|
59
|
+
// Basic length check
|
|
60
|
+
if (host.length > 253) {
|
|
61
|
+
return { clean: false, reason: "Hostname exceeds maximum length" };
|
|
62
|
+
}
|
|
63
|
+
return { clean: true };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Sanitize a username string.
|
|
67
|
+
*/
|
|
68
|
+
export function sanitizeUsername(username) {
|
|
69
|
+
if (!username || username.trim().length === 0) {
|
|
70
|
+
return { clean: false, reason: "Empty username" };
|
|
71
|
+
}
|
|
72
|
+
// Unix usernames typically: lowercase, digits, hyphens, underscores
|
|
73
|
+
if (!/^[a-zA-Z0-9._\-]+$/.test(username)) {
|
|
74
|
+
return { clean: false, reason: "Username contains invalid characters" };
|
|
75
|
+
}
|
|
76
|
+
if (username.length > 32) {
|
|
77
|
+
return { clean: false, reason: "Username exceeds maximum length" };
|
|
78
|
+
}
|
|
79
|
+
return { clean: true };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Sanitize a file path for SCP operations.
|
|
83
|
+
*/
|
|
84
|
+
export function sanitizePath(filePath) {
|
|
85
|
+
if (!filePath || filePath.trim().length === 0) {
|
|
86
|
+
return { clean: false, reason: "Empty file path" };
|
|
87
|
+
}
|
|
88
|
+
// Check for null bytes
|
|
89
|
+
if (filePath.includes("\x00")) {
|
|
90
|
+
return { clean: false, reason: "Path contains null bytes" };
|
|
91
|
+
}
|
|
92
|
+
// Check for command substitution in paths
|
|
93
|
+
if (/[`$()]/.test(filePath)) {
|
|
94
|
+
return { clean: false, reason: "Path contains shell metacharacters (possible injection)" };
|
|
95
|
+
}
|
|
96
|
+
// Path traversal is allowed (user might need to navigate), but log it
|
|
97
|
+
// The blocklist will catch dangerous operations anyway
|
|
98
|
+
return { clean: true };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Escape a string for safe use in shell commands.
|
|
102
|
+
* Useful when building compound commands.
|
|
103
|
+
*/
|
|
104
|
+
export function escapeShellArg(arg) {
|
|
105
|
+
// Wrap in single quotes, escaping any single quotes within
|
|
106
|
+
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=sanitizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizer.js","sourceRoot":"","sources":["../../src/ssh/sanitizer.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
|
+
import type { SecurityMode } from "./config/schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Create all SSH tools for the plugin.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createSshTools(mode?: SecurityMode, maxSessions?: number, defaultTimeout?: number, extraBlocklist?: string[]): Record<string, ToolDefinition>;
|
|
7
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAe/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAatD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,IAAI,GAAE,YAAqB,EAC3B,WAAW,GAAE,MAAU,EACvB,cAAc,GAAE,MAAW,EAC3B,cAAc,GAAE,MAAM,EAAO,GAC5B,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAmnBhC"}
|