# 复用主机认证的原生会话隔离 **实验性,Codex/Claude 默认开启,支持 macOS 与 Linux。** Node 调用主机已安装的 Codex 或 Claude Code。请先用运行 Node 的同一系统用户,按官方方式登录或配置客户端。 开启 `SESSION_ISOLATION=native` 不需要在每个沙箱重新登录,也不强制提供新的 `CODEX_API_KEY` / `ANTHROPIC_API_KEY`。Node 不读取、复制、刷新或代理订阅 token。 ## 认证与工具的边界 官方客户端是可信宿主进程,继续负责自己的认证;模型控制的工具单独受限制: - Codex 使用命名文件权限配置和系统沙箱,工具使用私有 HOME 与精简环境。 新建及恢复 thread 都不再传旧的 sandbox 覆盖参数,避免覆盖精确文件权限。 明确关闭客户端内执行的 `view_image` 和主机配置的 `notify` 命令,即使用户配置开启它们。 - Claude 保留原来的 HOME / CLAUDE_CONFIG_DIR,使用 `--safe-mode --restricted`, 不使用会跳过订阅认证的 `--bare`。**本地工具默认只开 Bash**;显式配置才能开启 Read/Edit/Write/Glob/Grep,它们受客户端权限检查,不在 OS 沙箱中。 Bash 使用客户端系统沙箱,要求初始化成功、禁止排除命令和无沙箱重试。 命令环境始终禁用凭据:macOS 另开全局子进程清理,Linux 使用常规 Bash 沙箱的 credential deny 规则和 PID/proc 命名空间。Claude 2.1.259 的全局清理开关会额外 放宽 Linux 可写目录,本模式在 Linux 明确关闭该开关。 `CLAUDE_CODE_TMPDIR` 也设为本轮私有临时目录,避免命令收尾记录使用公共 `/tmp/claude-`。 - 工具不能获得认证目录、其他会话或 Node 控制文件的读取授权;不会继承平台 token 和环境注入选项。个人插件、MCP、hooks、跨会话与桌面/浏览器入口不接入此模式。 网络白名单不能防止可读凭据通过输出泄露,因此凭据保护依靠文件和进程边界。 官方二进制、系统用户/管理员、管理员下发的客户端策略和显式允许的代码路径仍然可信。 这不是防御恶意宿主进程、被篡改客户端或主动放宽沙箱的管理员,也不保证任意客户端 版本都安全。实验阶段继续只对可信调用方开放。不要把个人订阅直接作为共享公共服务; 账号使用权限、供应商条款、费用限额和平台访问控制仍需单独满足。 ## 会话复用 一个 Node 可同时处理多个会话,每个活跃 Run 启动对应客户端进程,其工具在各自沙箱 运行;空闲会话保留数据,无需常驻客户端。Core 提供的可信 principal、Agent、会话 key 与 Core 命名空间共同决定存储位置,不接受 payload 自报身份。跨进程锁防止同一 会话并发写入,取消 A 不会停止 B。 A → B → A 会恢复 A。不会把主机个人聊天历史导入会话。Codex 使用受控的 thread ID; Claude 另用 `CLAUDE_CODE_PROJECT_DIR_NAME` 分开项目记录,同时保持认证目录不变。 native 模式默认采用 `HOST_AUTH_CONCURRENCY=serial`:共享主机 HOME 的 Node 对同一种 Provider 排队。锁固定在 `$HOME/.local/state/openlinker-agent-node/host-auth-.lock`, 使用可信客户端的 HOME,不使用会话内 HOME、TMPDIR 或 XDG_STATE_HOME。最终目录 0700、 锁文件 0600;不会自动修改已有目录权限。HOME 别名会解析为真实路径;HOME 下的符号链接、 异常属主或可被其他用户写入的父目录会导致失败,不会退回 `/tmp`。工具不能读写或删除 这组控制文件,也不能把会话存储放进这个保留目录。 跨 Node 进程、Agent、会话目录互斥的前提是:**共享底层 HOME 状态目录,且文件系统提供 可用的 OS 文件锁**。私有 `/tmp` 不再拆开锁;不同 HOME、容器各自的 HOME 挂载或状态 副本仍然会拆开锁,即使路径字符串相同或共用同一账号。Node 无法检测另一个命名空间 的挂载与账号关系;这也不是跨设备的分布式锁。 锁从启动客户端之前保持到退出、重试和会话保存结束。等待可取消、计入 Run 超时、 占用已分配的 slot,并输出一次等待状态。串行模式建议显式设置 `OPENLINKER_AGENT_NODE_CAPACITY=1`,避免先领取多项任务后在本机空等,让其他 Node 保留获得派发的机会。程序不擅自覆盖配置的 capacity。等待方每 50ms 重试,**不保证 FIFO 或公平性**;持续竞争时可能直到超时也未开始执行。 显式设置 `HOST_AUTH_CONCURRENCY=client-managed` 才允许客户端并行;仅用于已独立验证 客户端与认证并发安全的配置,它不会增加令牌刷新保护。普通非 native 模式不受影响。 这属于 **Node 协作式排队,不是整个账号的刷新锁**。旧版本 Node、显式放开并发的 Node、 独立启动的终端或桌面客户端均不参与,账号敏感的测试应避免与它们共用登录。正常取消 会等客户端停止再释放锁;强制杀死 Node 则可能在锁已释放后留下客户端进程,重启前应 检查并停止该 Node 遗留的客户端。运行期间不要删除 HOME 下的新锁文件;保留文件是刻意设计,锁由 OS 释放。从旧 `/tmp` 锁候选升级前, 须停止旧版本 Node,并等待或停止其客户端;新旧路径不互斥。升级不自动删除旧锁或会话。 真实 OAuth 轮换仍未验证,详见[并发刷新待办](native-session-isolation-follow-ups.md)。 ## 配置 已验证的客户端基线为 Codex 0.153.0、Claude Code 2.1.259。Linux 需要可用的非特权 user/network/PID namespaces,Claude 另需 `bwrap`、`socat`、`rg`。macOS 使用客户端 内建 Seatbelt 沙箱。Node 的此模式不再要求另装 npm SRT 包。 认证与会话目录必须放在系统/代码只读授权之外。Linux Codex 还需读取当前进程的 辅助程序别名目录和可执行文件;Node 先确认目录只有已知别名及空锁文件,再只读 开放这些代码路径,不开放认证目录或整个 tmp 树。 Ubuntu 26.04 的 `apparmor` 包为 `/usr/bin/bwrap` 强制启用 `bwrap-userns-restrict` profile:bwrap 可以创建 namespace,但它启动的所有进程(包括再嵌套的 user namespace 内)都没有 capability。在该默认策略下,Codex 原生隔离可用,Node 启动探测也能通过; Claude 沙箱的 seccomp 辅助程序需要带 capability 的嵌套 user namespace,无法启动。 此时每次 Claude Bash 调用都会以 `apply-seccomp: write /proc/self/setgroups (nested userns is capability-restricted ...)` 失败关闭;文件工具仍限制在工作区内,不会退回 无沙箱 shell。给 bwrap 子进程授予带 capability 的 user namespace 正是该发行版策略要 阻止的行为,属于 Node 之外的宿主安全决策。原生隔离 CI 作业运行在 Ubuntu 24.04, 不覆盖 26.04。 不要在发行版 profile 之外再加载另一个挂载到 `/usr/bin/bwrap` 的 AppArmor profile: 内核会因 `conflicting profile attachments` 两者都不应用,bwrap 报 `loopback: Failed RTM_NEWADDR`,Node 启动探测随之对两种客户端都拒绝原生隔离。 ```sh export OPENLINKER_AGENT_NODE_ADAPTER=codex # 或 claude;默认即 native 隔离 export OPENLINKER_AGENT_NODE_CAPACITY=1 # 默认串行模式建议值 # 可选:默认 $HOME/.local/state/openlinker-agent-node-sessions export OPENLINKER_AGENT_NODE_SESSION_ROOT=/absolute/private/node-sessions # 保留原来的 HOME,以及已配置的 CODEX_HOME / CLAUDE_CONFIG_DIR。 # 已有客户端登录时,无需增加 API key 环境变量。 ``` `SESSION_ROOT` 必须为当前用户独占的目录,且不能位于任何 git 工作树内(自身及上级 目录都不能有 `.git` 目录或文件)。可信客户端会在工具沙箱生效前读取工作目录的 git 状态、最近提交和项目说明,放在仓库内会把该仓库暴露给模型。不能以 root 运行。 native 模式让每个会话使用私有工作区,因此会拒绝而不是忽略 `CODEX_WORKSPACE` / `CLAUDE_WORKSPACE`、`*_SESSION_REUSE=false`、`SESSION_STORE`、委派和 `ENV_ALLOWLIST`;native 下 `*_SESSION_REUSE` 默认 `true`。不会自动退回无沙箱运行: 不支持的平台、root 用户或上述配置都会导致启动失败,需修正配置或显式设置 `SESSION_ISOLATION=off`。Codex mock 响应不启动客户端,保持 `off`;HTTP、A2A、 command 适配器不做隔离。以下选项都加 `OPENLINKER_AGENT_NODE_` 前缀: | 参数 | 含义 | | --- | --- | | `SESSION_ISOLATION` | Codex/Claude 默认 `native`,`off` 显式关闭 | | `HOST_AUTH_CONCURRENCY` | 仅 native:未设置/`serial` 按共享主机 HOME 与 Provider 串行运行 Node 客户端;`client-managed` 显式允许并发 | | `SESSION_ROOT` | 私有持久目录,不能在 git 工作树内;默认 `$HOME/.local/state/openlinker-agent-node-sessions` | | `SESSION_TEMP_ROOT` | 可选私有临时目录,解析后路径不超过 40 字节 | | `SESSION_READ_PATHS` | 为 shell 额外开放的只读代码/库路径 JSON 数组;不能暴露认证、会话和控制目录 | | `SESSION_NETWORK_DOMAINS` | 沙箱命令可访问的精确公网 HTTPS 主机名 JSON 数组,空数组表示命令断网 | | `CLAUDE_ALLOWED_TOOLS` | 未设置/空数组默认 Bash;可信任务可用显式 JSON 数组选择 Read、Edit、Write、Glob、Grep、Bash,注意下方文件工具边界 | | `CODEX_WEB_SEARCH` / `CLAUDE_WEB_SEARCH` | 单独控制客户端原生搜索工具 | Codex 保留既有模型 Provider 配置,不再强制改到默认 API 地址。Claude 复用缓存认证 或标准凭据环境及 `ANTHROPIC_BASE_URL`。显式的 `CODEX_BASE_URL` / `CLAUDE_BASE_URL` 覆盖仍支持 HTTPS 443 和完整 API 路径;Codex 显式网关沿用可选的 `CODEX_API_KEY`。这不会使普通订阅登录变成必须提供 key。 域名列表仅限制**沙箱命令**联网,不限制可信客户端模型请求、认证与服务端 WebSearch。 配置网关不会自动给命令开放网关权限,也不会关闭 TLS 验证。Claude restricted 模式 忽略用户/项目自定义设置,依赖 settings 内 helper 或云厂商认证的特殊配置还需另行 验证,不能自动提取订阅 token 或改为代理。 ## 迁移与验收 ### 显式开启文件工具的边界 账号敏感场景保留默认值;Bash 仍可在会话目录内读写、搜索文件。 `CLAUDE_WEB_SEARCH` 独立控制(默认开启,显式 `false` 关闭),不随文件工具默认值变化。 不要由宿主向会话目录放入敏感文件的硬链接。硬链接是同一个 inode 的另一个名字, 不能靠 realpath 判断其原始路径。真实客户端测试分别验证沙箱 Bash 创建的链接, 以及可信测试宿主预置的硬链接;显式开启的 Read/Grep 和沙箱 Bash 都可以读取后者。 只开 Bash 不会撤回宿主已暴露的 inode。因此符号链接 测试通过不等于任意 inode 别名都安全。关闭工具不会擦除已有客户端记录和 Core 历史; 收紧权限后,不应继续复用可能已包含凭据的会话。 新增矩阵覆盖五个文件工具、文件/目录符号链接、硬链接,以及 Linux `/proc/self` 别名。正向对照确认工具实际执行;检查宿主原文件,区分原子替换链接与修改原 inode。 默认模式还主动提交未注册的文件工具调用,要求拒绝。结论仅覆盖固定版本与具体用例, 不代表链接切换竞态或所有客户端文件/IPC 接口已被证明安全。 ### 旧 SRT 会话 旧实现把整个客户端放入 SRT 沙箱,会改认证目录并要求专用 key。新实现使用独立的 host-auth 存储 scope,不会复制或删除旧工作区、历史或可能包含凭据的数据。因此迁移 后第一次会新建会话,后续继续复用。该变化针对旧 SRT 模式迁移至主机认证模式; 后续默认仅 Bash、关闭 view_image 的修复没有再次改变 scope。Core 保留的历史与旧客户端 私有会话数据是两回事。删除旧的 `SESSION_SANDBOX_BIN`,保留会报错, 不会静默忽略。不能混用旧 `SESSION_STORE`、委派 socket 和任意 `ENV_ALLOWLIST`。 启动检查客户端能力和系统依赖;macOS Codex 还实际验证允许写入/拒绝读取,Linux 检查 user/network/PID 命名空间可用。Claude 在开始 任务时由 `failIfUnavailable` 强制初始化沙箱,版本/help 检查本身不能证明文件隔离。 任何失败都不允许自动退回无沙箱运行。 `test-native-session-isolation.sh` 使用真实客户端、临时合成登录和本地固定模型响应, 分别检查显式开启的文件工具、默认 Bash、A-B-A、文件工具链接矩阵、跨会话读取、越界写入、环境与父进程 凭据读取;macOS 还使用明确指定的临时钥匙串测试合成密码,不读取登录钥匙串。不会消耗 真实账号额度;这不代表真实模型、WebSearch 或账号合规已完成验收。CI 配置了两套系统, 在 PR 或版本 tag 推送时触发;配置存在不等于已跑绿。 当前源码 `25c49b2` 已通过 [PR #36 CI](https://github.com/OpenLinker-ai/openlinker-agent-node/actions/runs/34810002330) 的 macOS 与 Ubuntu 24.04 amd64 验证,包含实际客户端认证复用、链接和排队测试。 本地 macOS arm64 与隔离的 Ubuntu 24.04.5 arm64 OrbStack 环境也通过。后者使用 OrbStack 内核且没有 AppArmor;GitHub Ubuntu 提供保留系统策略、按可执行文件授权 userns 的独立证据。这不代表所有 Linux 宿主或真实账号都已通过;详见 [当前验收记录](host-auth-acceptance.md),旧容器与整个客户端 SRT 记录不能替代它。 Linux 的空白 tmpfs 覆盖层可能允许创建同名临时文件,验收以宿主真实认证文件和 目录未被修改为准;写入隔离覆盖层不等于获得宿主写权限。 **尚无会话级磁盘、内存、进程数硬配额。** 临时目录设置和超时不能替代配额。即使 读不到凭据,任务仍可能消耗磁盘或账号预算。详见[跟踪记录](native-session-isolation-follow-ups.md)。