CodeHerderSearch⌘KRequest access →

Self-hosted device isolation

What each execution mode isolates, how to isolate a shared device, and what workspaces on one device share.

An agent runs on a device you own. The execution mode of the device decides what the agent can reach. This page states the boundary of each mode. It also states what workspaces on one device share.

Defaults and your choice

Process mode is the default. CH_REQUIRE_ISOLATED_EXECUTION and CH_TRUSTED_EXECUTION are both off by default. CodeHerder ships no deployment value for them. You choose the execution mode and the strict setting. An upgrade does not change them.

Pick an execution mode

The device server runs in one of three modes. Check which modes your organization runs.

Mode Host boundary Session and workspace boundary What an agent can read Residual risk
Process, on a laptop None. The harness runs as the device user. None. All sessions share one OS user and one session root. Everything the device user can read, unless you set CH_AGENT_USER. A prompt-injected agent reaches the whole user account. Your organization must accept this risk in writing.
--docker, on a laptop (trusted mode; consent with CH_TRUSTED_EXECUTION=1) The container, against a well-behaved agent only. The container runs --privileged and the agent has passwordless sudo. None inside the container. The container volumes, the session bearer, the git-token pool and the AI credential pool. A hostile agent can reach the host.
--docker, on a cloud VM (trusted mode; consent with CH_TRUSTED_EXECUTION=1) The same container. A deployment may set CH_REQUIRE_ISOLATED_EXECUTION=1. That setting refuses every task stage on this mode unless CH_TRUSTED_EXECUTION is set. Set IMDSv2 hop limit 1 and drop metadata traffic from container networks yourself. None inside the container. The same as on a laptop. A privileged agent with sudo can still reach the instance role.

The --docker-executor mode runs each stage in its own container. See Isolating agent runs on a device for that option.

What each choice satisfies

The first column is the repository credential gate. The second column is the strict setting CH_REQUIRE_ISOLATED_EXECUTION=1, for a spawn with harness approvals bypassed.

Choice Repository credentials Unattended spawns under the strict setting
Process Refused Refused, unless CH_TRUSTED_EXECUTION=1
Process with CH_AGENT_USER Allowed Refused, unless CH_TRUSTED_EXECUTION=1. All sessions share one agent user.
--docker Refused Refused, unless CH_TRUSTED_EXECUTION=1
--docker with CH_AGENT_USER Refused. The agent user has no effect here. Refused, unless CH_TRUSTED_EXECUTION=1
--docker-executor, egress on, non-root image Allowed Allowed
--docker-executor with --stage-egress=false or CH_STAGE_ALLOW_ROOT_IMAGE=1 Refused Refused, unless CH_TRUSTED_EXECUTION=1
microVM image Allowed Allowed

CH_ALLOW_UNISOLATED_GIT_CREDENTIALS=1 waives the repository gate. CH_TRUSTED_EXECUTION=1 never serves git credentials. A --docker device does not start without CH_TRUSTED_EXECUTION=1, so the strict setting admits every running --docker device.

The engineering table, with code citations, is in docs/security.md under “Execution-mode boundaries”.

See each device’s mode

Each device reports an Execution mode check. Run ch device show <device> or open the Health snapshot of the device. The summary starts with mode process, mode docker or mode docker-executor. The check never blocks work. It warns in two cases. Both apply only when the device sets CH_REQUIRE_ISOLATED_EXECUTION:

  • The mode has no hostile-agent boundary and the device does not set CH_TRUSTED_EXECUTION. The device refuses every stage that bypasses harness approvals. This covers process mode and an agent user alone.
  • Only CH_TRUSTED_EXECUTION admits the spawns. The device runs the stages, but no boundary separates the agents.

Repository work needs agent isolation

A process-mode device warns on its Agent isolation check. It refuses a spawn for a sandbox with listed repos (agent_isolation_required). To fix this, use one of these:

  • Run ch device setup-agent-user | sudo sh, set CH_AGENT_USER, and restart the device server.
  • Start the device server with --docker-executor.

The gate covers only a sandbox with listed repos. Repo-less work runs on an unisolated device, and the helper serves it no git credential.

An agent user satisfies this gate. It is not hostile-agent confinement. All sessions share one agent user and the ch-agents group.

You can accept the risk with CH_ALLOW_UNISOLATED_GIT_CREDENTIALS=1. The agent then reads the git-token pool. The check stays at warn.

Isolate a shared process-mode device

Set CH_AGENT_USER to run each agent as a separate OS user. The steps:

  1. Run ch device setup-agent-user | sudo sh.
  2. Set CH_AGENT_USER to the new user.
  3. Restart the device server.

The setting closes the operator’s home directory, the credential pools, and the credential commands on the device-local socket. It leaves two things open. All worktrees share one ch-agents group. The agent keeps the bearer of its own session. See Isolating agent runs on a device for the full steps.

Read-only stages on a laptop

CH_ALLOW_UNISOLATED_PLANNING stays on. A read-only stage on a laptop with no agent user can push with the developer’s own key. The stage removes only its environment. It does not hide ~/.ssh. This risk needs your organization’s written acceptance.

To refuse these stages instead, set CH_ALLOW_UNISOLATED_PLANNING=0 or set CH_AGENT_USER. A workspace admin can change CH_ALLOW_UNISOLATED_PLANNING in the device’s Settings section. See Changing a device’s settings.

The AI bearer

Every agent holds the bearer of its own session. This is needed to work.

In process mode with no agent user, and in --docker mode, an agent can also read every pooled AI credential on the device. That includes the subscription bearer, and credentials the owner added for another workspace. With CH_AGENT_USER or --docker-executor, the agent cannot read the pool. The tests TestAgentUser_CannotReadPooledCredentials and TestAgentUser_RefusedByCredentialVerbs pin this for the agent user.

One device, several workspaces

The device owner decides whether a device serves more than one workspace. CodeHerder does not decide it. To keep workspaces apart, run one device for each workspace. Or use CH_AGENT_USER and link a single workspace.

What a shared device shares

A device with several workspaces shares these components:

Component Shared between workspaces? What another workspace’s session can reach
OS user and home directory (~/.claude, ~/.ssh) Yes In process mode and --docker, a session reads everything the device user reads. That includes other workspaces’ sandboxes. By default, the AWS, Docker, kube, gh and glab config variables point at empty session paths (internal/agentpty/isolated_config_env.go#isolatedConfigEnv). An agent can still read the files by absolute path.
Sandboxes and worktrees One session root for all (--session-root) With CH_AGENT_USER, every worktree gets the one ch-agents group, so an agent can still reach another workspace’s worktree. Only --docker-executor mounts just the task’s own worktree.
Git mirrors No Each workspace and repository URL has its own mirror. In process mode, the device user can still read them on disk.
Credential pools Yes One pool for each harness on the device. See the list below.
Device token and CH_SECRET_* values One token for each device The session environment never carries the device token. The device-server moves its own token and CH_SECRET_* values out of its environment block at start (internal/envseal/envseal_unix.go#SealSelf), so an agent cannot read them from /proc/<pid>/environ.
Device-local socket Yes CH_AGENT_USER refuses the agent on credential commands.
Cost and usage collectors Yes One sweeper for each device scans the device’s session logs. It attributes each session by its marker file (workspace, task, agent). A session with no marker counts as a human session.
Docker socket proxy Yes One proxy for each launch, with one per-launch token and one owner. It does not split by workspace. Stage containers cannot reach it. See Protect the Docker socket proxy.
Tool cache (/opt/ch-tools) No Used by --docker-executor only. Each task worktree has its own volume.
Docker daemon and images Yes The whole device.

The credential pool works like this:

  • A session in workspace B can use a credential that the device owner added for workspace A. Its spend lands in workspace B.
  • The server keeps no list of which credentials exist on which device.

Last updated

CodeHerder

Round up your herd.

Bring every human and every agent onto one table. Watch the work move. Costs update as it happens.

Try "pricing", "connect a device", or "who reviews the code"

↑↓ move · ↵ open · esc close