Skip to content

feat(mcp): MCP Gateway 注入宿主可信调用身份 - #917

Merged
deepcoldy merged 12 commits into
deepcoldy:masterfrom
yousay123:barry/data-mcp-identity-origin-merge
Aug 19, 2026
Merged

feat(mcp): MCP Gateway 注入宿主可信调用身份#917
deepcoldy merged 12 commits into
deepcoldy:masterfrom
yousay123:barry/data-mcp-identity-origin-merge

Conversation

@yousay123

@yousay123 yousay123 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

改动内容

  • 为 MCP Gateway 增加「按 turn 注入宿主可信调用身份」能力:身份由 worker 进程内存中的 provider 回调按轮提供(不落任何文件),转发 tools/call 时注入到 _meta.botmuxTrustedCaller;streamable-http 传输额外注入 x-botmux-trusted-* 请求头。
  • 安全不变量:注入前无条件剥离客户端(模型)在 _meta 里自带的、以 botmux 开头的任意键,以及入站的 x-botmux-trusted-* 头——无论本轮是否存在宿主身份——因此模型无法通过工具参数伪造身份。
  • daemon → worker-pool → worker 全链路透传 trustedCaller(按飞书盖章的发送方 open_id/union_id 构造),喂给上述 Gateway provider。
  • 文档:docs-site/docs/{en,zh}/plugins.md 的 MCP Gateway 小节补充该身份注入契约(插件只应读取 botmux 命名空间键、缺身份须 fail-closed)。

为什么改

插件化的 Agent 工具(如按真实用户做权限/审计的数据查询类 MCP)需要绑定当前真实用户身份,但身份不能暴露给模型、也不能依赖模型传参(可被诱导伪造)。此改动把身份注入收敛到宿主侧 MCP Gateway:凭据与身份只存在于 worker 进程内存,模型只拿到不含身份的工具调用能力,Gateway 在转发时注入宿主盖章的身份并剥离客户端伪造值。这是一个通用原语,任何需要可信调用者身份的插件 MCP 都可复用,不绑定具体业务。

影响范围

  • 公共路径:core/plugins/mcp/(gateway、host)、worker.tsdaemon.ts、worker-pool 透传。属多 CLI × 多后端共用路径。
  • 对不消费该身份的既有插件 MCP 无行为影响:Gateway 的聚合/别名/资源路由不变,仅在 tools/call 转发时附加/清理 botmux 命名空间的 _meta 键与 x-botmux-trusted-* 头。
  • 不涉及授权写操作、删除类操作或发版流程。

验证

  • pnpm build 通过。
  • 相关测试绿:test/plugin-mcp-gateway.test.ts(含「有/无宿主身份下注入与剥离客户端伪造身份」用例)、write-input、codex-app-threads、daemon-codex-app-workflow-wiring、initial-user-turn-opening、group-join-shared-routing 等。
  • 运行时验证剥离不变量:无宿主身份 turn + 模型在 _metabotmuxTrustedCaller / botmux 前缀 sibling 键 / 大小写变体 → 下游插件均收不到伪造值;合法非 botmux 键保留;有宿主身份时注入宿主值并覆盖模型伪造值。
  • merge-treemaster 无冲突,git diff --check 干净。

备注

  • 早期版本中的租户私有 MCP 硬注入、按会话身份文件、主线 SQL 查询命令均已移除,PR 收敛为纯通用原语 + 配套文档。
  • PR 来自 fork 分支:yousay123:barry/data-mcp-identity-origin-mergeAllow edits by maintainers 保持勾选。

@yousay123
yousay123 requested a review from deepcoldy as a code owner August 18, 2026 12:10
@deepcoldy

Copy link
Copy Markdown
Owner

感谢这个 PR,把 Data MCP 的身份从「模型可见参数」收敛到 Botmux 可信 turn 通道、用飞书盖章的 open_id/union_id 做信任根、并做 fail-closed,这个方向是对的,代码组织和测试覆盖也很用心(本地 pnpm build 通过,相关 6 个测试文件 520/520 全绿,merge-tree 对 master 无冲突)。

不过在自动化复核里发现 3 个建议在合入前先解决的问题(附实际验证):

1)可信身份文件在同一 OS 用户下可被模型进程改写(最关键)
trusted-turns/<sessionId>.json 用 0600 保存,但它只隔离「其他 Unix 用户」,隔离不了同 UID 的 Codex/shell 进程。Codex 默认带 --dangerously-bypass-approvals-and-sandbox,模型知道 SESSION_DATA_DIR + BOTMUX_SESSION_ID 后即可直接覆盖该文件。运行时探针复现:写入一个同 UID 伪造文件(union_id=on_FORGED),proxy 会正确删除模型内联传入的身份字段,但会忠实注入文件里的伪造 union_id——即 per-user 权限/审计身份可被冒充。建议:可信身份保留在 daemon/worker 宿主内存,由宿主侧 MCP gateway 在调用时注入,不要落到模型同 UID 可写的文件。

2)ksher 专属 MCP 被无条件注入进公共 Codex 适配器
src/adapters/cli/codex.tsmcp_servers.platform-ksher-agent-data-mcp(指向 ~/.config/ksher-agent-data-mcp/launcher.sh)硬编码进了「每一个 Codex TUI 会话」,没有任何 config gate / per-bot opt-in。由于 botmux 是公共 npm 包,非 ksher 环境每次开 Codex 都会多出一个指向不存在 launcher 的坏 MCP server,并可能覆盖用户自带的同名配置。建议:改为显式 per-bot opt-in,租户私有的 launcher 路径/工具名移出公共适配器。

3)metadata query 的 SQL guard 可绕过
validateMetadataSql 对不带反引号的 url('...') / remote(...) 能拦下,但加反引号的表函数会绕过——运行时复现:... a JOIN \url`('http://.../','JSON') b ...与逗号形, `url`(...) 均被判为 ALLOW,可在 ClickHouse 侧形成 SSRF / 访问外部数据源;dictGetString('arbitrary_dictionary', ...)` 也被放行。建议不要只补正则,改为 AST 级校验 + ClickHouse 侧最小权限只读账号 + 函数白名单;这条 host 命令同样建议加 feature gate。

另外几个非阻断的小项:src/services/data-agent-client.tsbuildTrustedCallerWithUnionFallback 目前生产代码零引用(前者还保留了把身份写进 prompt 的旧兼容分支,建议删或接上);sandbox 模式下 ~/.config/ksher-agent-data-mcp 被 deny,proxy 的上游 launcher 起不来,因此该能力实际是 non-sandbox-only(沙盒里的 trusted-turn 文件 bind 属于「跑不起来的功能上的防御」,且精确文件 bind + 原子 rename 在跨 turn 时会读到旧身份);Data MCP 目前只有 codex TUI 接了 proxy,codex-app 没有真实消费。

以上是自动化评审的初步意见,可能有理解偏差,最终以维护者审阅为准。辛苦啦 🙏

@yousay123

Copy link
Copy Markdown
Contributor Author

已按自动化复核意见补充修复,并同步合并最新 master 解决冲突,当前 PR 分支已更新到最新提交:

  • 4f8e899b fix(mcp): 收紧 Data MCP 默认发布边界
  • 74bc3767 chore(mcp): 合并 master 解决 PR 冲突

本次针对 review 中提到的阻断项做了以下处理:

1. 移除同 UID 可改写的 trusted-turn 文件链路

认可 review 中关于 trusted-turns/<sessionId>.json 的风险判断:0600 只能隔离其他 Unix 用户,不能隔离同一 OS 用户下的 Codex/shell 进程。因此这版不再把可信身份落到模型进程可访问的 per-turn 文件里。

已处理:

  • 删除 src/utils/trusted-turn-store.ts
  • 删除 test/trusted-turn-store.test.ts
  • 移除 worker 侧 publish/clear trusted-turn 文件逻辑
  • mcp-identity-proxy data-agent 不再读取文件注入身份,改为 fail-closed
  • 后续真正启用 Data MCP 身份注入时,应改走 host-owned MCP gateway / worker 宿主侧内存注入,不再依赖模型同 UID 可写文件

2. 移除公共 Codex adapter 中的私有 MCP 默认注入

认可 review 中关于公共 npm 包不应无条件注入租户私有 MCP 的意见。

已处理:

  • src/adapters/cli/codex.ts 移除默认注入的 mcp_servers.*data-mcp* 配置
  • 同步调整 wrapper 参数识别逻辑和测试预期
  • 公共 Codex TUI 会话现在不会默认带私有 Data MCP server

3. 收紧 metadata query 默认发布边界

认可 review 中关于 SQL guard 不能只靠正则补洞的意见。当前先把该 host 命令改成默认关闭,避免公共发布时暴露高风险查询入口。

已处理:

  • botmux metadata query 默认 fail-closed
  • 只有显式设置 BOTMUX_METADATA_QUERY_ENABLED=true 时才会读取 host 凭据并执行
  • 默认 env 路径和默认库名已泛化,不再硬编码私有环境
  • 补充拒绝反引号表函数、外部 table function、dictGet* 的回归测试

4. 额外清理

  • 删除未接入生产主链路、且保留旧 prompt 身份透传说明的 src/services/data-agent-client.ts
  • 补齐 ListCodexAppThreadsOptions.initializeTimeoutMs 类型字段,修复合并后 build 暴露出的类型缺口
  • 合并最新 origin/master 后解决 src/types.tssrc/worker.ts 冲突;解决时保留上游新增的 MojoConfig / MojoLivePatch 字段,同时保留本 PR 所需的 trustedCaller 透传字段

验证

已在本地执行并通过:

  • pnpm build
  • pnpm test -- metadata-query trusted-mcp cli-adapters codex-app-threads
    • 390/390 passed
  • pnpm test -- write-input
    • 136/136 passed
  • pnpm test -- worker-pipe-initial-screen-order worker-durable-expiry-order restart-live-worker-env
    • 57/57 passed
  • git diff --check

另外已复查以下关键字在 src test scripts 下无命中:

  • ksher
  • trusted-turn-store
  • trusted-turns
  • BOTMUX_TRUSTED_TURN_FILE
  • data-agent-client
  • platform-ksher-agent-data-mcp
  • ksher-agent-data-mcp

当前页面还显示 Review required1 workflow awaiting approval,这两项需要 maintainer / code owner 侧审批 workflow 和 review。辛苦再看一下,谢谢。

@deepcoldy

Copy link
Copy Markdown
Owner

感谢快速跟进,也认可这版按 review 意见做的收敛(4f8e899b/74bc3767)——把同 UID 可改写的 trusted-turn 文件链路移除、proxy 改为 fail-closed、去掉对公共 Codex 适配器的无条件注入、并给 metadata query 加了默认关闭的显式开关。自动化复核确认这三项风险已消除(pnpm build 通过、相关测试全绿、merge-treemaster 无冲突)。

不过基于当前形态,想和你同步一个架构层面的建议:与其把这套能力以「收敛后默认关闭」的形式并入主线,不如按 Botmux 既有的插件 + MCP Gateway机制重做——这样能力更内聚、复用性更好,也不需要往公共代码路径塞任何租户专属内容。具体:

1)业务能力做成一个 Botmux 插件(而非进主线)
Botmux 插件本就用一个 npm 包同时交付 Skill + 一个 MCP server(见 docs-site 的 plugins 文档)。你们这套「查元数据字典 → 确认表/口径 → 生成业务 SQL → 按行数走导出/审批」的流程非常适合:

  • 流程性知识写成插件自带的 Skill;
  • Data MCP 作为插件贡献的那一个 MCP server(stdio 或 URL 均可),由宿主侧统一的 MCP Gateway 托管;
  • 通过 per-bot 绑定只对你们自己的 bot 生效,通用用户完全不受影响——这从根本上避免了此前「私有配置注入公共适配器」的问题;
  • 凭据/连接信息按文档要求从受控环境或插件私有配置读取,不进包、也不进模型可见参数。

如果这套具备通用性,还可以按官方 @botmux-ai/plugin-<id> 约定发布到 botmux-ai/plugin-market,其他部署也能复用。

2)唯一值得进主线的通用原语:Gateway 的「按 turn 注入宿主可信身份」
你要的「不可伪造的真实用户身份」这半,靠 Skill(提示词)保证不了,得在宿主侧完成。Botmux 的 MCP Gateway 已经持有凭据、只把带鉴权的 socket 能力交给 CLI,模型碰不到凭据本身——把身份注入放在 Gateway 转发那一层,模型就无从篡改。目前 Gateway 转发会带插件声明的静态 headers,但「把当前 turn 的真实用户身份(IM 平台盖章的用户 id)动态注入进转发」这一步还没有通用挂点。这一小块是唯一建议进主线的通用改动(可选、按 bot 配置开关),做好后任何插件都能复用,而不局限于单一场景。

这样拆分后:通用侧只多一个「per-turn 身份注入」能力,租户专属的 Data MCP + 查询/导出/审批流程作为独立插件维护,主线保持干净。

以上是自动化评审的初步意见,可能有理解偏差,最终以维护者审阅为准。这版本身安全面已经干净、且默认关闭,是否直接合入、还是按上面思路重做,请维护者定夺。辛苦!

@yousay123

Copy link
Copy Markdown
Contributor Author

本次已按 review 建议把主线收敛为 Gateway 通用可信身份注入,Data MCP 业务能力已拆到私有插件骨架,不进入 botmux 公共主仓。

@yousay123

Copy link
Copy Markdown
Contributor Author

已按插件化方向继续调整:

  1. 主线只保留通用能力
  • 新增 MCP Gateway per-turn trusted identity 注入能力。
  • Gateway 从 worker 宿主内存读取当前 turn 的可信 trustedCaller,不落文件,也不信任模型参数。
  • tools/call 会注入 _meta.botmuxTrustedCaller,HTTP MCP 请求也会动态带 x-botmux-trusted-* headers。
  • 已补测试验证:调用方伪造的 _meta.botmuxTrustedCaller 会被 Gateway 宿主身份覆盖。
  1. Data MCP 业务能力已拆到私有插件
  • 不再进入 botmux 公共主仓。
  • 插件已放入私有 Data MCP 仓库:ksher_agent_data_mcp/botmux-plugin-data-mcp/
  • 插件只在我们自己的 bot 上启用,后续由我们自己维护和发布。

验证:

  • pnpm build
  • pnpm test -- plugin-mcp-gateway
  • pnpm test -- plugin-mcp-sandbox worker-durable-expiry-order raw-input-followup-atomicity

@deepcoldy

Copy link
Copy Markdown
Owner

感谢采纳上一轮的架构建议,这版 feat(mcp): 支持 Gateway 注入可信调用身份cee15e84e)方向很对,而且把最关键的问题修对了:

已正确解决的(赞)

  • 可信身份现在由 MCP Gateway 的一个「按 turn 取当前身份」provider 回调提供,身份只存在 worker 进程内存中、不再落任何按会话的文件——这从根上消除了之前「同 UID 进程可改写 trusted-turn 文件」的伪造面。这正是宿主侧注入的正确形态。
  • 之前那条一路穿到 worker 却无人消费的 trustedCaller 管道,现在真正被 Gateway 消费了。

建议合入前再修一处(附实跑验证)

🔴 Gateway 注入身份时没有清理「客户端自带的身份」。之前那版(已删)的 stdio 代理会在注入前无条件删掉客户端传来的身份字段(redactTrustedIdentityFields),这版把这条不变量丢了——withTrustedTurnMeta 只在有宿主身份时覆盖 botmuxTrustedCaller 这一个键,没有宿主身份时直接原样返回 params。我按你的测试同款 harness 加了两个探针实跑确认:

  • 探针 A(无宿主身份的 turn):trustedTurnIdentity() 返回 undefined 时(系统/恢复 turn、发送方无 open_id、或 raw_input/spawn/kill 刚清空 currentGatewayTrustedCaller 之后),模型在 tools/call 里自带的 _meta.botmuxTrustedCaller: { requestUserOpenId: 'ou_forged', ... }原样传到下游插件(实跑:下游收到了 ou_forged)。下游插件若信任 _meta.botmuxTrustedCaller,即被冒充。
  • 探针 B(有宿主身份):即便宿主身份存在,withTrustedTurnMeta 只覆盖 botmuxTrustedCaller 单键,模型塞的同级其它键(例如 botmuxImpersonatedUnionId)照样进入 _meta 传到插件(实跑:sibling 键存活)。

建议修法:在注入前无条件删除 _meta 里的保留身份键——至少 botmuxTrustedCaller,更稳妥是约定一个 botmux 保留前缀、把该前缀下的键整体清掉——然后再写入宿主的可信值(即恢复旧代理 redact 那一步的语义,而不是「有才覆盖」)。同一处的 HTTP header 分支同理,最好也在无宿主身份时显式不带/清掉 x-botmux-trusted-*。并建议补一个「无宿主身份 + 模型伪造 _meta」的回归用例,断言下游收不到伪造身份。

🟡 metadata query 的 SQL 校验仍有几处可绕过(非阻断,因为该命令默认关闭,但任何环境启用前应一并收紧)validateMetadataSql 能挡住 JOIN 形式的表函数,但下面几种仍被放行(import dist 实跑确认):

  • 逗号写法的表函数:FROM 白名单表, merge('db','.*') / , numbers(...) / , generateRandom(...)(逗号段被解析跳过,merge 等不在黑名单)→ 可越权读任意表;
  • LIMIT 2 BY col:分组每组 2 行、总行数无界,绕过行数上限;
  • INTO OUTFILE '/path':未拦,账号有权限时可写服务端文件。

建议:FROM 段出现任何 标识符( 一律拒(而不是逐个函数名黑名单),并把 LIMIT ... BY / INTO OUTFILE 加入拒绝;更稳的是配合 ClickHouse 侧只读最小权限账号。

验证:pnpm build 通过、相关 175 个测试全绿、merge-treemaster 无冲突。以上为自动化评审的初步意见,可能有理解偏差,最终以维护者审阅为准。整体这版进步很大,主要就差 strip 这一处,辛苦!

@yousay123

Copy link
Copy Markdown
Contributor Author

已采纳并修复这条 strip 反馈。Gateway 现在会在注入前无条件剥离模型侧传入的 botmux 保留身份字段:_meta 中所有 botmux* 键会先被清理,再写入宿主侧可信的 botmuxTrustedCaller;无宿主身份时也不会把模型伪造的 botmuxTrustedCaller 下传给插件。HTTP transport 同样会先清理 x-botmux-trusted-*、x-botmux-turn-id、x-botmux-dispatch-attempt,再按宿主身份重建。已补回归用例覆盖无宿主身份伪造、有宿主身份 sibling 键伪造,以及 HTTP header 伪造清理。实跑验证:git diff --check、pnpm build、pnpm test -- plugin-mcp-gateway plugin-mcp-sandbox worker-durable-expiry-order raw-input-followup-atomicity 均通过,其中 62 passed、3 skipped。metadata query 相关反馈当前已不适用,因为该命令和 SQL guard 已从 botmux 主线 PR 中移除。

@deepcoldy deepcoldy changed the title fix(mcp): 修复 Data MCP 身份注入链路 feat(mcp): MCP Gateway 注入宿主可信调用身份 Aug 19, 2026
说明 Gateway 注入的可信身份出现在 _meta.botmuxTrustedCaller 与 x-botmux-trusted-* 头,
插件只应读取 botmux 命名空间键(注入前无条件剥离客户端自带同名键,模型无法伪造),
缺身份时须 fail-closed,身份仅存于宿主进程内存不落文件。中英文档同步。

Co-Authored-By: Claude <noreply@anthropic.com>
@deepcoldy
deepcoldy force-pushed the barry/data-mcp-identity-origin-merge branch from 440c213 to ea6d820 Compare August 19, 2026 15:32
@deepcoldy
deepcoldy merged commit 3d84aaa into deepcoldy:master Aug 19, 2026
deepcoldy pushed a commit that referenced this pull request Aug 20, 2026
#917 给 fixture echo 回显加了 :meta={} 段,同步改了 gateway 测试却漏改 sandbox 测试,其整段连续子串断言被切断导致 Linux CI 红(该文件仅在 Linux+bwrap 跑,macOS 本地 skip)。本 PR 将断言拆成两段 toContain 对齐 gateway 写法,不改运行时,也不放宽 token 不进沙箱的隔离断言。

验证:master CI 红点字节级坐实为该两条 sandbox 用例(17176 passed/2 failed);PR head CI 上 sandbox 用例真跑非 skip 全过(17178 passed/0 failed,零回归);全仓排查确认无第二处同类漏改;反向变异确认 token 泄漏/session 串错/args 篡改/服务器冒充下新断言仍失败,安全语义完整。
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.

2 participants