Feature Flag:
FEATURE_TREE_SITTER_BASH=1实现状态:完整可用(纯 TypeScript 实现,~7000+ 行) 引用数:3
TREE_SITTER_BASH 启用一个完整的 Bash AST 解析器,用于安全验证 Bash 命令。它用完整的树遍历安全分析器取代了旧的基于正则表达式的 shell-quote 解析器。关键属性是 fail-closed:任何无法识别的内容都被归类为 too-complex 并需要用户批准。
| Feature | 说明 |
|---|---|
TREE_SITTER_BASH |
激活用于权限检查的 AST 解析器 |
TREE_SITTER_BASH_SHADOW |
Shadow/观测模式:运行解析器但丢弃结果,仅记录遥测 |
核心设计使用 allowlist 遍历模式:
walkArgument()只处理已知安全的节点类型(word、number、raw_string、string、concatenation、arithmetic_expansion、simple_expansion)- 任何未知节点类型 →
tooComplex()→ 需要用户批准 - 解析器加载但失败(超时/节点预算/panic)→ 返回
PARSE_ABORTED符号(区别于"模块未加载")
parseForSecurity(cmd) 返回:
{ kind: 'simple', commands: SimpleCommand[] } // 可静态分析
{ kind: 'too-complex', reason, nodeType } // 需要用户批准
{ kind: 'parse-unavailable' } // 解析器未加载parseForSecurity(cmd)
│
▼
parseCommandRaw(cmd) → AST root node
│
▼
预检查:控制字符、Unicode 空白、反斜杠+空白、
zsh ~[ ] 语法、zsh =cmd 展开、大括号+引号混淆
│
▼
walkProgram(root) → collectCommands(root, commands, varScope)
│
├── 'command' → walkCommand()
├── 'pipeline'/'list' → 结构性,递归子节点
├── 'for_statement' → 跟踪循环变量为 VAR_PLACEHOLDER
├── 'if/while' → 作用域隔离的分支
├── 'subshell' → 作用域复制
├── 'variable_assignment' → walkVariableAssignment()
├── 'declaration_command' → 验证 declare/export flags
├── 'test_command' → walk test expressions
└── 其他 → tooComplex()
│
▼
checkSemantics(commands)
├── EVAL_LIKE_BUILTINS(eval, source, exec, trap...)
├── ZSH_DANGEROUS_BUILTINS(zmodload, emulate...)
├── SUBSCRIPT_EVAL_FLAGS(test -v, printf -v, read -a)
├── Shell keywords as argv[0](误解析检测)
├── /proc/*/environ 访问
├── jq system() 和危险 flags
└── 包装器剥离(time, nohup, timeout, nice, env, stdbuf)
| 模块 | 文件 | 行数 | 职责 |
|---|---|---|---|
| 门控入口 | src/utils/bash/parser.ts |
~110 | parseCommand()、parseCommandRaw()、ensureInitialized() |
| Bash 解析器 | src/utils/bash/bashParser.ts |
4437 | 纯 TS 词法分析 + 递归下降解析器 |
| 安全分析器 | src/utils/bash/ast.ts |
2680 | 树遍历安全分析 + parseForSecurity() |
| AST 分析辅助 | src/utils/bash/treeSitterAnalysis.ts |
507 | 引号上下文、复合结构、危险模式提取 |
| 权限检查入口 | src/tools/BashTool/bashPermissions.ts |
— | 集成 AST 结果到权限决策 |
文件:src/utils/bash/bashParser.ts(4437 行)
- 纯 TypeScript 实现(无原生依赖)
- 生成与 tree-sitter-bash 兼容的 AST
- 关键类型:
TsNode(type、text、startIndex、endIndex、children) - 安全限制:
PARSE_TIMEOUT_MS = 50、MAX_NODES = 50_000— 防止对抗性输入导致 OOM
文件:src/utils/bash/ast.ts(2680 行)
核心函数:
| 函数 | 职责 |
|---|---|
parseForSecurity(cmd) |
顶层入口,返回 simple/too-complex/parse-unavailable |
parseForSecurityFromAst(cmd, root) |
接受预解析 AST |
checkSemantics(commands) |
后解析语义检查 |
walkCommand() |
提取 argv、envVars、redirects |
walkArgument() |
Allowlist 参数遍历 |
collectCommands() |
递归收集所有命令 |
文件:src/utils/bash/treeSitterAnalysis.ts(507 行)
| 函数 | 职责 |
|---|---|
extractQuoteContext() |
识别单引号、双引号、ANSI-C 字符串、heredoc |
extractCompoundStructure() |
检测管道、子 shell、命令组 |
hasActualOperatorNodes() |
区分真实 ;/&&/` |
extractDangerousPatterns() |
检测命令替换、参数展开、heredocs |
analyzeCommand() |
单次遍历提取 |
TREE_SITTER_BASH_SHADOW 运行解析器但从不影响权限决策:
// Shadow 模式:记录遥测,然后强制使用旧版路径
astResult = { kind: 'parse-unavailable' }
astRoot = null
// 记录: available, astTooComplex, astSemanticFail, subsDiffer, ...记录 tengu_tree_sitter_shadow 事件,包含与旧版 splitCommand() 的对比数据。用于在不影响行为的情况下收集遥测。
- Allowlist 遍历:只处理已知安全的节点类型,未知类型直接
tooComplex() - PARSE_ABORTED 符号:区分"解析器未加载"和"解析器加载但失败"。后者阻止回退旧版(旧版缺少
EVAL_LIKE_BUILTINS检查) - 变量作用域跟踪:
VAR=value && cmd $VAR模式。静态值解析为真实字符串,$()输出使用VAR_PLACEHOLDER - PS4/IFS Allowlist:PS4 赋值使用严格字符白名单
[A-Za-z0-9 _+:.\/=\[\]-],只允许${VAR}引用 - 包装器剥离:从 argv 前面剥离
time/nohup/timeout/nice/env/stdbuf,未知标志 → fail-closed - Shadow 安全性:Shadow 模式总是强制
astResult = { kind: 'parse-unavailable' },绝不影响权限
# 激活 AST 解析用于权限检查
FEATURE_TREE_SITTER_BASH=1 bun run dev
# Shadow 模式(仅遥测,不影响行为)
FEATURE_TREE_SITTER_BASH_SHADOW=1 bun run dev| 文件 | 行数 | 职责 |
|---|---|---|
src/utils/bash/parser.ts |
~110 | 门控入口点 |
src/utils/bash/bashParser.ts |
4437 | 纯 TS bash 解析器 |
src/utils/bash/ast.ts |
2680 | 安全分析器(核心) |
src/utils/bash/treeSitterAnalysis.ts |
507 | AST 分析辅助 |
src/tools/BashTool/bashPermissions.ts:1670-1810 |
~140 | 权限集成 + Shadow 遥测 |