Skip to content

fix(tmux): 会话级 env 不再泄漏进 tmux server 全局环境(误路由 AskUserQuestion 卡片)#364

Open
deepcoldy wants to merge 1 commit into
masterfrom
fix/tmux-global-env-leak
Open

fix(tmux): 会话级 env 不再泄漏进 tmux server 全局环境(误路由 AskUserQuestion 卡片)#364
deepcoldy wants to merge 1 commit into
masterfrom
fix/tmux-global-env-leak

Conversation

@deepcoldy

Copy link
Copy Markdown
Owner

问题

botmux 的 tmux 后端用默认 tmux socket/tmp/tmux-*/default)跑会话,和用户自己的交互 tmux 共用同一个 server。tmuxEnv() 此前只剥 TMUX/TMUX_PANE,会把本会话的 BOTMUX_SESSION_ID / BOTMUX_CHAT_ID / BOTMUX_LARK_APP_ID / SESSION_DATA_DIR 等带进 tmux client env。当 botmux 首个 new-session 引导起 server 时,tmux 把 client env 固化成 server 的全局环境,此后同 server 上新建的每个会话(包括用户 ssh devbox 起的 tmux new-session -A -s cc)的 pane 都继承这份全局 env。

后果:用户终端里一个普通 Claude Code 会话继承了 botmux 的 BOTMUX_SESSION_ID/CHAT_ID,其 AskUserQuestion 的 PreToolUse hook(botmux hook claude-code)误以为自己是 botmux 会话,把选择题卡片 POST 给 daemon、发进那个飞书话题群。

修法(对正在运行的会话零影响)

  1. 闸口tmuxEnv() 增加剥离 botmux 托管的会话/机器人级 env key(isBotmuxManagedTmuxEnvKeyBOTMUX* 前缀一律剥 + __OWNER_OPEN_ID/SESSION_DATA_DIR/IS_SANDBOX/CLAUDE_CONFIG_DIR/CLAUDE_CODE_RESUME_TOKEN_THRESHOLD/CJADK_INTERACTIVE + 裸 IM 凭据)。pane 本身靠 /usr/bin/env KEY=VAL 注入(读 opts.env 而非 tmuxEnv(opts.env))拿正确的 per-session 值,不走 client/global env,对 botmux 自己的会话完全无感。新引导的 server 全局环境从此干净。

  2. 自愈老 server:新增 TmuxBackend.scrubServerGlobalEnvOnce(),daemon 每进程跑一次,把已被旧版 botmux 污染的 server 全局环境里的 botmux key set-environment -g -u 掉。只改全局环境表、不触碰已运行 pane 的进程环境——活会话字节不变,只有之后新建的 pane 才拿到干净环境。在 TmuxBackend.spawn()TmuxPipeBackend.spawn() 顶部触发(运行时真正实例化的是 TmuxPipeBackend)。

影响面

  • 会话后端:TmuxBackend / TmuxPipeBackendPtyBackend 不受影响(其 CLI child 仍正常直接拿 BOTMUX_*)。
  • 三个非注入的 BOTMUX_*BOTMUX_BOT_INDEX/QUIET_RESTART/MEMORY_DIAG_INTERVAL_MS)经确认只在 daemon/CLI 启动层读取、pane 内从不读,剥离安全。
  • 不影响正在运行的 botmux 会话:per-session env 走 env(1) 注入而非 tmux 环境;reattach 不重置 pane env;scrub 不动运行中进程。

测试

  • pnpm build 绿。
  • 单测 126 passed:tmux-env-isolation / tmux-backend-env / tmux-pipe-backend / child-env / per-bot-env / tmux-probe / tmux-reattach / startup-tmux-gate / backend-gate / selector-shape。
  • e2e 4 passed:tmux-backend.e2e。
  • 隔离 socket 端到端复现+验证脚本(12/12 PASS):tmuxEnv 剥 BOTMUX_/TMUX 保 HOME/PATH;新 tmuxEnv 引导的 server 全局环境无 BOTMUX_*/SESSION_DATA_DIR/LARK_APP_ID 且 co-tenant pane 不继承;scrub 清污染 server 后运行中 pane 存活且 env 字节一致。

待 live 验证

pnpm switch:here && botmux restart 部署后在飞书里确认:botmux 会话仍能正常触发 ask 卡片,且用户终端新开的 tmux pane 不再带 BOTMUX_*

## 问题
botmux 的 TmuxBackend/TmuxPipeBackend 用**默认 tmux socket**(/tmp/tmux-*/default)跑
会话,和用户自己的交互 tmux 共用同一个 server。tmuxEnv() 之前只剥 TMUX/TMUX_PANE,会把
本会话的 BOTMUX_SESSION_ID / BOTMUX_CHAT_ID / BOTMUX_LARK_APP_ID / SESSION_DATA_DIR 等
带进 tmux **client env**——当 botmux 首个 `new-session` 引导起 server 时,tmux 把 client
env 固化成 server 的**全局环境**,此后同 server 上新建的每个会话(包括用户 `ssh devbox` 起的
`tmux new-session -A -s cc`)的 pane 都继承这份全局 env。

后果:用户终端里一个普通 Claude Code 会话继承了 botmux 的 BOTMUX_SESSION_ID/CHAT_ID,
其 AskUserQuestion 的 PreToolUse hook(botmux hook claude-code)误以为自己是 botmux 会话,
把选择题卡片 POST 给 daemon、发到那个飞书话题群里。

## 修法(对正在运行的会话零影响)
1. tmuxEnv() 增加剥离 botmux 托管的会话/机器人级 env key(isBotmuxManagedTmuxEnvKey:
   BOTMUX* 前缀一律剥 + __OWNER_OPEN_ID/SESSION_DATA_DIR/IS_SANDBOX/CLAUDE_CONFIG_DIR/
   CLAUDE_CODE_RESUME_TOKEN_THRESHOLD/CJADK_INTERACTIVE + 裸 IM 凭据)。pane 本身靠
   `/usr/bin/env KEY=VAL` 注入拿正确的 per-session 值(不走 client/global env),所以对
   botmux 自己的会话完全无感。新引导的 server 全局环境从此干净。
2. 新增 TmuxBackend.scrubServerGlobalEnvOnce():daemon 每进程跑一次,把已被旧版 botmux
   污染的 server 全局环境里的 botmux key `set-environment -g -u` 掉(自愈老 server)。
   只改全局环境表、**不触碰已运行 pane 的进程环境**——活会话字节不变,只有之后新建的
   pane 才拿到干净环境。在 TmuxBackend.spawn() 与 TmuxPipeBackend.spawn() 顶部触发
   (运行时真正实例化的是 TmuxPipeBackend)。

## 影响面
- 会话后端:TmuxBackend / TmuxPipeBackend(PtyBackend 不受影响,其 CLI child 仍正常拿 BOTMUX_*)。
- 三个非注入的 BOTMUX_*(BOTMUX_BOT_INDEX/QUIET_RESTART/MEMORY_DIAG_INTERVAL_MS)经确认只在
  daemon/CLI 启动层读取、pane 内从不读,剥离安全;顺带修掉了它们对其它 bot 面板的过期泄漏。

## 测试
- pnpm build 绿。
- 单测:tmux-env-isolation / tmux-backend-env / tmux-pipe-backend / child-env / per-bot-env /
  tmux-probe / tmux-reattach / startup-tmux-gate / backend-gate / selector-shape = 126 passed。
- e2e:tmux-backend.e2e = 4 passed。
- 隔离 socket 端到端复现+验证脚本(12/12 PASS):
  · tmuxEnv 剥掉 BOTMUX_SESSION_ID/TMUX,保留 HOME/PATH;
  · 经新 tmuxEnv 引导的 server 全局环境无 BOTMUX_*/SESSION_DATA_DIR/LARK_APP_ID,co-tenant pane 不继承;
  · scrub 清掉预污染 server 的 BOTMUX_*,且运行中 pane 存活、其 env 前后字节一致。
@deepcoldy deepcoldy force-pushed the fix/tmux-global-env-leak branch from 6e6b16b to d2bcfe9 Compare July 4, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant