Skip to main content

Module pane_env

Module pane_env 

Source
Expand description

Instance identity must not be inheritable by the processes a pane spawns.

§The defect this closes

Both pane-spawn paths build their environment by inheriting srv’s, and srv’s environment is where the launcher injects the whole instance identity set (DataPaths::to_env_vars — data dir, config dir, cache dir, channel, runtime mode) and where srv adds more of its own (AGENTMUX_DATA_HOME, AGENTMUX_CONFIG_HOME, AGENTMUX_LOCAL_URL).

  • terminal panes: portable_pty::CommandBuilder seeds from std::env::vars_os(), so .env() calls are overrides on a full copy.
  • agent panes: tokio::process::Command inherits by default.

So every AGENTMUX_* variable reached every pane, and therefore every process launched from one — including another AgentMux build, which then adopted the launching instance’s identity instead of the one baked into it. Confirmed live: a 0.56.3 build launched from a pane wrote into a 0.56.2 instance’s channel. That is invariant I6 breached through a channel I1–I6 never named, because they enumerate OS objects and directories and treat the threat as “a new launch must not crash a running one” — this is neither.

Full analysis, including the five prior documents that identified this and left it open: docs/retro/retro-env-inheritance-instance-isolation-breach-2026-09-17.md.

§The rule

An AGENTMUX_* variable crosses into a pane only if it is on PANE_ENV_KEEP — an allowlist, not a denylist, so a variable added later is excluded by default rather than leaking until someone remembers to deny it. That direction is the whole point: the previous state of the world was an implicit allow-everything.

This strips only what would arrive by inheritance. Callers apply their own explicit .env() values afterwards, which is why the terminal path can strip nearly everything — it re-sets what it needs a few lines later.

Constants§

NESTING_SENTINEL_KEY
The nesting sentinel. Presence tells a launcher started from a pane to ignore ambient AGENTMUX_* and re-derive from its own exe path (crates/launcher/src/data_dir.rs).
PANE_ENV_KEEP
AGENTMUX_* variables permitted to reach a pane by inheritance.

Functions§

all_agentmux_keys 🔒
Every AGENTMUX* key in this process’s environment except the sentinel.
build_info
Which AgentMux a pane runs in, set on every process a pane starts so an agent can say so without reading it out of a log path:
keys_to_strip
Keys in this process’s environment that must not cross into a pane.
sanitize_external_command
Strip every AGENTMUX_* variable, including the in-pane helper keep-set.
sanitize_external_pty_command
sanitize_external_command for a PTY command.
sanitize_external_std_command
sanitize_external_command for a synchronous std::process::Command.
sanitize_process_command
sanitize_pty_command for a tokio::process::Command — agent subprocesses, and the /api/v1/shell/create runner behind the MCP Shell tool, which is how CLAUDE.md tells agents to launch task dev / task package. That path was missed entirely by the first revision of this fix (ReAgent P0), which is notable because it is the single most likely way for an agent to start a second instance.
sanitize_pty_command
Apply the policy to a PTY command: strip inherited identity, then mark the child as running inside AgentMux.
should_strip
Is key an AgentMux variable that must not be inherited by a pane?