Files
cs-note/claude-code-best/docs/features/tree-sitter-bash.md
T

164 lines
6.6 KiB
Markdown
Raw Normal View History

2026-06-09 23:15:17 +08:00
---
tags: [tree-sitter, bash, AST, 安全, 权限, feature-flag]
create time: 2026-06-09 22:30
---
2026-06-08 23:08:57 +08:00
# TREE_SITTER_BASH — Bash AST 解析
2026-06-09 23:15:17 +08:00
## 概述
TREE_SITTER_BASH 启用一个完整的 Bash AST 解析器,用于安全验证 Bash 命令。它用完整的树遍历安全分析器取代了旧的基于正则表达式的 shell-quote 解析器。关键属性是 **fail-closed**:任何无法识别的内容都被归类为 `too-complex` 并需要用户批准。
> [!info]
2026-06-08 23:08:57 +08:00
> Feature Flag: `FEATURE_TREE_SITTER_BASH=1`
> 实现状态:完整可用(纯 TypeScript 实现,~7000+ 行)
2026-06-09 23:15:17 +08:00
## 正文
2026-06-08 23:08:57 +08:00
### 关联 Feature
| Feature | 说明 |
|---------|------|
| `TREE_SITTER_BASH` | 激活用于权限检查的 AST 解析器 |
| `TREE_SITTER_BASH_SHADOW` | Shadow/观测模式:运行解析器但丢弃结果,仅记录遥测 |
2026-06-09 23:15:17 +08:00
### 安全架构
2026-06-08 23:08:57 +08:00
2026-06-09 23:15:17 +08:00
#### Fail-Closed 设计
2026-06-08 23:08:57 +08:00
核心设计使用 **allowlist** 遍历模式:
- `walkArgument()` 只处理已知安全的节点类型(`word`、`number`、`raw_string`、`string`、`concatenation`、`arithmetic_expansion`、`simple_expansion`)
2026-06-09 23:15:17 +08:00
- 任何未知节点类型 -> `tooComplex()` -> 需要用户批准
- 解析器加载但失败(超时/节点预算/panic)-> 返回 `PARSE_ABORTED` 符号(区别于"模块未加载")
2026-06-08 23:08:57 +08:00
2026-06-09 23:15:17 +08:00
#### 解析结果
2026-06-08 23:08:57 +08:00
2026-06-09 23:15:17 +08:00
```typescript
2026-06-08 23:08:57 +08:00
parseForSecurity(cmd) 返回:
{ kind: 'simple', commands: SimpleCommand[] } // 可静态分析
{ kind: 'too-complex', reason, nodeType } // 需要用户批准
{ kind: 'parse-unavailable' } // 解析器未加载
```
2026-06-09 23:15:17 +08:00
#### 安全检查层次
```mermaid
flowchart TD
A["parseForSecurity(cmd)"] --> B["parseCommandRaw(cmd) -> AST root node"]
B --> C["预检查: 控制字符、Unicode 空白、反斜杠+空白、zsh 语法"]
C --> D["walkProgram(root) -> collectCommands(root, commands, varScope)"]
D --> E{"节点类型"}
E -->|"command"| F["walkCommand()"]
E -->|"pipeline/list"| G["结构性,递归子节点"]
E -->|"for_statement"| H["跟踪循环变量为 VAR_PLACEHOLDER"]
E -->|"if/while"| I["作用域隔离的分支"]
E -->|"subshell"| J["作用域复制"]
E -->|"variable_assignment"| K["walkVariableAssignment()"]
E -->|"declaration_command"| L["验证 declare/export flags"]
E -->|"test_command"| M["walk test expressions"]
E -->|"其他"| N["tooComplex()"]
D --> O["checkSemantics(commands)"]
O --> P["EVAL_LIKE_BUILTINS eval/source/exec/trap..."]
O --> Q["ZSH_DANGEROUS_BUILTINS zmodload/emulate..."]
O --> R["SUBSCRIPT_EVAL_FLAGS test -v/printf -v/read -a"]
O --> S["Shell keywords as argv0 误解析检测"]
O --> T["/proc/*/environ 访问"]
O --> U["jq system() 和危险 flags"]
O --> V["包装器剥离 time/nohup/timeout/nice/env/stdbuf"]
2026-06-08 23:08:57 +08:00
```
2026-06-09 23:15:17 +08:00
### 实现架构
2026-06-08 23:08:57 +08:00
2026-06-09 23:15:17 +08:00
#### 核心模块
2026-06-08 23:08:57 +08:00
| 模块 | 文件 | 行数 | 职责 |
|------|------|------|------|
| 门控入口 | `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 结果到权限决策 |
2026-06-09 23:15:17 +08:00
#### Bash 解析器
2026-06-08 23:08:57 +08:00
文件:`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
2026-06-09 23:15:17 +08:00
#### 安全分析器
2026-06-08 23:08:57 +08:00
文件:`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()` | 递归收集所有命令 |
2026-06-09 23:15:17 +08:00
#### AST 分析辅助
2026-06-08 23:08:57 +08:00
文件:`src/utils/bash/treeSitterAnalysis.ts`(507 行)
| 函数 | 职责 |
|------|------|
| `extractQuoteContext()` | 识别单引号、双引号、ANSI-C 字符串、heredoc |
| `extractCompoundStructure()` | 检测管道、子 shell、命令组 |
| `hasActualOperatorNodes()` | 区分真实 `;`/`&&`/`||` 与转义形式 |
| `extractDangerousPatterns()` | 检测命令替换、参数展开、heredocs |
| `analyzeCommand()` | 单次遍历提取 |
2026-06-09 23:15:17 +08:00
#### Shadow 模式
2026-06-08 23:08:57 +08:00
`TREE_SITTER_BASH_SHADOW` 运行解析器但**从不影响权限决策**:
2026-06-09 23:15:17 +08:00
```typescript
2026-06-08 23:08:57 +08:00
// Shadow 模式:记录遥测,然后强制使用旧版路径
astResult = { kind: 'parse-unavailable' }
astRoot = null
// 记录: available, astTooComplex, astSemanticFail, subsDiffer, ...
```
记录 `tengu_tree_sitter_shadow` 事件,包含与旧版 `splitCommand()` 的对比数据。用于在不影响行为的情况下收集遥测。
2026-06-09 23:15:17 +08:00
### 关键设计决策
2026-06-08 23:08:57 +08:00
1. **Allowlist 遍历**:只处理已知安全的节点类型,未知类型直接 `tooComplex()`
2. **PARSE_ABORTED 符号**:区分"解析器未加载"和"解析器加载但失败"。后者阻止回退旧版(旧版缺少 `EVAL_LIKE_BUILTINS` 检查)
3. **变量作用域跟踪**:`VAR=value && cmd $VAR` 模式。静态值解析为真实字符串,`$()` 输出使用 `VAR_PLACEHOLDER`
4. **PS4/IFS Allowlist**:PS4 赋值使用严格字符白名单 `[A-Za-z0-9 _+:.\/=\[\]-]`,只允许 `${VAR}` 引用
2026-06-09 23:15:17 +08:00
5. **包装器剥离**:从 argv 前面剥离 `time/nohup/timeout/nice/env/stdbuf`,未知标志 -> fail-closed
2026-06-08 23:08:57 +08:00
6. **Shadow 安全性**:Shadow 模式**总是**强制 `astResult = { kind: 'parse-unavailable' }`,绝不影响权限
2026-06-09 23:15:17 +08:00
### 使用方式
2026-06-08 23:08:57 +08:00
```bash
# 激活 AST 解析用于权限检查
FEATURE_TREE_SITTER_BASH=1 bun run dev
# Shadow 模式(仅遥测,不影响行为)
FEATURE_TREE_SITTER_BASH_SHADOW=1 bun run dev
```
2026-06-09 23:15:17 +08:00
### 文件索引
2026-06-08 23:08:57 +08:00
| 文件 | 行数 | 职责 |
|------|------|------|
| `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 分析辅助 |
| `packages/builtin-tools/src/tools/BashTool/bashPermissions.ts` | ~140 | 权限集成 + Shadow 遥测 |
2026-06-09 23:15:17 +08:00
## 关联笔记
- [[claude-code-best/docs/features/bash-classifier]]