Merge branch 'NousResearch:main' into main

This commit is contained in:
BathreeNode 2026-03-02 12:14:34 +03:00 committed by GitHub
commit bd8b20b933
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 19 additions and 15 deletions

View file

@ -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

View file

@ -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 |

View file

@ -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 []):