11import { spawn } from 'child_process' ;
22import { spawn as ptySpawn } from 'node-pty' ;
3- import { existsSync } from 'fs' ;
3+ import { existsSync , writeFileSync , chmodSync , unlinkSync } from 'fs' ;
4+ import { join } from 'path' ;
5+ import { tmpdir } from 'os' ;
46
57
68/**
@@ -194,26 +196,45 @@ class SSHExecutionService {
194196 */
195197 async transferScriptsFolder ( server , onData , onError ) {
196198 const { ip, user, password, auth_type = 'password' , ssh_key_passphrase, ssh_key_path, ssh_port = 22 } = server ;
197-
199+
200+ const cleanupTempFile = ( /** @type {string | null } */ tempPath ) => {
201+ if ( tempPath ) {
202+ try {
203+ unlinkSync ( tempPath ) ;
204+ } catch ( _ ) {
205+ // ignore
206+ }
207+ }
208+ } ;
209+
198210 return new Promise ( ( resolve , reject ) => {
211+ /** @type {string | null } */
212+ let tempPath = null ;
199213 try {
200- // Build rsync command based on authentication type
214+ // Build rsync command based on authentication type.
215+ // Use sshpass -f with a temp file so password/passphrase never go through the shell (safe for special chars like {, $, ").
201216 let rshCommand ;
202217 if ( auth_type === 'key' ) {
203218 if ( ! ssh_key_path || ! existsSync ( ssh_key_path ) ) {
204219 throw new Error ( 'SSH key file not found' ) ;
205220 }
206-
221+
207222 if ( ssh_key_passphrase ) {
208- rshCommand = `sshpass -P passphrase -p ${ ssh_key_passphrase } ssh -i ${ ssh_key_path } -p ${ ssh_port } -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null` ;
223+ tempPath = join ( tmpdir ( ) , `sshpass-${ process . pid } -${ Date . now ( ) } .tmp` ) ;
224+ writeFileSync ( tempPath , ssh_key_passphrase ) ;
225+ chmodSync ( tempPath , 0o600 ) ;
226+ rshCommand = `sshpass -P passphrase -f ${ tempPath } ssh -i ${ ssh_key_path } -p ${ ssh_port } -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null` ;
209227 } else {
210228 rshCommand = `ssh -i ${ ssh_key_path } -p ${ ssh_port } -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null` ;
211229 }
212230 } else {
213231 // Password authentication
214- rshCommand = `sshpass -p ${ password } ssh -p ${ ssh_port } -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null` ;
232+ tempPath = join ( tmpdir ( ) , `sshpass-${ process . pid } -${ Date . now ( ) } .tmp` ) ;
233+ writeFileSync ( tempPath , password ?? '' ) ;
234+ chmodSync ( tempPath , 0o600 ) ;
235+ rshCommand = `sshpass -f ${ tempPath } ssh -p ${ ssh_port } -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null` ;
215236 }
216-
237+
217238 const rsyncCommand = spawn ( 'rsync' , [
218239 '-avz' ,
219240 '--delete' ,
@@ -226,31 +247,31 @@ class SSHExecutionService {
226247 stdio : [ 'pipe' , 'pipe' , 'pipe' ]
227248 } ) ;
228249
229- rsyncCommand . stdout . on ( 'data' , ( /** @type {Buffer } */ data ) => {
230- // Ensure proper UTF-8 encoding for ANSI colors
231- const output = data . toString ( 'utf8' ) ;
232- onData ( output ) ;
233- } ) ;
250+ rsyncCommand . stdout . on ( 'data' , ( /** @type {Buffer } */ data ) => {
251+ const output = data . toString ( 'utf8' ) ;
252+ onData ( output ) ;
253+ } ) ;
234254
235- rsyncCommand . stderr . on ( 'data' , ( /** @type {Buffer } */ data ) => {
236- // Ensure proper UTF-8 encoding for ANSI colors
237- const output = data . toString ( 'utf8' ) ;
238- onError ( output ) ;
239- } ) ;
255+ rsyncCommand . stderr . on ( 'data' , ( /** @type {Buffer } */ data ) => {
256+ const output = data . toString ( 'utf8' ) ;
257+ onError ( output ) ;
258+ } ) ;
240259
241- rsyncCommand . on ( 'close' , ( code ) => {
242- if ( code === 0 ) {
243- resolve ( ) ;
244- } else {
245- reject ( new Error ( `rsync failed with code ${ code } ` ) ) ;
246- }
247- } ) ;
260+ rsyncCommand . on ( 'close' , ( code ) => {
261+ cleanupTempFile ( tempPath ) ;
262+ if ( code === 0 ) {
263+ resolve ( ) ;
264+ } else {
265+ reject ( new Error ( `rsync failed with code ${ code } ` ) ) ;
266+ }
267+ } ) ;
248268
249- rsyncCommand . on ( 'error' , ( error ) => {
250- reject ( error ) ;
251- } ) ;
252-
269+ rsyncCommand . on ( 'error' , ( error ) => {
270+ cleanupTempFile ( tempPath ) ;
271+ reject ( error ) ;
272+ } ) ;
253273 } catch ( error ) {
274+ cleanupTempFile ( tempPath ) ;
254275 reject ( error ) ;
255276 }
256277 } ) ;
0 commit comments