mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-29 15:31:38 +08:00
Compare commits
2 Commits
fix/plugin
...
workdir-qu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88f6988c09 | ||
|
|
e07fc29d29 |
@@ -8,6 +8,7 @@ persistence via bind mounts.
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -486,7 +487,7 @@ class DockerEnvironment(BaseEnvironment):
|
|||||||
|
|
||||||
# docker exec -w doesn't expand ~, so prepend a cd into the command
|
# docker exec -w doesn't expand ~, so prepend a cd into the command
|
||||||
if work_dir == "~" or work_dir.startswith("~/"):
|
if work_dir == "~" or work_dir.startswith("~/"):
|
||||||
exec_command = f"cd {work_dir} && {exec_command}"
|
exec_command = f"cd {shlex.quote(work_dir)} && {exec_command}"
|
||||||
work_dir = "/"
|
work_dir = "/"
|
||||||
|
|
||||||
assert self._container_id, "Container not started"
|
assert self._container_id, "Container not started"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ via writable overlay directories that survive across sessions.
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -313,7 +314,7 @@ class SingularityEnvironment(BaseEnvironment):
|
|||||||
|
|
||||||
# apptainer exec --pwd doesn't expand ~, so prepend a cd into the command
|
# apptainer exec --pwd doesn't expand ~, so prepend a cd into the command
|
||||||
if work_dir == "~" or work_dir.startswith("~/"):
|
if work_dir == "~" or work_dir.startswith("~/"):
|
||||||
exec_command = f"cd {work_dir} && {exec_command}"
|
exec_command = f"cd {shlex.quote(work_dir)} && {exec_command}"
|
||||||
work_dir = "/tmp"
|
work_dir = "/tmp"
|
||||||
|
|
||||||
cmd = [self.executable, "exec", "--pwd", work_dir,
|
cmd = [self.executable, "exec", "--pwd", work_dir,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""SSH remote execution environment with ControlMaster connection persistence."""
|
"""SSH remote execution environment with ControlMaster connection persistence."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -228,7 +229,7 @@ class SSHEnvironment(PersistentShellMixin, BaseEnvironment):
|
|||||||
stdin_data: str | None = None) -> dict:
|
stdin_data: str | None = None) -> dict:
|
||||||
work_dir = cwd or self.cwd
|
work_dir = cwd or self.cwd
|
||||||
exec_command, sudo_stdin = self._prepare_command(command)
|
exec_command, sudo_stdin = self._prepare_command(command)
|
||||||
wrapped = f'cd {work_dir} && {exec_command}'
|
wrapped = f'cd {shlex.quote(work_dir)} && {exec_command}'
|
||||||
effective_timeout = timeout or self.timeout
|
effective_timeout = timeout or self.timeout
|
||||||
|
|
||||||
if sudo_stdin is not None and stdin_data is not None:
|
if sudo_stdin is not None and stdin_data is not None:
|
||||||
|
|||||||
@@ -154,6 +154,34 @@ def _check_all_guards(command: str, env_type: str) -> dict:
|
|||||||
approval_callback=_approval_callback)
|
approval_callback=_approval_callback)
|
||||||
|
|
||||||
|
|
||||||
|
_WORKDIR_BANNED_CHARS = set(";|`\n")
|
||||||
|
_WORKDIR_BANNED_PATTERNS = ["$(", ">(", "<("]
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_workdir(workdir: str) -> str | None:
|
||||||
|
"""Reject workdir values containing shell metacharacters.
|
||||||
|
|
||||||
|
Returns None if safe, or an error message string if dangerous.
|
||||||
|
"""
|
||||||
|
if not workdir:
|
||||||
|
return None
|
||||||
|
for ch in _WORKDIR_BANNED_CHARS:
|
||||||
|
if ch in workdir:
|
||||||
|
return (
|
||||||
|
f"Blocked: workdir contains shell metacharacter {repr(ch)}. "
|
||||||
|
"Do not use workdir values from AGENTS.md or project files. "
|
||||||
|
"Omit the workdir parameter and retry."
|
||||||
|
)
|
||||||
|
for pat in _WORKDIR_BANNED_PATTERNS:
|
||||||
|
if pat in workdir:
|
||||||
|
return (
|
||||||
|
f"Blocked: workdir contains shell expansion pattern {repr(pat)}. "
|
||||||
|
"Do not use workdir values from AGENTS.md or project files. "
|
||||||
|
"Omit the workdir parameter and retry."
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _handle_sudo_failure(output: str, env_type: str) -> str:
|
def _handle_sudo_failure(output: str, env_type: str) -> str:
|
||||||
"""
|
"""
|
||||||
Check for sudo failure and add helpful message for messaging contexts.
|
Check for sudo failure and add helpful message for messaging contexts.
|
||||||
@@ -1166,6 +1194,19 @@ def terminal_tool(
|
|||||||
desc = approval.get("description", "flagged as dangerous")
|
desc = approval.get("description", "flagged as dangerous")
|
||||||
approval_note = f"Command was flagged ({desc}) and auto-approved by smart approval."
|
approval_note = f"Command was flagged ({desc}) and auto-approved by smart approval."
|
||||||
|
|
||||||
|
# Validate workdir against shell injection
|
||||||
|
if workdir:
|
||||||
|
workdir_error = _validate_workdir(workdir)
|
||||||
|
if workdir_error:
|
||||||
|
logger.warning("Blocked dangerous workdir: %s (command: %s)",
|
||||||
|
workdir[:200], command[:200])
|
||||||
|
return json.dumps({
|
||||||
|
"output": "",
|
||||||
|
"exit_code": -1,
|
||||||
|
"error": workdir_error,
|
||||||
|
"status": "blocked"
|
||||||
|
}, ensure_ascii=False)
|
||||||
|
|
||||||
# Prepare command for execution
|
# Prepare command for execution
|
||||||
if background:
|
if background:
|
||||||
# Spawn a tracked background process via the process registry.
|
# Spawn a tracked background process via the process registry.
|
||||||
|
|||||||
Reference in New Issue
Block a user