Skip to content

Commit cc3dbfb

Browse files
committed
quote arguments in run_cli_command before shell execution
1 parent 1991922 commit cc3dbfb

3 files changed

Lines changed: 60 additions & 3 deletions

File tree

sdk/ml/azure-ai-ml/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
### Bugs Fixed
88

9+
- Quote each argument in the local-endpoint `run_cli_command` helper before running it under `shell=True`, so a path containing spaces or shell metacharacters is passed through literally instead of being re-interpreted by the shell.
910
- Fixed cross-tenant registry endpoint resolution for deployment template operations by using the registry discovery API instead of ARM calls.
1011
- Fixed deployment template update failing with immutable field errors by ensuring `allowedInstanceType` and `allowedEnvironmentVariableOverrides` are properly round-tripped during serialization.
1112

sdk/ml/azure-ai-ml/azure/ai/ml/_local_endpoints/utilities/commandline_utility.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import json
66
import os
7+
import shlex
78
import subprocess
89
import sys
910
import time
@@ -27,9 +28,14 @@ def run_cli_command(
2728
if not custom_environment:
2829
custom_environment = os.environ
2930

30-
# We do this join to construct a command because "shell=True" flag, used below, doesn't work with the vector
31-
# argv form on a mac OS.
32-
command_to_execute = " ".join(cmd_arguments)
31+
# "shell=True" (used below) is needed so the platform "az"/"code" wrappers resolve, and it doesn't work with
32+
# the vector argv form on macOS, so the arguments are collapsed into a single command string. Quote each
33+
# argument while joining so a value containing spaces or shell metacharacters is passed through literally
34+
# instead of being re-interpreted by the shell.
35+
if os.name == "nt":
36+
command_to_execute = subprocess.list2cmdline(cmd_arguments)
37+
else:
38+
command_to_execute = shlex.join(cmd_arguments)
3339

3440
if not do_not_print: # Avoid printing the az login service principal password, for example
3541
print("Preparing to run CLI command: \n{}\n".format(command_to_execute))
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# ---------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# ---------------------------------------------------------
4+
5+
6+
import os
7+
import shlex
8+
import subprocess
9+
from unittest.mock import patch
10+
11+
import pytest
12+
13+
from azure.ai.ml._local_endpoints.utilities.commandline_utility import run_cli_command
14+
15+
16+
def _expected_command(args):
17+
return subprocess.list2cmdline(args) if os.name == "nt" else shlex.join(args)
18+
19+
20+
@pytest.mark.unittest
21+
class TestRunCliCommand:
22+
def test_arguments_are_quoted_before_shell_execution(self):
23+
# An argument carrying shell metacharacters (e.g. a deployment-derived path component).
24+
args = ["echo", "x$(touch pwned)"]
25+
26+
with patch(
27+
"azure.ai.ml._local_endpoints.utilities.commandline_utility.subprocess.check_output",
28+
return_value=b"",
29+
) as mock_check_output:
30+
run_cli_command(args)
31+
32+
command_to_execute = mock_check_output.call_args[0][0]
33+
# The raw space-join would let the shell evaluate the $(...) substitution.
34+
assert command_to_execute != " ".join(args)
35+
assert command_to_execute == _expected_command(args)
36+
37+
def test_plain_uri_argument_is_unchanged(self):
38+
uri = "vscode-remote://dev-container+deadbeef/var/azureml-app/onlinescoring"
39+
args = ["code", "--folder-uri", uri]
40+
41+
with patch(
42+
"azure.ai.ml._local_endpoints.utilities.commandline_utility.subprocess.check_output",
43+
return_value=b"",
44+
) as mock_check_output:
45+
run_cli_command(args)
46+
47+
command_to_execute = mock_check_output.call_args[0][0]
48+
assert command_to_execute == _expected_command(args)
49+
if os.name != "nt":
50+
assert command_to_execute == f"code --folder-uri {uri}"

0 commit comments

Comments
 (0)