mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(docker): remove --read-only and allow exec on /tmp for package installs
The Docker sandbox previously used --read-only on the root filesystem and noexec on /tmp. This broke 30+ skills that need to install packages: - npm install -g (codex, claude-code, mcporter, powerpoint) - pip install (20+ mlops/media/productivity skills) - apt install (minecraft-modpack-server, ml-paper-writing) - Build tools that compile in /tmp (pip wheels, node-gyp) The container is already fully isolated from the host. Industry standard (E2B, Docker Sandboxes, OpenAI Codex) does not use --read-only — the container itself is the security boundary. Retained security hardening: - --cap-drop ALL (zero capabilities) - --security-opt no-new-privileges (no escalation) - --pids-limit 256 (no fork bombs) - Size-limited tmpfs for /tmp, /var/tmp, /run - nosuid on all tmpfs mounts - noexec on /var/tmp and /run (rarely need exec there) - Resource limits (CPU, memory, disk) - Ephemeral containers (destroyed after use) Fixes #189.
This commit is contained in:
parent
e265006fd6
commit
866fd9476b
3 changed files with 19 additions and 15 deletions
|
|
@ -411,7 +411,7 @@ Hermes has terminal access. Security matters.
|
||||||
| **Write deny list** | Protected paths (`~/.ssh/authorized_keys`, `/etc/shadow`) resolved via `os.path.realpath()` to prevent symlink bypass |
|
| **Write deny list** | Protected paths (`~/.ssh/authorized_keys`, `/etc/shadow`) resolved via `os.path.realpath()` to prevent symlink bypass |
|
||||||
| **Skills guard** | Security scanner for hub-installed skills (`tools/skills_guard.py`) |
|
| **Skills guard** | Security scanner for hub-installed skills (`tools/skills_guard.py`) |
|
||||||
| **Code execution sandbox** | `execute_code` child process runs with API keys stripped from environment |
|
| **Code execution sandbox** | `execute_code` child process runs with API keys stripped from environment |
|
||||||
| **Container hardening** | Docker: read-only root, all capabilities dropped, no privilege escalation, PID limits |
|
| **Container hardening** | Docker: all capabilities dropped, no privilege escalation, PID limits, size-limited tmpfs |
|
||||||
|
|
||||||
### When contributing security-sensitive code
|
### When contributing security-sensitive code
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -769,7 +769,7 @@ Hermes includes multiple layers of security beyond sandboxed terminals and exec
|
||||||
| **Write deny list with symlink resolution** | Protected paths (`~/.ssh/authorized_keys`, `/etc/shadow`, etc.) are resolved via `os.path.realpath()` before comparison, preventing symlink bypass |
|
| **Write deny list with symlink resolution** | Protected paths (`~/.ssh/authorized_keys`, `/etc/shadow`, etc.) are resolved via `os.path.realpath()` before comparison, preventing symlink bypass |
|
||||||
| **Recursive delete false-positive fix** | Dangerous command detection uses precise flag-matching to avoid blocking safe commands |
|
| **Recursive delete false-positive fix** | Dangerous command detection uses precise flag-matching to avoid blocking safe commands |
|
||||||
| **Code execution sandbox** | `execute_code` scripts run in a child process with API keys and credentials stripped from the environment |
|
| **Code execution sandbox** | `execute_code` scripts run in a child process with API keys and credentials stripped from the environment |
|
||||||
| **Container hardening** | Docker containers run with read-only root, all capabilities dropped, no privilege escalation, PID limits |
|
| **Container hardening** | Docker containers run with all capabilities dropped, no privilege escalation, PID limits, size-limited tmpfs |
|
||||||
| **DM pairing** | Cryptographically random pairing codes with 1-hour expiry and rate limiting |
|
| **DM pairing** | Cryptographically random pairing codes with 1-hour expiry and rate limiting |
|
||||||
| **User allowlists** | Default deny-all for messaging platforms; explicit allowlists or DM pairing required |
|
| **User allowlists** | Default deny-all for messaging platforms; explicit allowlists or DM pairing required |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
"""Docker execution environment wrapping mini-swe-agent's DockerEnvironment.
|
"""Docker execution environment wrapping mini-swe-agent's DockerEnvironment.
|
||||||
|
|
||||||
Adds security hardening, configurable resource limits (CPU, memory, disk),
|
Adds security hardening (cap-drop ALL, no-new-privileges, PID limits),
|
||||||
and optional filesystem persistence via `docker commit`/`docker create --image`.
|
configurable resource limits (CPU, memory, disk), and optional filesystem
|
||||||
|
persistence via bind mounts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -19,13 +20,15 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Security flags applied to every container
|
# Security flags applied to every container.
|
||||||
|
# The container itself is the security boundary (isolated from host).
|
||||||
|
# We drop all capabilities, block privilege escalation, and limit PIDs.
|
||||||
|
# /tmp is size-limited and nosuid but allows exec (needed by pip/npm builds).
|
||||||
_SECURITY_ARGS = [
|
_SECURITY_ARGS = [
|
||||||
"--read-only",
|
|
||||||
"--cap-drop", "ALL",
|
"--cap-drop", "ALL",
|
||||||
"--security-opt", "no-new-privileges",
|
"--security-opt", "no-new-privileges",
|
||||||
"--pids-limit", "256",
|
"--pids-limit", "256",
|
||||||
"--tmpfs", "/tmp:rw,noexec,nosuid,size=512m",
|
"--tmpfs", "/tmp:rw,nosuid,size=512m",
|
||||||
"--tmpfs", "/var/tmp:rw,noexec,nosuid,size=256m",
|
"--tmpfs", "/var/tmp:rw,noexec,nosuid,size=256m",
|
||||||
"--tmpfs", "/run:rw,noexec,nosuid,size=64m",
|
"--tmpfs", "/run:rw,noexec,nosuid,size=64m",
|
||||||
]
|
]
|
||||||
|
|
@ -37,12 +40,13 @@ _storage_opt_ok: Optional[bool] = None # cached result across instances
|
||||||
class DockerEnvironment(BaseEnvironment):
|
class DockerEnvironment(BaseEnvironment):
|
||||||
"""Hardened Docker container execution with resource limits and persistence.
|
"""Hardened Docker container execution with resource limits and persistence.
|
||||||
|
|
||||||
Security: read-only root, all capabilities dropped, no privilege escalation,
|
Security: all capabilities dropped, no privilege escalation, PID limits,
|
||||||
PID limits, tmpfs for writable scratch. Writable overlay for /home and cwd
|
size-limited tmpfs for scratch dirs. The container itself is the security
|
||||||
via tmpfs or bind mounts.
|
boundary — the filesystem inside is writable so agents can install packages
|
||||||
|
(pip, npm, apt) as needed. Writable workspace via tmpfs or bind mounts.
|
||||||
|
|
||||||
Persistence: when enabled, `docker commit` saves the container state on
|
Persistence: when enabled, bind mounts preserve /workspace and /root
|
||||||
cleanup, and the next creation restores from that image.
|
across container restarts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -114,9 +118,9 @@ class DockerEnvironment(BaseEnvironment):
|
||||||
"--tmpfs", "/root:rw,exec,size=1g",
|
"--tmpfs", "/root:rw,exec,size=1g",
|
||||||
]
|
]
|
||||||
|
|
||||||
# All containers get full security hardening (read-only root + writable
|
# All containers get security hardening (capabilities dropped, no privilege
|
||||||
# mounts for the workspace). Persistence uses Docker volumes, not
|
# escalation, PID limits). The container filesystem is writable so agents
|
||||||
# filesystem layer commits, so --read-only is always safe.
|
# can install packages as needed.
|
||||||
# User-configured volume mounts (from config.yaml docker_volumes)
|
# User-configured volume mounts (from config.yaml docker_volumes)
|
||||||
volume_args = []
|
volume_args = []
|
||||||
for vol in (volumes or []):
|
for vol in (volumes or []):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue