From b7d61ed7c95cc9af9b69a4827059ab798e773afb Mon Sep 17 00:00:00 2001 From: Ayushh Garg Date: Tue, 17 Mar 2026 12:17:03 +0530 Subject: [PATCH 1/5] injection fix using shell=false --- .../utilities/commandline_utility.py | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py index 3f41e5f0ffab..f7f8eb162ba1 100644 --- a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py +++ b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py @@ -1,18 +1,18 @@ -# --------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# --------------------------------------------------------- - import json import os import subprocess import sys import time - +from unittest import mock from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MlException def _print_command_results(test_passed, time_taken, output): - print("Command {} in {} seconds.".format("successful" if test_passed else "failed", time_taken)) + print( + "Command {} in {} seconds.".format( + "successful" if test_passed else "failed", time_taken + ) + ) print("Output: \n{}\n".format(output)) @@ -27,36 +27,51 @@ def run_cli_command( if not custom_environment: custom_environment = os.environ - # We do this join to construct a command because "shell=True" flag, used below, doesn't work with the vector - # argv form on a mac OS. - command_to_execute = " ".join(cmd_arguments) + # Use argv form with shell=False to avoid shell injection risks while keeping behavior + # consistent across platforms (including macOS). + # On Windows, many CLI tools (e.g., "code") are .cmd/.bat shims that require shell + # execution. We use subprocess.list2cmdline to safely quote the arguments before + # passing them to the shell, preventing command injection. - if not do_not_print: # Avoid printing the az login service principal password, for example - print("Preparing to run CLI command: \n{}\n".format(command_to_execute)) + if ( + not do_not_print + ): # Avoid printing the az login service principal password, for example + print("Preparing to run CLI command: \n{}\n".format(" ".join(cmd_arguments))) print("Current directory: {}".format(os.getcwd())) start_time = time.time() try: # We redirect stderr to stdout, so that in the case of an error, especially in negative tests, # we get the error reply back to check if the error is expected or not. - # We need "shell=True" flag so that the "az" wrapper works. # We also pass the environment variables, because for some tests we modify # the environment variables. subprocess_args = { - "shell": True, "stderr": subprocess.STDOUT, "env": custom_environment, } if not stderr_to_stdout: - subprocess_args = {"shell": True, "env": custom_environment} + subprocess_args = {"env": custom_environment} if sys.version_info[0] != 2: subprocess_args["timeout"] = timeout - output = subprocess.check_output(command_to_execute, **subprocess_args).decode(encoding="UTF-8") + # On Windows, many CLI commands are provided as .cmd/.bat shims that require + # shell execution. Use list2cmdline to build a safely quoted command string + # when invoking via the shell. + if os.name == "nt": + command_to_execute = subprocess.list2cmdline(cmd_arguments) + subprocess_args["shell"] = True + cmd_to_run = command_to_execute + else: + subprocess_args["shell"] = False + cmd_to_run = cmd_arguments + + output = subprocess.check_output(cmd_to_run, **subprocess_args).decode( + encoding="UTF-8" + ) time_taken = time.time() - start_time if not do_not_print: @@ -108,4 +123,4 @@ def exclude_warnings(cmd_output): curr_index = curr_index + 1 - return json_output + return json_output \ No newline at end of file From d7fc0a87861e62dc94947e9ac88c881cf6e0c07d Mon Sep 17 00:00:00 2001 From: Ayushh Garg Date: Tue, 17 Mar 2026 12:48:51 +0530 Subject: [PATCH 2/5] original change --- .../utilities/commandline_utility.py | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py index f7f8eb162ba1..4e839eb23784 100644 --- a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py +++ b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py @@ -1,9 +1,13 @@ +# --------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# --------------------------------------------------------- + import json import os import subprocess import sys import time -from unittest import mock + from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MlException @@ -27,12 +31,6 @@ def run_cli_command( if not custom_environment: custom_environment = os.environ - # Use argv form with shell=False to avoid shell injection risks while keeping behavior - # consistent across platforms (including macOS). - # On Windows, many CLI tools (e.g., "code") are .cmd/.bat shims that require shell - # execution. We use subprocess.list2cmdline to safely quote the arguments before - # passing them to the shell, preventing command injection. - if ( not do_not_print ): # Avoid printing the az login service principal password, for example @@ -48,28 +46,18 @@ def run_cli_command( # the environment variables. subprocess_args = { + "shell": False, "stderr": subprocess.STDOUT, "env": custom_environment, } if not stderr_to_stdout: - subprocess_args = {"env": custom_environment} + subprocess_args = {"shell": False, "env": custom_environment} if sys.version_info[0] != 2: subprocess_args["timeout"] = timeout - # On Windows, many CLI commands are provided as .cmd/.bat shims that require - # shell execution. Use list2cmdline to build a safely quoted command string - # when invoking via the shell. - if os.name == "nt": - command_to_execute = subprocess.list2cmdline(cmd_arguments) - subprocess_args["shell"] = True - cmd_to_run = command_to_execute - else: - subprocess_args["shell"] = False - cmd_to_run = cmd_arguments - - output = subprocess.check_output(cmd_to_run, **subprocess_args).decode( + output = subprocess.check_output(cmd_arguments, **subprocess_args).decode( encoding="UTF-8" ) @@ -123,4 +111,4 @@ def exclude_warnings(cmd_output): curr_index = curr_index + 1 - return json_output \ No newline at end of file + return json_output From fd805367d1fe514785f04b273a2026a592b57539 Mon Sep 17 00:00:00 2001 From: Ayushh Garg Date: Tue, 17 Mar 2026 13:34:40 +0530 Subject: [PATCH 3/5] ubuntu pipeline fix --- .../ml/_local_endpoints/utilities/commandline_utility.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py index 4e839eb23784..773c7723d15d 100644 --- a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py +++ b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py @@ -28,6 +28,13 @@ def run_cli_command( do_not_print=True, stderr_to_stdout=True, ): + # Ensure cmd_arguments is always a list for shell=False safety. + # Some callers may pass a pre-joined string; split it to maintain + # compatibility while keeping shell=False. + if isinstance(cmd_arguments, str): + import shlex + cmd_arguments = shlex.split(cmd_arguments) + if not custom_environment: custom_environment = os.environ From 477025774559bc32fba2946bd88d1b3dad11afa2 Mon Sep 17 00:00:00 2001 From: Ayushh Garg Date: Tue, 17 Mar 2026 13:38:50 +0530 Subject: [PATCH 4/5] cosmetic change --- .../ai/ml/_local_endpoints/utilities/commandline_utility.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py index 773c7723d15d..f7f74cf3eda0 100644 --- a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py +++ b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py @@ -7,7 +7,7 @@ import subprocess import sys import time - +import shlex from azure.ai.ml.exceptions import ErrorCategory, ErrorTarget, MlException @@ -32,9 +32,8 @@ def run_cli_command( # Some callers may pass a pre-joined string; split it to maintain # compatibility while keeping shell=False. if isinstance(cmd_arguments, str): - import shlex cmd_arguments = shlex.split(cmd_arguments) - + if not custom_environment: custom_environment = os.environ From 14ef8efb0c34411cad26d61b0e045fc5aec1b51b Mon Sep 17 00:00:00 2001 From: Ayushh Garg Date: Wed, 18 Mar 2026 13:17:23 +0530 Subject: [PATCH 5/5] tox formatting --- .../utilities/commandline_utility.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py index f7f74cf3eda0..7fc5f54dedba 100644 --- a/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py +++ b/sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py @@ -12,11 +12,7 @@ def _print_command_results(test_passed, time_taken, output): - print( - "Command {} in {} seconds.".format( - "successful" if test_passed else "failed", time_taken - ) - ) + print("Command {} in {} seconds.".format("successful" if test_passed else "failed", time_taken)) print("Output: \n{}\n".format(output)) @@ -37,9 +33,7 @@ def run_cli_command( if not custom_environment: custom_environment = os.environ - if ( - not do_not_print - ): # Avoid printing the az login service principal password, for example + if not do_not_print: # Avoid printing the az login service principal password, for example print("Preparing to run CLI command: \n{}\n".format(" ".join(cmd_arguments))) print("Current directory: {}".format(os.getcwd())) @@ -63,9 +57,7 @@ def run_cli_command( if sys.version_info[0] != 2: subprocess_args["timeout"] = timeout - output = subprocess.check_output(cmd_arguments, **subprocess_args).decode( - encoding="UTF-8" - ) + output = subprocess.check_output(cmd_arguments, **subprocess_args).decode(encoding="UTF-8") time_taken = time.time() - start_time if not do_not_print: