Deep Dive | src/utils/bash/ast.ts 2679 行 — Bash AST 安全遍历器¶
重要性:⭐⭐⭐(bash 安全的第二道关——AST 遍历 + 危险命令检测) 真实位置:
src/utils/bash/ast.ts(2679 行) 角色:在 bashParser 产出的 AST 上做递归遍历,识别危险命令、可疑模式、需要 ask 的操作 关联:topics/deep-dive-bash-parser.md(AST 来源)、topics/deep-dive-bash-security.md(规则层)、topics/deep-dive-bash-permissions.md(权限引擎)
1. 文件全景¶
ast.ts (2679 行)
│
├── 行 1-100 :imports + 类型定义
│ ├── Redirect type (行 25)
│ ├── SimpleCommand type (行 31)
│ └── ParseForSecurityResult type (行 42)
│
├── 行 50-220 :类型 + 集合常量
│ ├── STRUCTURAL_TYPES (node 类型集合)
│ ├── SEPARATOR_TYPES (操作符)
│ ├── CMDSUB_PLACEHOLDER / VAR_PLACEHOLDER
│ ├── BARE_VAR_UNSAFE_RE (正则)
│ ├── STDBUF_SHORT_SEP_RE / STDBUF_LONG_RE (stdbuf 检测)
│ ├── SAFE_ENV_VARS (白名单)
│ ├── SPECIAL_VAR_NAMES
│ ├── DANGEROUS_TYPES + DANGEROUS_TYPE_IDS (危险 node 类型)
│ └── nodeTypeId (行 213)
│
├── 行 220-330 :更多正则
│ ├── BRACE_EXPANSION_RE
│ ├── CONTROL_CHAR_RE
│ ├── BACKSLASH_WHITESPACE_RE
│ ├── ZSH_TILDE_BRACKET_RE
│ ├── ZSH_EQUALS_EXPANSION_RE
│ └── BRACE_WITH_QUOTE_RE
│
├── 行 331-380 :maskBracesInQuotedContexts (行 331) — 转义花括号
│
├── 行 381-460 :parseForSecurity (export async, 行 381) + parseForSecurityFromAst (export, 行 400)
│
├── 行 462-960 :**collectCommands (行 482, ~480 行)** — 主遍历
│
├── 行 962-1017:walkTestExpr (行 962)
│
├── 行 1017-1143:walkRedirectedStatement (行 1017) + walkFileRedirect (行 1071)
│
├── 行 1143-1237:walkHeredocRedirect + walkHerestringRedirect
│
├── 行 1237-1374:walkCommand (行 1237, ~140 行)
│
├── 行 1374-1399:collectCommandSubstitution (行 1374)
│
├── 行 1399-1508:walkArgument (行 1399, ~110 行)
│
├── 行 1508-1675:walkString (行 1508, ~165 行)
│
├── 行 1675-1721:walkArithmetic (行 1675)
│
├── 行 1721-1777:extractSafeCatHeredoc (行 1721, ~55 行)
│
├── 行 1777-1937:walkVariableAssignment (行 1777, ~160 行)
│
├── 行 1937-2017:resolveSimpleExpansion (行 1937, ~80 行)
│
├── 行 2017-2033:applyVarToScope + stripRawString
│
├── 行 2033-2060:tooComplex (行 2033, ~25 行)
│
├── 行 2060-2200 :更多危险 builtins 集合
│ ├── ZSH_DANGEROUS_BUILTINS
│ ├── EVAL_LIKE_BUILTINS
│ ├── TEST_ARITH_CMP_OPS
│ ├── BARE_SUBSCRIPT_NAME_BUILTINS
│ └── READ_DATA_FLAGS
│
└── 行 2200-2679:更多 walk* + helper
2. 3 个核心类型¶
export type Redirect = { ... }
export type SimpleCommand = { ... }
export type ParseForSecurityResult = {
isSafe: boolean
reason?: string
// 提取的命令、重定向、变量赋值等
}
3 个 type:
- Redirect —— 重定向结构
- SimpleCommand —— 简单命令
- ParseForSecurityResult —— 安全分析结果(含 isSafe + reason)
3. 8+ 个集合常量(黑/白名单)¶
const STRUCTURAL_TYPES = new Set([...]) // node 类型
const SEPARATOR_TYPES = new Set(['&&', '||', '|', ';', '&', '|&', '\n'])
const SAFE_ENV_VARS = new Set([...]) // 白名单环境变量
const SPECIAL_VAR_NAMES = new Set([...]) // $?, $$, $@ 等
const DANGEROUS_TYPES = new Set([...]) // 危险 node 类型
const ZSH_DANGEROUS_BUILTINS = new Set([...])
const EVAL_LIKE_BUILTINS = new Set(['eval', 'source', ...])
const TEST_ARITH_CMP_OPS = new Set(['-eq', '-ne', '-lt', ...])
黑/白名单模式:
- SAFE_ENV_VARS —— 不需要权限的环境变量
- DANGEROUS_TYPES —— AST 节点类型黑名单
- ZSH_DANGEROUS_BUILTINS / EVAL_LIKE_BUILTINS —— 命令黑名单
4. parseForSecurity + parseForSecurityFromAst — 2 个公开 API¶
export async function parseForSecurity(cmd: string): Promise<ParseForSecurityResult> {
// 行 381
// 1. parseWithTimeout (bashParser)
// 2. parseForSecurityFromAst
}
export function parseForSecurityFromAst(root: Node): ParseForSecurityResult {
// 行 400
// 1. walkProgram
// 2. 评估 isSafe
}
2 个 API:
- parseForSecurity —— 字符串 → 安全结果
- parseForSecurityFromAst —— AST → 安全结果
5. walkProgram + collectCommands — 主遍历(行 462-960)¶
function walkProgram(root: Node): ParseForSecurityResult {
// 行 462
// 顶层
}
function collectCommands(node: Node, state: WalkState): ParseForSecurityResult {
// 行 482
// ~480 行 — 收集所有 commands
}
核心:
- walkProgram 入口
- collectCommands 递归遍历
- 返回每个 command 的"安全标签"
480 行反映遍历复杂度。
6. 多个 walk* 细分¶
| 函数 | 处理 | 行 |
|---|---|---|
walkCommand |
单条命令 | 1237 |
walkArgument |
命令参数 | 1399 |
walkString |
字符串节点 | 1508 |
walkRedirectedStatement |
带重定向语句 | 1017 |
walkFileRedirect |
> file |
1071 |
walkHeredocRedirect |
<< EOF |
1143 |
walkHerestringRedirect |
<<< word |
1211 |
walkArithmetic |
((expr)) |
1675 |
walkTestExpr |
[[ expr ]] |
962 |
walkVariableAssignment |
FOO=bar |
1777 |
10+ 个 walker —— 每种 node 类型一个 walker。
7. 关键设计模式¶
7.1 AST 遍历 + 黑/白名单¶
两层防御:
1. AST node 类型黑名单 (DANGEROUS_TYPES)
2. 命令名黑名单 (ZSH_DANGEROUS_BUILTINS, EVAL_LIKE_BUILTINS)
7.2 占位符¶
占位符 —— 命令替换 / 变量替换用占位符替代,不执行实际内容。
7.3 递归 + state¶
state 模式 —— 跨递归共享上下文(var scope、redirected 状态等)。
7.4 tooComplex — 复杂时返回危险¶
保守原则 —— 太复杂不分析,直接拒绝。
7.5 resolveSimpleExpansion + applyVarToScope¶
变量解析 —— $VAR 解析为实际值,追踪变量赋值(FOO=bar 后 $FOO = bar)。
7.6 stripRawString + maskBracesInQuotedContexts¶
字符串处理 —— 移除 raw string quotes、mask 花括号(在引号内)。
细节 —— bash 引号规则复杂。
7.7 extractSafeCatHeredoc¶
function extractSafeCatHeredoc(subNode: Node): string | 'DANGEROUS' | null {
// 行 1721
// 提取安全的 cat << EOF 内容
}
heredoc 安全 —— 只允许纯文本 heredoc(不展开变量/命令)。
8. walkArgument — 110 行(行 1399)¶
function walkArgument(node: Node): ParseForSecurityResult {
// 遍历命令参数
// - 检测 $VAR (变量)
// - 检测 $(cmd) (命令替换)
// - 检测 backtick
// - 检测 glob
// - 检测 redirect-like 字符
}
110 行 —— 参数是"危险信号"最集中的地方。
9. walkString — 165 行(行 1508)¶
165 行 —— 引号规则复杂。
10. walkVariableAssignment — 160 行(行 1777)¶
function walkVariableAssignment(node: Node): ParseForSecurityResult {
// FOO=bar
// 追踪到 var scope
// applyVarToScope
}
160 行 —— 变量赋值是"上下文"。
11. resolveSimpleExpansion — 80 行(行 1937)¶
80 行 —— 简单变量展开。
12. applyVarToScope¶
var scope —— 整个 walk 期间的变量映射。
13. 关键设计模式¶
13.1 10+ 个 walker¶
每种 node 类型对应一个 walker —— visitor pattern。
13.2 黑/白名单双层¶
DANGEROUS_TYPES(node 类型)ZSH_DANGEROUS_BUILTINS(命令)
13.3 占位符替代动态内容¶
不执行命令替换 / 变量展开 —— 用占位符。
13.4 保守原则¶
tooComplex —— 太复杂直接拒绝。
13.5 state 跨递归¶
WalkState —— 共享 var scope、redirected 等上下文。
13.6 安全 heredoc¶
extractSafeCatHeredoc —— 只允许纯文本 heredoc。
14. 复杂度分析¶
| 维度 | 数字 |
|---|---|
| 总行数 | 2679 |
| walker 函数 | 10+ |
| 黑/白名单 | 8+ |
| 公开 API | 2 |
15. 性能特征¶
15.1 遍历速度¶
- 普通命令:< 1ms
- 大脚本(1K 行):~5-20ms
- 复杂递归:~50ms
15.2 内存¶
每个 WalkState 约 1-10KB。
15.3 优化¶
tooComplex提前返回- 占位符避免实际求值
- state 共享
16. 与其他文件的关系¶
ast.ts
├──→ bashParser.ts (parseForSecurity 调用)
├──→ bashSecurity.ts (用 ParseForSecurityResult 做规则)
├──→ bashPermissions.ts (用 isSafe 决定权限)
└──→ /utils/ParsedCommand.ts (包装结果)
ast.ts 是"AST walker"——把 AST 变成可分析的安全信息。
17. 关键洞察¶
17.1 AST 遍历是 bash 安全的核心¶
不遍历 AST 就无法检测 eval $USER_INPUT 这类危险模式。
17.2 占位符模式 = "不执行但保留结构"¶
不实际运行 $(cmd) —— 用占位符 __CMDSUB_OUTPUT__ 替代。
17.3 保守原则(tooComplex)¶
不完美主义 —— 太复杂直接拒绝,不冒险。
17.4 黑/白名单双层¶
- node 类型层
- 命令名层
多层防御。
17.5 var scope 追踪¶
applyVarToScope —— 在 walk 期间追踪变量赋值。
这是 bash 解释器的简化版。
17.6 heredoc 安全分类¶
extractSafeCatHeredoc —— 区分"纯文本 heredoc"和"展开 heredoc"。
安全语义细分。
18. 阅读建议¶
- 看 3 个 type(行 25-50)—— 数据模型
- 看黑/白名单(行 50-220)—— 规则基础
- 看 collectCommands(行 482)—— 主遍历
- 看 walkCommand(行 1237)—— 单命令
- 看 walkArgument(行 1399)—— 参数
- 看 tooComplex(行 2033)—— 保守原则
19. 与其他深度拆解的关系¶
| 文件 | 关系 |
|---|---|
bashParser.ts |
产 AST |
bashSecurity.ts |
规则层 |
bashPermissions.ts |
权限决策 |
ParsedCommand.ts |
包装结果 |
20. 阅读清单¶
- ✅ 看 3 个 type(行 25-50)
- ✅ 看黑/白名单(行 50-220)
- ✅ 看 collectCommands(行 482)
- ✅ 看 walkCommand(行 1237)
- ✅ 看 walkArgument(行 1399)
- ✅ 看 tooComplex(行 2033)
- 📌 对照 topics/deep-dive-bash-parser.md
- 📌 对照 topics/deep-dive-bash-security.md
21. 练习任务¶
- 数 walker 函数(grep)—— 10+
- 数黑名单(grep
Set\(\[) —— 8+ - 画 walker 调用图 —— collectCommands → walkCommand → walkArgument → ...
- 手写 walker(~50 行)—— 处理
commandnode - 思考:如果加入"机器学习检测"会怎样?能取代黑/白名单吗?