merge: sync with upstream NousResearch/hermes-agent (499 commits)
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 0s
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 0s
Resolves all 10 conflicts by keeping upstream versions of core files. Our 142 unique additions (wizard-bootstrap, CI, docs, tests, agent evolution) preserved. Upstream highlights: - Browser Use replaces Browserbase - notify_on_complete for background processes - Permanent command allowlist for approvals - Reasoning block display fixes - Credential pool auto-detection - Many bug fixes and improvements
This commit is contained in:
@@ -286,6 +286,26 @@ class DockerEnvironment(BaseEnvironment):
|
||||
# mode uses tmpfs (ephemeral, fast, gone on cleanup).
|
||||
from tools.environments.base import get_sandbox_dir
|
||||
|
||||
# SECURITY FIX (V-012): Block dangerous volume mounts
|
||||
# Prevent privilege escalation via Docker socket or sensitive paths
|
||||
_BLOCKED_VOLUME_PATTERNS = [
|
||||
"/var/run/docker.sock",
|
||||
"/run/docker.sock",
|
||||
"/var/run/docker.pid",
|
||||
"/proc", "/sys", "/dev",
|
||||
":/", # Root filesystem mount
|
||||
]
|
||||
|
||||
def _is_dangerous_volume(vol_spec: str) -> bool:
|
||||
"""Check if volume spec is dangerous (docker socket, root fs, etc)."""
|
||||
for pattern in _BLOCKED_VOLUME_PATTERNS:
|
||||
if pattern in vol_spec:
|
||||
return True
|
||||
# Check for docker socket variations
|
||||
if "docker.sock" in vol_spec.lower():
|
||||
return True
|
||||
return False
|
||||
|
||||
# User-configured volume mounts (from config.yaml docker_volumes)
|
||||
volume_args = []
|
||||
workspace_explicitly_mounted = False
|
||||
@@ -296,6 +316,15 @@ class DockerEnvironment(BaseEnvironment):
|
||||
vol = vol.strip()
|
||||
if not vol:
|
||||
continue
|
||||
|
||||
# SECURITY FIX (V-012): Block dangerous volumes
|
||||
if _is_dangerous_volume(vol):
|
||||
logger.error(
|
||||
f"SECURITY: Refusing to mount dangerous volume '{vol}'. "
|
||||
f"Docker socket and system paths are blocked to prevent container escape."
|
||||
)
|
||||
continue # Skip this dangerous volume
|
||||
|
||||
if ":" in vol:
|
||||
volume_args.extend(["-v", vol])
|
||||
if ":/workspace" in vol:
|
||||
@@ -575,22 +604,48 @@ class DockerEnvironment(BaseEnvironment):
|
||||
"""Stop and remove the container. Bind-mount dirs persist if persistent=True."""
|
||||
if self._container_id:
|
||||
try:
|
||||
# SECURITY FIX: Use list-based commands instead of shell=True
|
||||
# to prevent command injection via malicious container IDs
|
||||
# Stop in background so cleanup doesn't block
|
||||
stop_cmd = (
|
||||
f"(timeout 60 {self._docker_exe} stop {self._container_id} || "
|
||||
f"{self._docker_exe} rm -f {self._container_id}) >/dev/null 2>&1 &"
|
||||
container_id = self._container_id
|
||||
# Validate container ID format to prevent injection
|
||||
if not re.match(r'^[a-f0-9]{12,64}$', container_id):
|
||||
logger.warning("Invalid container ID format: %s", container_id)
|
||||
return
|
||||
|
||||
# Use subprocess with list args instead of shell=True
|
||||
subprocess.Popen(
|
||||
["timeout", "60", self._docker_exe, "stop", container_id],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
subprocess.Popen(stop_cmd, shell=True)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to stop container %s: %s", self._container_id, e)
|
||||
|
||||
if not self._persistent:
|
||||
# Also schedule removal (stop only leaves it as stopped)
|
||||
try:
|
||||
subprocess.Popen(
|
||||
f"sleep 3 && {self._docker_exe} rm -f {self._container_id} >/dev/null 2>&1 &",
|
||||
shell=True,
|
||||
# Use a delayed removal via threading instead of shell
|
||||
def delayed_remove(docker_exe, container_id, delay=3):
|
||||
import time
|
||||
time.sleep(delay)
|
||||
try:
|
||||
subprocess.run(
|
||||
[docker_exe, "rm", "-f", container_id],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=False,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
import threading
|
||||
remove_thread = threading.Thread(
|
||||
target=delayed_remove,
|
||||
args=(self._docker_exe, self._container_id, 3),
|
||||
daemon=True,
|
||||
)
|
||||
remove_thread.start()
|
||||
except Exception:
|
||||
pass
|
||||
self._container_id = None
|
||||
|
||||
Reference in New Issue
Block a user