跳转至

Deep Dive | src/utils/bash/ast.ts 2679 行 — Bash AST 安全遍历器

重要性:⭐⭐⭐(bash 安全的第二道关——AST 遍历 + 危险命令检测) 真实位置src/utils/bash/ast.ts2679 行角色:在 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 占位符

const CMDSUB_PLACEHOLDER = '__CMDSUB_OUTPUT__'
const VAR_PLACEHOLDER = '__TRACKED_VAR__'

占位符 —— 命令替换 / 变量替换用占位符替代,不执行实际内容

7.3 递归 + state

function collectCommands(node, state) { ... }

state 模式 —— 跨递归共享上下文(var scope、redirected 状态等)。

7.4 tooComplex — 复杂时返回危险

function tooComplex(node: Node): ParseForSecurityResult {
  // 行 2033
  // 节点数超阈值 → 标记不安全(保守)
}

保守原则 —— 太复杂不分析,直接拒绝。

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)

function walkString(node: Node): ParseForSecurityResult {
  // 字符串节点
  // 双引号 vs 单引号 vs 裸字符串
}

165 行 —— 引号规则复杂。


10. walkVariableAssignment — 160 行(行 1777)

function walkVariableAssignment(node: Node): ParseForSecurityResult {
  // FOO=bar
  // 追踪到 var scope
  // applyVarToScope
}

160 行 —— 变量赋值是"上下文"。


11. resolveSimpleExpansion — 80 行(行 1937)

function resolveSimpleExpansion(node: Node, state: WalkState): string | null {
  // $VAR 解析为值
}

80 行 —— 简单变量展开。


12. applyVarToScope

function applyVarToScope(state: WalkState, name: string, value: string): void {
  // 写入 var scope
}

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. 阅读建议

  1. 看 3 个 type(行 25-50)—— 数据模型
  2. 看黑/白名单(行 50-220)—— 规则基础
  3. 看 collectCommands(行 482)—— 主遍历
  4. 看 walkCommand(行 1237)—— 单命令
  5. 看 walkArgument(行 1399)—— 参数
  6. 看 tooComplex(行 2033)—— 保守原则

19. 与其他深度拆解的关系

文件 关系
bashParser.ts 产 AST
bashSecurity.ts 规则层
bashPermissions.ts 权限决策
ParsedCommand.ts 包装结果

20. 阅读清单

  1. ✅ 看 3 个 type(行 25-50)
  2. ✅ 看黑/白名单(行 50-220)
  3. ✅ 看 collectCommands(行 482)
  4. ✅ 看 walkCommand(行 1237)
  5. ✅ 看 walkArgument(行 1399)
  6. ✅ 看 tooComplex(行 2033)
  7. 📌 对照 topics/deep-dive-bash-parser.md
  8. 📌 对照 topics/deep-dive-bash-security.md

21. 练习任务

  1. 数 walker 函数(grep)—— 10+
  2. 数黑名单(grep Set\(\[) —— 8+
  3. 画 walker 调用图 —— collectCommands → walkCommand → walkArgument → ...
  4. 手写 walker(~50 行)—— 处理 command node
  5. 思考:如果加入"机器学习检测"会怎样?能取代黑/白名单吗?