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:
@@ -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]:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user