Merge pull request #1 from hobostay/fix/security-hardening-tool-exec

Security & robustness: command injection, path traversal, approval bypass in tool/market layer
This commit is contained in:
LZH-YS1998
2026-07-04 18:34:49 +08:00
14 changed files with 207 additions and 14 deletions
+9 -2
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import shlex
from typing import Any
from opc.layer4_tools.shell import shell_exec
@@ -16,7 +17,11 @@ async def git_commit(message: str, working_directory: str = ".", add_all: bool =
cmds = []
if add_all:
cmds.append("git add -A")
cmds.append(f'git commit -m "{message}"')
# The message flows through ``shell_exec`` -> ``bash -lc "<command>"``, so it is
# shell-interpolated. Quote it to prevent a crafted message (e.g.
# ``foo" && rm -rf / #``) from injecting arbitrary commands. Only the literal
# commit message must reach ``git commit``.
cmds.append(f"git commit -m {shlex.quote(str(message))}")
return await shell_exec(" && ".join(cmds), working_directory=working_directory)
@@ -26,7 +31,9 @@ async def git_diff(working_directory: str = ".", staged: bool = False) -> dict[s
async def git_clone(url: str, directory: str = ".") -> dict[str, Any]:
return await shell_exec(f"git clone {url}", working_directory=directory, timeout=300)
# Quote the URL: it is interpolated into a ``bash -lc`` command and a value like
# ``https://x.git; rm -rf /`` or ``$(curl ...)`` would otherwise be executed.
return await shell_exec(f"git clone {shlex.quote(str(url))}", working_directory=directory, timeout=300)
def create_git_tools() -> list[ToolDefinition]:
+6 -1
View File
@@ -118,7 +118,12 @@ async def _run_shell_command(
if active_prefix and active_prefix not in command:
separator = _POWERSHELL_CMD_SEPARATOR if is_powershell else _BASH_CMD_SEPARATOR
command = f"{active_prefix}{separator}{command}"
args = [args[0], args[1], command] if len(args) >= 2 else args
# Replace only the trailing command argument. PowerShell args are
# [exe, "-NoProfile", "-Command", command] (4 elements); the previous
# ``[args[0], args[1], command]`` dropped the "-Command" flag and left
# PowerShell unable to interpret the prefixed command. Bash args are
# [bash, "-lc", command] (3 elements), handled identically here.
args = [*args[:-1], command] if len(args) >= 1 else args
context = resolve_task_execution_context(task)
if resolved_cwd and not context.get("workspace_root"):
context["workspace_root"] = resolved_cwd