@@ -235,12 +235,12 @@ def write_stdin(stdin, buffer):
235235def remove_any_sudo_askpass_file (host ) -> None :
236236 sudo_askpass_path = host .connector_data .get ("sudo_askpass_path" )
237237 if sudo_askpass_path :
238- host .run_shell_command ("rm -f {0}" . format (sudo_askpass_path ))
238+ host .run_shell_command (StringCommand ( "rm" , "-f" , QuoteString (sudo_askpass_path ) ))
239239 host .connector_data ["sudo_askpass_path" ] = None
240240
241241 su_askpass_path = host .connector_data .get ("su_askpass_path" )
242242 if su_askpass_path :
243- host .run_shell_command ("rm -f {0}" . format (su_askpass_path ))
243+ host .run_shell_command (StringCommand ( "rm" , "-f" , QuoteString (su_askpass_path ) ))
244244 host .connector_data ["su_askpass_path" ] = None
245245
246246
@@ -293,7 +293,7 @@ def _ensure_askpass_set_for_host(host: "Host", key: str, env_var: str):
293293 )
294294 )
295295
296- host .connector_data [key ] = StringCommand ( QuoteString ( output .stdout_lines [0 ])). get_raw_value ()
296+ host .connector_data [key ] = output .stdout_lines [0 ]
297297
298298
299299def make_unix_command_for_host (
@@ -367,31 +367,38 @@ def make_unix_command(
367367 _shell_executable = "sh"
368368
369369 if _env :
370- env_string = " " .join (['"{0}={1}"' .format (key , value ) for key , value in _env .items ()])
371- command = StringCommand ("export" , env_string , "&&" , command )
370+ env_bits : list [Union [str , StringCommand , QuoteString ]] = ["export" ]
371+ for key , value in _env .items ():
372+ # Quote the whole `key=value` pair so arbitrary values cannot break
373+ # out into additional shell tokens. Invalid identifiers in `key` will
374+ # fail safely when the shell rejects the resulting `export` statement.
375+ env_bits .append (QuoteString ("{0}={1}" .format (key , value )))
376+ env_bits .append ("&&" )
377+ env_bits .append (command )
378+ command = StringCommand (* env_bits )
372379
373380 if _chdir :
374- command = StringCommand ("cd" , _chdir , "&&" , command )
381+ command = StringCommand ("cd" , QuoteString ( _chdir ) , "&&" , command )
375382
376383 command_bits : list [Union [str , StringCommand , QuoteString ]] = []
377384
378385 if _doas :
379386 command_bits .extend (["doas" , "-n" ])
380387
381388 if _doas_user :
382- command_bits .extend (["-u" , _doas_user ])
389+ command_bits .extend (["-u" , QuoteString ( _doas_user ) ])
383390
384391 if _dzdo :
385392 command_bits .extend (["dzdo" , "-H" , "-n" ])
386393
387394 if _dzdo_user :
388- command_bits .extend (["-u" , _dzdo_user ])
395+ command_bits .extend (["-u" , QuoteString ( _dzdo_user ) ])
389396
390397 if _sudo_password and _sudo_askpass_path :
391398 command_bits .extend (
392399 [
393400 "env" ,
394- "SUDO_ASKPASS={0}" . format (_sudo_askpass_path ),
401+ StringCommand ( "SUDO_ASKPASS=" , QuoteString (_sudo_askpass_path ), _separator = "" ),
395402 MaskString (
396403 "{0}={1}" .format (
397404 SUDO_ASKPASS_ENV_VAR ,
@@ -416,7 +423,7 @@ def make_unix_command(
416423 command_bits .append ("-E" )
417424
418425 if _sudo_user :
419- command_bits .extend (("-u" , _sudo_user ))
426+ command_bits .extend (("-u" , QuoteString ( _sudo_user ) ))
420427
421428 if _su_user :
422429 if _su_password and _su_askpass_path :
@@ -429,7 +436,7 @@ def make_unix_command(
429436 StringCommand (QuoteString (_su_password )).get_raw_value (),
430437 )
431438 ),
432- _su_askpass_path ,
439+ QuoteString ( _su_askpass_path ) ,
433440 "|" ,
434441 ],
435442 )
@@ -444,9 +451,16 @@ def make_unix_command(
444451 command_bits .append ("-m" )
445452
446453 if _su_shell :
447- command_bits .extend (["-s" , "`which {0}`" .format (_su_shell )])
454+ # Resolve the shell via `command -v`, with the user-supplied shell
455+ # name safely quoted so it cannot inject extra shell syntax.
456+ command_bits .extend (
457+ [
458+ "-s" ,
459+ StringCommand ("$(command -v " , QuoteString (_su_shell ), ")" , _separator = "" ),
460+ ]
461+ )
448462
449- command_bits .extend ([_su_user , "-c" ])
463+ command_bits .extend ([QuoteString ( _su_user ) , "-c" ])
450464
451465 if _shell_executable is not None :
452466 # Quote the whole shell -c 'command' as BSD `su` does not have a shell option
0 commit comments