Skip to content

🐛 fix(sdk): 插件安全域八项修复——权限哈希收口、cmd 审计、fail-closed 默认与信任模型文档 - #434

Merged
CavinHuang merged 18 commits into
mainfrom
fix/sdk-plugin-security
Aug 22, 2026
Merged

🐛 fix(sdk): 插件安全域八项修复——权限哈希收口、cmd 审计、fail-closed 默认与信任模型文档#434
CavinHuang merged 18 commits into
mainfrom
fix/sdk-plugin-security

Conversation

@CavinHuang

Copy link
Copy Markdown
Owner

概述

修复 sdk 全量 review 提出的 8 个 issue(plugins 安全域;#301/#302 已由 main 侧 PR#420 上游修复,本分支 rebase 时冗余 commit 已 skip),另含 review 环节追加发现的 2 个同机制漏洞收口。

Issue 修复
#315 权限哈希纳入完整 env 键值对与 metadata(stableStringify 键排序保证顺序无关);并修 review 发现的 args 排列绕过——canonicalize 不再对数组排序,cp A Bcp B A 哈希必变,无序集合改显式 deepSortStringArrays
#316 LEGACY/CODEX deny 名单死名改真名(Read/Write/Edit/Agent);sidecar permission-interceptor 与 market-service 风险标签同步
#317 win32 非 .exe/.com 经 cmd.exe 前对模型可控 payload 做元字符审计(%VAR%(含子串语法)/& | < > ^/换行),命中即 fail-closed 拒绝
#344 checkSensitiveCapability 按当前接受哈希过滤审批记录 + 接受新哈希时修剪旧 bundle;空哈希通配仅限存量数据兼容
#345 can-use-tool 入口接线 isHardDeniedTool:插件 tools.deny 在 bypass 判定前无条件拒绝,硬拒绝承诺真正生效
#346 codex 格式适配器权限改显式字段驱动 fail-closed 映射:mcpServers/hooks 声明才启用、shell 恒 false、缺省回落全拒基线
#347 hooks/mcp 配置内容哈希(capabilityFileHash 复用),审批后改文件不再免重审生效
#296 README 新增「插件信任模型」小节:进程内 import() 无沙箱、安装即宿主全权、三条真实防线

Review 追加修复

  • allow_always 空哈希通配收口:交互式持久化记录现写入 gateResult 计算的真实 permissionsHash(仅退化场景回落空串),失真注释重写——堵住「主审批路径豁免哈希过滤」的未来化绕过面。

验证

  • bun run typecheck 六 workspace 全绿
  • packages/sdk plugins 套件 101 pass / 0 fail;sidecar plugins+permissions 139 pass / 1 fail(唯一失败为 plugin-hooks-bridge 真实 spawner 环境性预存失败,干净基线同样失败)
  • 独立 security review 通过:11 commit 攻击性验证(根外路径变体、entry 逃逸形态、cmd 元字符清单、哈希顺序无关性、bypass 前置位置等),追加发现均已在本轮修复

Closes #315, closes #316, closes #317, closes #344, closes #345, closes #346, closes #347, closes #296

🤖 Generated with Claude Code

TaTaLiao and others added 13 commits August 22, 2026 18:07
win32 非 .exe/.com 命令经 execFile('cmd.exe', ['/d','/s','/c',...]) 执行,
末参是模型可控的 JSON payload;MSVCRT 引号规则防不住 cmd 的 %VAR% 展开与
& | < > ^ 操作符,等于手工绕开了 Node 对 .bat/.cmd 的 EINVAL 硬化。现对
走 cmd.exe 路由的 payload 审计元字符,命中即拒绝执行并返回明确错误;
清单内 command/args 属已审批内容(纳入权限哈希)不在审计范围。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
canonicalSummary 此前只取 envKeys 键名不收值、metadata 整体缺席:审批后
插件把 env.TARGET 换成注入值或把 metadata.isReadOnly 翻 true(直接改变
isReadOnly 谓词→auto-allow 分类)均不改变哈希,脏授权跨配置复用。现
commandTools 摘要纳入完整 env 键值对与全量 metadata;哈希变化即视为配
置变更触发重审,此为预期语义。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
canonicalSummary 中 hooks/mcpServers 只贡献配置路径,仅 lsp 有内容哈希:
审批通过后直接改磁盘上的 hooks.json(hook 命令换成任意程序)或 mcp.json
(换 server 启动命令)哈希不变保持 loaded,新命令全程免重审生效。现复用
capabilityFileHash 增加 hooksConfigHash/mcpServersConfigHash,配置文件内
容变化与 commandTool 变化同等触发重审。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LEGACY/CODEX allow/deny 名单写死 FileRead/FileWrite/FileEdit/AgentTool 等
死名,而 SDK 内建工具真名为 Read/Write/Edit/Agent,checkToolPermission
精确比对导致这些 deny/allow 项永不命中、名单形同虚设。现全部改为真实工
具名(Bash 等本已有效的项保留),并同步更新钉死死名的测试。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
适配器此前无条件硬编码 register:true/shell.allow:true/全 hook 事件:仅凭
存在一份未知来源的 codex 清单即获得 MCP 注册、LSP 代码执行入口(shell.allow
门)与全部 hook 事件过滤通过,默认方向与 lume 格式全 false 相反。现改为
显式字段驱动:mcpServers 声明才 register:true,hooks 声明才授事件表,
shell 恒为 false;缺省回落与 lume inferDefaults 一致的全拒绝基线。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
resolveSensitiveApproval 只按 key/scope/workspace 匹配、collectSensitiveApprovals
跨版本合并不过滤 permissionsHash:换命令触发重审并接受新哈希后,旧 allow 依
然命中新配置,审批跨配置永续。现 checkSensitiveCapability 按当前接受哈希过
滤审批记录(空哈希视为通配以兼容存量数据),且 installMarketItem 接受新哈希
时删除 approvalsByHash 中的陈旧 bundle。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
permission-interceptor 的 FILESYSTEM_TOOLS 写死 FileRead/FileWrite/FileEdit
死名,内建 Read/Write/Edit 永远进不了插件文件系统门(仅 Glob/Grep 生效);
market-service 高危工具风险标签同病。现统一改为真实内建工具名并同步测试。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
isHardDeniedTool 此前零生产调用方,"硬拒绝不可被 bypassPermissions 覆盖"
的承诺未接线。现在 can-use-tool 入口处对来源插件的 permissions.tools.deny
调用 isHardDeniedTool,命中即在 bypass 判定之前无条件拒绝;来源绑定不变,
未声明 deny 的插件与无来源的内建工具不受影响。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JS 插件入口经 import() 在宿主进程内执行、无沙箱,权限门只约束声明的能力
键,安装插件等同授予宿主全部权限——该信任模型此前未文档化。新增小节说明
进程内加载语义、安装即信任的边界,以及加载根白名单/权限哈希复审/命令工具
最小环境三条真实防线;纯文档,不改行为。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
canonicalize 对所有纯字符串数组执行 sort(),commandTools[].args 因此被排
序:同元素反序(cp A B → cp B A)产生相同哈希,审批通过后仅调换参数顺
序即可绕过 needs-review 门按反转语义执行。现 canonicalize 保留数组原序,
无序集合(权限 pattern/allow/deny/ask 列表)改由 deepSortStringArrays 在
canonicalSummary 内显式排序,对象键排序语义不变;修正失实 docstring 并补
排列负例测试。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
持久化 allow_always 记录此前写 permissionsHash: "",而空哈希在 #344 过滤
语义中定义为通配,主审批路径因此整体豁免哈希过滤。现
checkSensitiveCapability 结果携带其据以判定的当前哈希,sensitive-gate 透
传,can-use-tool 写入 gateResult.permissionsHash(仅无任何已接受哈希的退
化记录回落空串通配),并修正失真注释。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CavinHuang

Copy link
Copy Markdown
Owner Author

Code Review

八项逐项核对完毕,全部真实落地、方向正确、CI 6/6 绿(含 Computer Use 双平台)、无 P0/P1,可合

亮点核验

非阻塞 P3 记录(建议 PR 描述或 follow-up 记档)

  • cmd 审计在 sandbox 分支之前生效:processIsolation 开启时 spawn 不经 cmd.exe 但 payload 含 & < > ^ % 的合法业务内容仍被硬拒——对已隔离插件属过度拦截
  • 存量 permissionsHash: "" 审批继续按通配放行(有意取舍,已注释明示)
  • 哈希格式变更使全部已装插件一次性翻转 needs-review(预期迁移成本,需发版说明)
  • inputSchema 内 string 数组保序,schema 重排会触发多余重审(纯噪音)

BEHIND,merge main 过 gate 后即可合。

@CavinHuang
CavinHuang merged commit dbee2d1 into main Aug 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment