Skip to content

fix(docker): per-session container isolation and session-scoped workspace mounts - #82731

Merged
teknium1 merged 1 commit into
mainfrom
fix/docker-session-isolation
Aug 9, 2026
Merged

teknium1 merged 1 commit into
mainfrom
fix/docker-session-isolation

Conversation

@teknium1

@teknium1 teknium1 commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Docker sessions with container_persistent: false now get one fresh container per session, and a new chat can no longer inherit the previous session's workspace mount or fail every command with exit 126 from a host-path cd.

Reported by lpha3ch0 (Discord): sandboxed cybersecurity profile on the desktop app saw (1) a brand-new chat's container carrying a previous session's repo bind-mounted rw at /workspace, and (2) every command prefixed with cd /Users/<user>/dev/<repo> inside the container → exit 126.

Root causes

Changes

  • tools/terminal_tool.py: per-session container keying when docker + container_persistent: false (_docker_session_isolation_enabled); subagent→parent container alias registry (register_container_alias); _resolve_task_host_cwd() as the single owner of the cwd→/workspace mount policy across all 4 env-creation sites — refuses process-global cwd sources under isolation; _resolve_command_cwd() gains the container host-path guard (env_type param, wired at all 4 call sites); session-scoped containers disable cross-process persist and are removed at session close/idle instead of leaking one running container per chat ever opened.
  • tools/delegate_tool.py: children register a container alias at spawn so subagents share the parent session's sandbox.
  • tui_gateway/server.py: _terminal_task_cwd_with_source() tags cwd overrides session vs process; a session-attached workspace now wins over the stale process env var.
  • tools/file_tools.py, tools/code_execution_tool.py: mount resolution routed through the shared owner.
  • website/docs/user-guide/configuration.md: per-session isolation mode documented under Docker Backend.
  • tests/tools/test_docker_session_isolation.py: 27 tests — keying, alias chain (incl. cycle safety), mount policy, host-cwd-record guard, lifecycle flags.

Default behavior (container_persistent: true) is unchanged: ONE long-lived shared container, cross-process reuse, RL/benchmark override isolation all as before.

Validation

Check Result
E2E vs real Docker: two sessions → distinct containers
E2E: fresh session with nothing attached sees empty /workspace (no leak from session A)
E2E: host-path session cwd record → command runs (exit 0), no exit 126
E2E: in-container cd still persists across commands
E2E: both containers removed at session teardown
Targeted suites (isolation, shared-task-id, cwd-sanitize, modal fixes, tui cwd-follow, tui server) 607/607

Infographic

Sandbox session isolation

…pace mounts

Two bugs reported on the docker terminal backend (desktop app, sandboxed
profiles with container_persistent: false):

1. A NEW chat's container inherited the PREVIOUS session's workspace,
   bind-mounted rw at /workspace, because the mount source was the
   process-global TERMINAL_CWD env var (written by the workspace picker,
   outliving its session) and all sessions shared one 'default' container.

2. Every command failed with exit 126 because the desktop gateway recorded
   the HOST launch directory as the session cwd, and each command was
   prefixed with 'cd /Users/<user>/...' inside the container.

Fixes (class-wide, single owners):

- container_persistent: false + docker now keys containers PER SESSION:
  fresh container per chat, removed at session close/idle. delegate_task
  children share the parent's container via an explicit alias registry.
  container_persistent: true keeps the documented ONE-long-lived-container
  contract unchanged.
- _resolve_task_host_cwd() is the single owner of the cwd->/workspace mount
  policy across all four env-creation sites; under isolation it refuses
  process-global cwd sources and mounts only the session's own attached
  workspace (tui_gateway now tags overrides with cwd_source).
- _resolve_command_cwd() gains the same host-path guard the env-creation
  sites already had (#50636/#54447 sibling site): a recorded host cwd is
  discarded on container backends instead of cd-ing every command into a
  nonexistent path.

E2E-tested against real Docker: distinct containers per session, no stale
mount in a fresh session, no exit 126 from host cwd records, containers
removed at session teardown.
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 905b834 — fix(docker): per-session container isolation and session-sco

all good!

@teknium1
teknium1 merged commit e95e137 into main Aug 9, 2026
43 checks passed
@teknium1
teknium1 deleted the fix/docker-session-isolation branch August 9, 2026 21:35
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/tools Tool registry, model_tools, toolsets comp/tui Terminal UI (ui-tui/ + tui_gateway/) tool/terminal Terminal execution and process management tool/delegate Subagent delegation tool/code-exec execute_code sandbox tool/file File tools (read, write, patch, search) backend/docker Docker container execution area/docker Docker image, Compose, packaging sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 9, 2026
benbarclay added a commit that referenced this pull request Aug 24, 2026
PR-review finding (andrexibiza, blocker 1): container_persistent: false
is the repository-wide per-session isolation contract (#82731), but the
per-session branch in _resolve_container_task_id applied to Docker only.
Non-persistent Sprites still collapsed to 'default', resolved the same
deterministic profile-scoped name, and unconditionally get_sprite()d it
— so two independent ephemeral runs could attach one live VM and either
cleanup could delete it out from under the other, and a crashed run's
stale survivor was silently resumed.

- terminal_tool: the isolation authority generalizes to
  _session_isolation_enabled() ({docker, sprites} + non-persistent);
  _docker_session_isolation_enabled() remains as the docker-gated view
  so docker-only paths (workspace mount selection, session-scoped
  container teardown) are unchanged. Delegated children still alias to
  their parent via the existing alias registry.
- SpritesEnvironment: an ephemeral constructor now mints a unique
  hermes-eph-{task}-{nonce12} name and only ever CREATES — it never
  adopts a pre-existing Sprite. Persistent mode keeps resume-by-name
  with the race-safe create-or-adopt.
- Tests: ephemeral-never-adopts, unique-per-construction, DNS-bounded
  ephemeral names, and the terminal_tool keying contract for sprites
  (per-session when non-persistent, shared 'default' when persistent;
  docker-only helper stays False for sprites). #82731's own suite is
  unchanged and green (27/27). The live-suite fixture pins the
  ephemeral naming path into the run-unique test namespace too. Docs
  state the single-use ephemeral behavior.
teknium1 added a commit that referenced this pull request Aug 25, 2026
…er-session

Commit a270c4a's session-key fallback in _resolve_container_task_id was
added to stop cross-profile SSH environment reuse, but it wasn't backend-
gated: persistent Docker silently fragmented into one container per gateway
session, breaking the product contract (one long-lived container per profile,
shared by CLI and every session of that profile). #93950's vanishing MEDIA
attachments were downstream damage.

- persistent Docker (container_persistent: true) now keys to the profile:
  literal 'default' for the default profile (same container as CLI),
  'profile:<name>' for named profiles
- SSH and non-persistent Docker keep session scoping (the original leak fix
  and the #82731 isolation contract are untouched)
- gateway MEDIA translation follows the profile layout and keeps the legacy
  bug-window per-session sandboxes as fallback candidates, trying each until
  the file resolves — old sessions self-heal, no migration
- /root/.hermes credential-surface refusal preserved across all layouts
teknium1 added a commit that referenced this pull request Aug 25, 2026
…er-session

Commit a270c4a's session-key fallback in _resolve_container_task_id was
added to stop cross-profile SSH environment reuse, but it wasn't backend-
gated: persistent Docker silently fragmented into one container per gateway
session, breaking the product contract (one long-lived container per profile,
shared by CLI and every session of that profile). #93950's vanishing MEDIA
attachments were downstream damage.

- persistent Docker (container_persistent: true) now keys to the profile:
  literal 'default' for the default profile (same container as CLI),
  'profile:<name>' for named profiles
- SSH and non-persistent Docker keep session scoping (the original leak fix
  and the #82731 isolation contract are untouched)
- gateway MEDIA translation follows the profile layout and keeps the legacy
  bug-window per-session sandboxes as fallback candidates, trying each until
  the file resolves — old sessions self-heal, no migration
- /root/.hermes credential-surface refusal preserved across all layouts
and7777 pushed a commit to and7777/hermes-agent that referenced this pull request Aug 27, 2026
…er-session

Commit a270c4a's session-key fallback in _resolve_container_task_id was
added to stop cross-profile SSH environment reuse, but it wasn't backend-
gated: persistent Docker silently fragmented into one container per gateway
session, breaking the product contract (one long-lived container per profile,
shared by CLI and every session of that profile). NousResearch#93950's vanishing MEDIA
attachments were downstream damage.

- persistent Docker (container_persistent: true) now keys to the profile:
  literal 'default' for the default profile (same container as CLI),
  'profile:<name>' for named profiles
- SSH and non-persistent Docker keep session scoping (the original leak fix
  and the NousResearch#82731 isolation contract are untouched)
- gateway MEDIA translation follows the profile layout and keeps the legacy
  bug-window per-session sandboxes as fallback candidates, trying each until
  the file resolves — old sessions self-heal, no migration
- /root/.hermes credential-surface refusal preserved across all layouts
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…er-session

Commit a270c4a's session-key fallback in _resolve_container_task_id was
added to stop cross-profile SSH environment reuse, but it wasn't backend-
gated: persistent Docker silently fragmented into one container per gateway
session, breaking the product contract (one long-lived container per profile,
shared by CLI and every session of that profile). NousResearch#93950's vanishing MEDIA
attachments were downstream damage.

- persistent Docker (container_persistent: true) now keys to the profile:
  literal 'default' for the default profile (same container as CLI),
  'profile:<name>' for named profiles
- SSH and non-persistent Docker keep session scoping (the original leak fix
  and the NousResearch#82731 isolation contract are untouched)
- gateway MEDIA translation follows the profile layout and keeps the legacy
  bug-window per-session sandboxes as fallback candidates, trying each until
  the file resolves — old sessions self-heal, no migration
- /root/.hermes credential-surface refusal preserved across all layouts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docker Docker image, Compose, packaging backend/docker Docker container execution comp/tools Tool registry, model_tools, toolsets comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/code-exec execute_code sandbox tool/delegate Subagent delegation tool/file File tools (read, write, patch, search) tool/terminal Terminal execution and process management type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants