尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Qwen Code Shell 命令安全性三态分类:基于 tree-sitter 的 Plan 模式只读判定架构解析

Qwen Code Shell 命令安全性三态分类:基于 tree-sitter 的 Plan 模式只读判定架构解析 Qwen Code Shell 命令安全性三态分类基于 tree-sitter 的 Plan 模式只读判定架构解析【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读Qwen Code终端中的开源 AI 编程代理在 Plan 模式下需要判断一条 shell 命令是否为纯只读操作以决定能否直接放行。然而布尔值只读/非只读无法保留命令行为在静态分析下无法确定这一关键信息。本文深入讲解 Qwen Code 引入的三态安全事实层read-only/write/unknown它实现在 shellAstParser.ts 中基于 web-tree-sitter tree-sitter-bash 对命令进行真实语法分析。读完本文你将掌握三态分类的完整契约、解析器失败时的降级策略、受支持的证据规则边界、现有布尔消费者的迁移路径以及 Plan 模式如何消费这一事实层做一次性审批。背景为什么布尔值不够用Issue #6949 要求 Plan 模式能够区分已被证明只读的命令与无法静态确定其行为的命令。此前基于正则的isShellCommandReadOnly()只能回答是/否一旦遇到无法判定的命令只能保守地当作非只读处理这会导致两类问题真实只读但写法复杂的命令如带子 shell、进程替换、复杂重定向的命令被误伤无法表达不确定也就无法设计放行但要求一次性审批这类中间策略。因此本次改动在 shellAstParser.ts 中引入三态事实层同时明确不改变Shell、Monitor、PermissionManager、推测speculation、记忆作用域代理memory-scoped agent、ACP、Plan 模式提示词与 Plan 退出行为的权限路由与调用点。现有布尔消费者可以在分类器加固后变得更保守后续改动可以借助新的三态事实将unknown命令路由到一次性审批而无需改动分类器本身。三态分类契约classifyShellCommandSafety(command)是模块内部 API源码见 shellAstParser.ts返回三种结果结果含义read-only当前规则可证明所有可执行路径都不会修改持久化或外部状态write语法中存在文件、Git、进程或其他状态变更的明确证据命令最终是否成功执行不影响判定unknown现有静态规则既无法证明安全也无法证明会变更状态源码中类型定义为export type ShellCommandSafety read-only | write | unknown;shellAstParser.ts并提供两个入口classifyShellCommandSafety(command)仅分析给定的源字符串classifyShellCommandSafetyInDirectory(command, cwd)额外结合工作目录下的 Git 本地配置风险如diff.external、fsmonitor做判定供 Plan 模式权限边界使用shellAstParser.ts。结果合并优先级对于一个合法的 AST多个语句的结果按write unknown read-only的顺序合并——mergeSafety()的实现即先看是否包含write再看是否包含unknownshellAstParser.ts。具体规则包括包含ERROR节点的树在评估部分语法之前即被判定为unknown命令替换与进程替换施加unknown下限floor同时会扫描其内部的可执行内容——如果嵌套内容含已知写入命令结果可提升为write重定向分析独立拥有替换节点内的替换扫描命令与语句求值器会从自己的替换扫描中排除重定向节点避免嵌套替换被重复遍历控制流如if/while/for使用同样的unknown下限并扫描所有可能分支函数定义不算执行因此保持unknown且不会把函数体当作已执行写入来分类。兼容性行为与边界独立纯赋值语句与cd保留原有兼容行为视为只读前缀在命令前、或与其他语句组成复合序列的赋值语句施加unknown下限——因为LD_PRELOAD、PATH、PAGER或工具特定配置会改变命令行为显式写入证据仍然优先write胜出子 shell 与命令组聚合其实际执行的内容该 API 只分析提供的源字符串不解包sudo或解释器不解析 PATH 或别名也不加载 shell 配置。从源码看环境变量前缀的处理在evaluateCommandSafety()末尾若命令节点带variable_assignment子节点则结果与unknown合并shellAstParser.ts。解析器失败与兼容 API诚实的不确定 vs 保留可用性这一层存在一个刻意的不对称设计私有分类器AST 路径可能抛错加载或运行 tree-sitter 时。公开的三态 API 把这些失败统一映射为unknown绝不用正则结果替代——因为新消费者需要的是诚实的不确定事实而不是正则的伪确定。实现上classifyShellCommandSafety()直接catch(() unknown)shellAstParser.ts。解析抛错的 parser 实例会被丢弃并重建parseShellCommand()捕获异常后置空单例用已加载的 Bash 语言重新构造实例不重新加载运行时或语言shellAstParser.ts。之所以要重建是因为失败的实例可能已被污染。语法无效的树root.hasError是正常的unknown结果而非解析器失败因此永远不会进入正则回退路径shellAstParser.ts。每个成功返回的 tree 都在finally块中释放一次tree.delete()见 shellAstParser.ts避免 WASM 内存泄漏。兼容 APIisShellCommandReadOnlyAST()现有布尔 APIisShellCommandReadOnlyAST()shellAstParser.ts只有 AST 判定为read-only时才返回true但保留原有正则回退当 WASM 初始化永久失败如符号链接安装后 WASM 文件缺失时回退到 shellReadOnlyChecker.ts 的正则检查器并叠加本地 Git 配置风险检查保证代理仍可用而不是挂死或崩溃shellAstParser.ts运行时异常如首次调用时的 WASM 初始化错误同样回退到正则检查器。这一不对称是有意为之新消费者需要诚实的不确定事实而现有布尔消费者在显式迁移之前保留其解析器可用性行为。此外解析器单例初始化采用惰性单例 失败永久标记parserInitFailed一旦 WASM 初始化失败后续调用直接抛错并走回退防止反复重试导致代理挂起shellAstParser.ts。受支持的证据规则分类器识别一个有界、大小写敏感的规则集合全部见 shellAstParser.ts 顶部常量区命令级证据直接文件系统写入命令chgrp|chmod|chown|cp|install|ln|mkdir|mkfifo|mknod|mv|rename|rm|rmdir|shred|touch|truncate|unlinkWRITE_ROOT_COMMANDL119-L120带--help时降为unknown进程信号命令kill/killall/pkill由processSafety()判定——信号 0探测与列出信号的选项为unknown否则为writeL928-L960输出重定向、、、、|为写WRITE_REDIRECT_OPERATORSL162指向文件描述符/-时安全指向含扩展字符的目标为unknownevaluateRedirectionSafety()L1066-L1089命令替换与进程替换内部可执行内容被扫描L962-L987。Git 变更族evaluateGitSafety写子命令add|am|checkout|cherry-pick|clean|clone|commit|fetch|gc|init|merge|mv|pull|push|rebase|reset|restore|revert|rm|stash|switchWRITE_GIT_SUBCOMMANDL136-L137只读子命令blame|branch|cat-file|diff|grep|log|ls-files|remote|rev-parse|show|status|describeL122-L135但--dry-run/-n及--help会降级为unknowngit diff/log/show的--output输出文件是写入evaluateOutputOption()L776-L789git branch的变更标志-c/-C/-d/-D/-m/-M/-u及--delete/--move/--copy/--set-upstream...等为写纯列表标志-a/-l/-r、--all/--list/--remotes/--show-current等为只读L846-L861git remote的add|remove|rm|rename|set-branches|set-head|set-url|update为写prune无--dry-run时为写L828-L845git blame --output为unknown。特殊命令的显式写模式find-delete、-fls/-fprint/-fprintf为写-exec/-execdir/-ok/-okdir内部命令被递归分类L866-L905sed/awk使用共享的线性扫描器shell-safety-rules.ts 中classifySedCommandSafety/classifyAwkCommandSafety能区分内联程序与选项值、文件参数因此转义、畸形或高度重复的输入不会触发正则回溯也不会从文件名中伪造写入证据sed的w/W命令、s///w写标志为写e/r/R执行类为unknownawk的system()、close()、getline、间接调用、输出重定向为写/未知sort/tree-o/--output写文件为写sort --compress-program为unknownuniq超过一个位置参数输出文件为写tee带文件参数为写dd出现of为写printf -v带状态变量赋值形式为unknown。明确失败关闭fail-closed的集合以下情况一律unknown显式 Git helpers 与签名验证--ext-diff、--filters、--show-signature、--textconv、--open-files-in-pager以及git log/show中的%G[?GKFPST]签名占位符grep 的外部 pager-O不支持的 Git 全局选项与子命令 help 路径help 可能启动外部查看器动态执行命令/进程替换、$数组展开、外部脚本、模糊输出目标、解释器与 wrapperripgrep 预处理器--pre、--hostname-bin、归档搜索--search-zip、-z普通 pager 命令less/more为unknownsort --compress-program。同时选项终止符--与受支持选项的值元数会被解释因此字面命名为--help的文件名或消息不会被误判为 help 调用见beforeTerminator()与hasHelp()L754-L765。不同大小写的命令名、未列出的包管理器、服务与自定义可执行文件也保持unknown——分类器不是沙箱。同步检查器deprecated的保守镜像废弃的同步检查器 shellReadOnlyChecker.ts 镜像了同步调度所需的每一个新拒绝模式使用哨兵保留参数展开而不是让shell-quote抹掉它们拒绝畸形的尾部管道与带赋值的复合语句从原始命令评估 wrapper不解包有意保持布尔且比 AST 分类器更保守printf、选项密集的sort/tree/uniq/rg/ripgrep、以及超出最简单列表模式的 Git branch 形式都会串行执行不进并发批次。消费者与迁移边界当前布尔消费者包括消费者说明Shell 工具packages/core/src/tools/shell.ts执行 shell 命令Monitor 工具packages/core/src/tools/monitor.ts监控类命令PermissionManagerpackages/core/src/permissions/permission-manager.ts权限路由推测门speculation gatepackages/core/src/followup/speculationToolGate.ts记忆作用域代理配置packages/core/src/memory/memory-scoped-agent-config.ts这些调用点在本重构中不改变。同步检查器还服务于核心工具调度器coreToolScheduler.ts与遗留 shell 权限工具。调度器现在把原始命令传给检查器使 wrapper 保持unknown而不是被解包成看似只读的命令。extractCommandRules()最小作用域通配符权限规则提取shellAstParser.ts独立于安全分类继续负责从命令中提取git clone *、npm install *这类规则。Plan 模式消费三态事实后续落地示例文档约定后续提交fix(core): Route unknown Plan shell commands to one-off approval应只在 Plan 权限边界消费classifyShellCommandSafety()并单独定义审批来源provenance、有效期、ACP 行为及与 Plan 退出的交互——这些策略不属于事实层。仓库中已落地的消费示例是 plan-mode-shell-policy.tsevaluatePlanModeShellPolicy()在ApprovalMode.PLAN下对 Shell/Monitor 命令调用classifyShellCommandSafety(InDirectory)plan-mode-shell-policy.ts随后decoratePlanModeShellConfirmation()对unknown结果附加本次调用一次性审批、可能修改系统状态的警告并隐藏始终允许选项validatePlanModeShellApproval()通过上下文快照比对模式、权限策略、工作目录、参数深比较保证审批只对完全相同的调用有效任何变化都会使其过期plan-mode-shell-policy.ts。Claude Code 参考两个设计原则Claude Code 的 Bash 分析为两个设计原则提供了佐证解析不确定性必须显式表示当解析不可用或过于复杂时权限决策必须失败关闭。但 Qwen Code 只复制了原则没有复制其更大的 Bash 解析器与策略引擎——当前边界只需要一个小型分类器。验证与测试覆盖单元测试采用表驱动矩阵覆盖见 shellAstParser.test.ts 与 shell-ast-parser-lazy.test.ts三态全部结果只读放行、变更命令拒绝rm、重定向、命令替换、换行分隔的 CVE 式攻击、find -exec、sed原地编辑、awk system()、Git push 等复合优先级/||/;/|/链式命令中任一写入则整体拒绝替换反引号与$(...)命令替换、进程替换、variable_assignment内的替换语法错误空命令、畸形尾部管道、赋值复合语句解析器初始化与运行时失败通过_resetParser()/_setParserFailedForTesting()模拟 WASM 初始化失败与运行失败验证三态 API 返回unknown、兼容 API 走正则回退对抗性嵌套与转义输入的有界行为sed/awk 线性扫描器保证不回溯、不从文件名伪造写入证据兼容单调性同步检查器与调度器测试防止新发现的危险命令进入 Shell 并发批次。测试用例还覆盖了仓库本地 Git 配置风险diff.external、fsmonitorIssue #8575导致git diff/git status降级为unknown的场景以及cd之后使用 git 时不模拟目录变更、直接失败关闭的行为。总结三态 shell 安全分类是 Qwen Code Plan 模式权限体系的事实基础read-only放行、write阻止、unknown交给策略层决定如一次性审批。它用 tree-sitter AST 替代正则猜测用写 未知 只读的合并规则处理复合命令用诚实的unknown映射解析器失败同时为存量布尔消费者保留正则回退以维持可用性。对于希望理解或扩展 Qwen Code 权限模型的开发者建议从 shellAstParser.ts 的常量表与evaluate*Safety系列函数入手并以 shellAstParser.test.ts 的表驱动矩阵作为行为契约。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表