(client: Client, command: string)
| 180 | * Execute a command on the SSH connection |
| 181 | */ |
| 182 | export function executeSSHCommand(client: Client, command: string): Promise<SSHCommandResult> { |
| 183 | return new Promise((resolve, reject) => { |
| 184 | client.exec(command, (err, stream) => { |
| 185 | if (err) { |
| 186 | reject(err) |
| 187 | return |
| 188 | } |
| 189 | |
| 190 | let stdout = '' |
| 191 | let stderr = '' |
| 192 | let stdoutBytes = 0 |
| 193 | let stderrBytes = 0 |
| 194 | let stdoutTruncated = false |
| 195 | let stderrTruncated = false |
| 196 | |
| 197 | stream.on('close', (code: number) => { |
| 198 | resolve({ |
| 199 | stdout: stdoutTruncated |
| 200 | ? `${stdout.trim()}\n[output truncated: exceeded 16MB limit]` |
| 201 | : stdout.trim(), |
| 202 | stderr: stderrTruncated |
| 203 | ? `${stderr.trim()}\n[stderr truncated: exceeded 16MB limit]` |
| 204 | : stderr.trim(), |
| 205 | exitCode: code ?? -1, |
| 206 | }) |
| 207 | }) |
| 208 | |
| 209 | stream.on('data', (data: Buffer) => { |
| 210 | const remaining = MAX_OUTPUT_BYTES - stdoutBytes |
| 211 | if (remaining <= 0) { |
| 212 | stdoutTruncated = true |
| 213 | return |
| 214 | } |
| 215 | const chunk = data.subarray(0, remaining) |
| 216 | stdout += chunk.toString() |
| 217 | stdoutBytes += chunk.length |
| 218 | if (data.length > remaining) stdoutTruncated = true |
| 219 | }) |
| 220 | |
| 221 | stream.stderr.on('data', (data: Buffer) => { |
| 222 | const remaining = MAX_OUTPUT_BYTES - stderrBytes |
| 223 | if (remaining <= 0) { |
| 224 | stderrTruncated = true |
| 225 | return |
| 226 | } |
| 227 | const chunk = data.subarray(0, remaining) |
| 228 | stderr += chunk.toString() |
| 229 | stderrBytes += chunk.length |
| 230 | if (data.length > remaining) stderrTruncated = true |
| 231 | }) |
| 232 | }) |
| 233 | }) |
| 234 | } |
| 235 | |
| 236 | /** |
| 237 | * Sanitize command input to prevent command injection |
no test coverage detected