4.3 BashTool 防御链 — 七层安全防御的工程实现

4.3 BashTool 防御链 --- 七层安全防御的工程实现

对应原书 :第4章 4.7节"BashTool 的七层安全防御"、4.7.1节"命令清洗的固定点迭代"、4.7.2节"Sed 模拟执行"、4.7.3节"AsyncGenerator 驱动的流式执行"、4.10节"与其他框架对比"、4.11节"工具沙箱隔离概述"

辅助源码tools/BashTool/ 目录全部文件(BashTool.tsx、bashSecurity.ts、bashPermissions.ts、pathValidation.ts、sedValidation.ts、readOnlyValidation.ts、shouldUseSandbox.ts、commandSemantics.ts、destructiveCommandWarning.ts、modeValidation.ts)、utils/bash/ast.ts

重点关注:七层防御架构、AST 解析(Tree-sitter)、权限决策引擎、路径约束系统、Sed 约束白名单、命令清洗固定点迭代、Sed 模拟执行、AsyncGenerator 流式执行


1. 导语:为什么 BashTool 是"重中之重"

原书第4章将 BashTool 的安全防御称为整个工具系统的"重中之重"。这并非夸张------BashTool 是唯一一个允许 LLM 执行任意 shell 命令的工具,一旦被 Prompt 注入绕过,攻击者就能获得完整的 shell 访问权限。与 FileEditTool(只能写文件)或 GlobTool(只能搜索文件)不同,BashTool 的攻击面是整个操作系统。

源码验证了这一判断:tools/BashTool/ 目录包含 10+ 个专用安全文件 ,总代码量超过 6000 行,远超任何其他工具。这些文件构成了一个从命令解析到执行监控的完整防御链。

复制代码
tools/BashTool/
├── BashTool.tsx                    # 主组件(53KB)--- 工具定义 + AsyncGenerator 执行
├── bashSecurity.ts                 # 危险模式检测(20K+ tokens)--- 23 种安全检查
├── bashPermissions.ts              # 权限决策引擎(83.5KB)--- deny/ask/allow 规则匹配
├── pathValidation.ts               # 路径约束(1304行)--- 35 种 PathCommand 路径验证
├── sedValidation.ts                # Sed 约束(685行)--- 双模式白名单
├── readOnlyValidation.ts           # 只读验证(76.3KB)--- 多组只读命令白名单
├── shouldUseSandbox.ts             # 沙箱决策(154行)--- 排除命令检测
├── commandSemantics.ts             # 命令语义(141行)--- exit code 解释器
├── destructiveCommandWarning.ts    # 破坏性警告(103行)--- 15 种危险模式
├── modeValidation.ts               # 模式验证(116行)--- acceptEdits 自动放行
├── bashCommandHelpers.ts           # 命令辅助 --- 操作符权限检查
├── sedEditParser.ts                # Sed 解析 --- 表达式到 JS 替换转换
└── utils.ts / toolName.ts / ...    # 辅助工具

2. 七层防御架构总览

原书 4.7 节描述了七层防御的流水线结构。源码验证了这一架构,但实际实现远比原书描述的更精细:

复制代码
命令字符串
    │
    ▼
┌─────────────────────────────────────────────────────────────────┐
│ Layer 1: AST 解析 (Tree-sitter)                                  │
│ ast.ts: parseForSecurityFromAst() → SimpleCommand[]              │
│ 预检查:控制字符 / Unicode空白 / 反斜杠空白 / Zsh扩展 / 花括号引号  │
│ 语义检查:checkSemantics() --- eval/exec/source/. 等危险命令名      │
├─────────────────────────────────────────────────────────────────┤
│ Layer 2: 精确匹配规则 (Exact Match)                              │
│ bashPermissions.ts: bashToolCheckExactMatchPermission()          │
│ 用户配置的 deny / ask / allow 精确匹配                            │
├─────────────────────────────────────────────────────────────────┤
│ Layer 3: 前缀/通配符规则 (Prefix/Wildcard)                       │
│ bashPermissions.ts: bashPermissionRule() + matchWildcardPattern()│
│ deny 规则用 stripAllLeadingEnvVars(激进剥离)                    │
│ allow 规则用 stripSafeWrappers(保守剥离)                        │
├─────────────────────────────────────────────────────────────────┤
│ Layer 4: 路径约束 (Path Constraints)                             │
│ pathValidation.ts: checkPathConstraints()                        │
│ 35 种 PathCommand 路径提取 + -- 端标志 + 危险路径检测              │
│ 重定向目标验证 + cd+write 组合检测                                 │
├─────────────────────────────────────────────────────────────────┤
│ Layer 5: Sed 约束 (Sed Constraints)                              │
│ sedValidation.ts: checkSedConstraints()                          │
│ Pattern 1: 行打印白名单 (-n + p/Np/N,Mp)                         │
│ Pattern 2: 替换命令白名单 (s/pattern/replacement/flags)          │
│ denylist: w/W/e/E 命令 / 非ASCII / 花括号 / 换行 / 注释           │
├─────────────────────────────────────────────────────────────────┤
│ Layer 6: 只读自动放行 (Read-Only Auto-Allow)                     │
│ readOnlyValidation.ts: validateReadOnlyCommand()                 │
│ GIT/GH/RIPGREP/PYRIGHT/DOCKER/EXTERNAL 多组白名单                 │
│ validateFlags() flag 白名单 + UNC 路径漏洞检测                     │
├─────────────────────────────────────────────────────────────────┤
│ Layer 7: 分类器审批 (Classifier Approval)                        │
│ bashPermissions.ts: classifyBashCommand() + bashClassifier       │
│ LLM 分类器 + 用户审批 UI + pendingClassifierCheck                │
└─────────────────────────────────────────────────────────────────┘
    │
    ▼
  执行 / 拒绝 / 询问用户

2.1 防御层与源码文件映射表

防御层 原书描述 核心源码文件 关键函数
Layer 1 命令解析与 AST 分析 utils/bash/ast.ts parseForSecurityFromAst() / checkSemantics()
Layer 2 精确匹配权限规则 bashPermissions.ts bashToolCheckExactMatchPermission()
Layer 3 前缀/通配符权限规则 bashPermissions.ts bashPermissionRule() / matchWildcardPattern()
Layer 4 路径检查 pathValidation.ts checkPathConstraints() / validateSinglePathCommandArgv()
Layer 5 Sed 命令约束 sedValidation.ts checkSedConstraints() / sedCommandIsAllowedByAllowlist()
Layer 6 只读命令自动放行 readOnlyValidation.ts validateReadOnlyCommand() / validateFlags()
Layer 7 分类器与用户审批 bashPermissions.ts + bashClassifier.ts classifyBashCommand() / isClassifierPermissionsEnabled()

3. Layer 1: AST 解析 --- Tree-sitter 的精确命令分析

3.1 从 shell-quote 到 Tree-sitter 的迁移

原书提到 Claude Code 使用 AST 解析来理解命令结构。源码揭示了一个重要的架构演进:从旧的 shell-quote 解析器迁移到 Tree-sitter bash 语法分析器。

bashPermissions.tsbashToolHasPermission() 函数(第 1663 行)是整个权限决策的入口:

typescript 复制代码
export async function bashToolHasPermission(
  input: z.infer<typeof BashTool.inputSchema>,
  context: ToolUseContext,
  getCommandSubcommandPrefixFn = getCommandSubcommandPrefix,
): Promise<PermissionResult> {
  // 0. AST-based security parse
  const injectionCheckDisabled = isEnvTruthy(
    process.env.CLAUDE_CODE_DISABLE_COMMAND_INJECTION_CHECK,
  )
  const shadowEnabled = feature('TREE_SITTER_BASH_SHADOW')
    ? getFeatureValue_CACHED_MAY_BE_STALE('tengu_birch_trellis', true)
    : false

  let astRoot = injectionCheckDisabled
    ? null
    : feature('TREE_SITTER_BASH_SHADOW') && !shadowEnabled
      ? null
      : await parseCommandRaw(input.command)

  let astResult: ParseForSecurityResult = astRoot
    ? parseForSecurityFromAst(input.command, astRoot)
    : { kind: 'parse-unavailable' }

这里有三层 Feature Flag 控制:

  • CLAUDE_CODE_DISABLE_COMMAND_INJECTION_CHECK:环境变量紧急开关
  • TREE_SITTER_BASH_SHADOW:编译期 Feature Flag,控制 Tree-sitter 代码是否被包含
  • tengu_birch_trellis:GrowthBook 运行时开关,支持灰度发布

3.2 parseForSecurityFromAst:六道预检查

ast.ts 第 400 行的 parseForSecurityFromAst() 在 Tree-sitter 解析前执行六道预检查,拦截 Tree-sitter 与 bash 在词法边界上的已知差异:

typescript 复制代码
export function parseForSecurityFromAst(
  cmd: string,
  root: Node | typeof PARSE_ABORTED,
): ParseForSecurityResult {
  // 预检查 1: 控制字符
  if (CONTROL_CHAR_RE.test(cmd)) {
    return { kind: 'too-complex', reason: 'Contains control characters' }
  }
  // 预检查 2: Unicode 空白字符(如 \u00A0,可隐藏内容)
  if (UNICODE_WHITESPACE_RE.test(cmd)) {
    return { kind: 'too-complex', reason: 'Contains Unicode whitespace' }
  }
  // 预检查 3: 反斜杠转义的空白字符
  if (BACKSLASH_WHITESPACE_RE.test(cmd)) {
    return { kind: 'too-complex', reason: 'Contains backslash-escaped whitespace' }
  }
  // 预检查 4: Zsh ~[ 动态目录语法
  if (ZSH_TILDE_BRACKET_RE.test(cmd)) {
    return { kind: 'too-complex', reason: 'Contains zsh ~[ dynamic directory syntax' }
  }
  // 预检查 5: Zsh =cmd 等号扩展
  if (ZSH_EQUALS_EXPANSION_RE.test(cmd)) {
    return { kind: 'too-complex', reason: 'Contains zsh =cmd equals expansion' }
  }
  // 预检查 6: 花括号+引号混淆
  if (BRACE_WITH_QUOTE_RE.test(maskBracesInQuotedContexts(cmd))) {
    return { kind: 'too-complex', reason: 'Contains brace with quote character' }
  }
  // ... 然后 walkProgram(root) 遍历 AST

这些预检查的设计哲学是Fail-Closed :只要发现 Tree-sitter 可能与 bash 解析不一致的输入,直接返回 too-complex,降级为询问用户。

3.3 PARSE_ABORTED 对抗性输入处理

一个关键的安全细节------当 Tree-sitter 解析超时或资源耗尽时(可被对抗性输入触发),不降级为旧的 shell-quote 路径,而是直接 fail-closed:

typescript 复制代码
if (root === PARSE_ABORTED) {
  // SECURITY: module loaded but parse aborted (timeout / node budget /
  // panic). Adversarially triggerable --- `(( a[0][0]... ))` with ~2800
  // subscripts hits PARSE_TIMEOUT_MICROS under the 10K length limit.
  // Previously indistinguishable from module-not-loaded → routed to
  // legacy (parse-unavailable), which lacks EVAL_LIKE_BUILTINS --- `trap`,
  // `enable`, `hash` leaked with Bash(*). Fail closed: too-complex → ask.
  return {
    kind: 'too-complex',
    reason: 'Parser aborted (timeout or resource limit) --- possible adversarial input',
    nodeType: 'PARSE_ABORT',
  }
}

注释明确指出了一个真实漏洞:(( a[0][0]... )) 嵌套约 2800 层下标就能触发解析超时,旧代码会降级到 legacy 路径,而 legacy 路径缺少 EVAL_LIKE_BUILTINS 检查,导致 trapenablehash 等危险命令泄漏。

3.4 checkSemantics:命令名级别的语义检查

ast.ts 第 2213 行的 checkSemantics() 在 AST 解析成功后执行命令名级别的语义检查:

typescript 复制代码
export function checkSemantics(commands: SimpleCommand[]): SemanticCheckResult {
  for (const cmd of commands) {
    // 剥离安全包装器(nohup, time, timeout N, nice -n N)
    let a = cmd.argv
    for (;;) {
      if (a[0] === 'time' || a[0] === 'nohup') {
        a = a.slice(1)
      } else if (a[0] === 'timeout') {
        // 解析 GNU timeout 的所有 flag 形式...
        let i = 1
        while (i < a.length) {
          // --foreground, --preserve-status, --verbose (无值)
          // --kill-after=N, --signal=SIG (等号融合值)
          // --kill-after N, --signal SIG (空格分隔值)
          // -v, -k DUR, -s SIG, -k5, -sTERM (短 flag)
          // 未知 flag → fail closed
        }
        // ... 验证 duration 格式
      } else if (a[0] === 'nice') {
        // nice cmd / nice -n N cmd / nice -N cmd
        // nice $((0-5)) → fail closed(算术扩展无法静态分析)
      } else {
        break
      }
    }
    // 检查命令名是否在危险列表中
    // eval, exec, source, ., trap, enable, hash, system() 等
  }
}

语义检查的核心是剥离安全包装器后检查实际命令名 。这意味着 nohup eval "rm -rf /" 不会被 Bash(nohup:*) 的 allow 规则放行------剥离 nohup 后暴露出 eval,语义检查直接拒绝。


4. Layer 2-3: 权限规则匹配引擎

4.1 三种规则类型

bashPermissions.ts 支持三种权限规则类型:

复制代码
| 规则类型 | 语法示例 | 匹配方式 | 源码函数 |
|---------|---------|---------|---------|
| 精确匹配 | `Bash(npm test)` | 命令字符串完全一致 | bashToolCheckExactMatchPermission() |
| 前缀匹配 | `Bash(npm:*)` | 命令以 "npm " 开头 | bashPermissionRule() prefix 分支 |
| 通配符 | `Bash(git commit *)` | glob 模式匹配 | matchWildcardPattern() |

4.2 deny vs allow 的不对称剥离策略

这是整个权限引擎最精妙的设计------deny 规则和 allow 规则使用不同的环境变量剥离策略:

deny 规则用激进剥离stripAllLeadingEnvVars):

typescript 复制代码
/**
 * Strip ALL leading env var prefixes, regardless of whether the var
 * name is in the safe-list.
 *
 * Used for deny/ask rule matching: when a user denies `claude` or `rm`,
 * the command should stay blocked even if prefixed with arbitrary env vars
 * like `FOO=bar claude`. The safe-list restriction in stripSafeWrappers
 * is correct for allow rules (prevents `DOCKER_HOST=evil docker ps` from
 * auto-matching `Bash(docker ps:*)`), but deny rules must be harder to
 * circumvent.
 */
export function stripAllLeadingEnvVars(
  command: string,
  blocklist?: RegExp,  // BINARY_HIJACK_VARS for excludedCommands
): string {

allow 规则用保守剥离stripSafeWrappers):

typescript 复制代码
export function stripSafeWrappers(command: string): string {
  // 只剥离已知安全的环境变量(SAFE_ENV_VARS 白名单)
  // 只剥离已知安全的包装器(timeout/nice/nohup/time/stdbuf)
  // ...
}

这种不对称设计的安全含义:

  • deny 不可绕过FOO=bar rm -rf / 中的 FOO=bar 会被激进剥离,暴露出 rm -rf /,匹配 deny 规则
  • allow 不可扩大DOCKER_HOST=evil docker ps 中的 DOCKER_HOST 不在安全列表中,不会被剥离,因此不匹配 Bash(docker ps:*) allow 规则,仍然需要询问用户

4.3 固定点迭代:命令清洗的核心算法

原书 4.7.1 节描述了"命令清洗的固定点迭代"。源码验证了这一设计------stripSafeWrappers 使用两阶段固定点迭代:

typescript 复制代码
export function stripSafeWrappers(command: string): string {
  let stripped = command
  let previousStripped = ''

  // Phase 1: 剥离环境变量和注释(固定点迭代)
  while (stripped !== previousStripped) {
    previousStripped = stripped
    stripped = stripCommentLines(stripped)
    const envVarMatch = stripped.match(ENV_VAR_PATTERN)
    if (envVarMatch) {
      const varName = envVarMatch[1]!
      if (SAFE_ENV_VARS.has(varName) || isAntOnlySafe) {
        stripped = stripped.replace(ENV_VAR_PATTERN, '')
      }
    }
  }

  // Phase 2: 剥离包装器和注释(固定点迭代,不剥离环境变量)
  // SECURITY: 包装器后的 VAR=val 是命令而非赋值(HackerOne #3543050)
  previousStripped = ''
  while (stripped !== previousStripped) {
    previousStripped = stripped
    stripped = stripCommentLines(stripped)
    for (const pattern of SAFE_WRAPPER_PATTERNS) {
      stripped = stripped.replace(pattern, '')
    }
  }

  return stripped.trim()
}

为什么需要两阶段? 注释中解释了一个 HackerOne 漏洞(#3543050):在 bash 中,VAR=val 出现在包装器之后会被当作命令执行,而非环境变量赋值。例如 nohup FOO=bar cmd 中,FOO=barnohup 的参数,bash 会尝试执行名为 FOO=bar 的命令。因此 Phase 2 不能剥离环境变量。

为什么需要固定点迭代? 因为环境变量和包装器可以交替出现:NODE_ENV=prod timeout 5 nice -n 10 npm test。每次迭代剥离一层,直到不再变化(收敛到固定点)。

4.4 stripWrappersFromArgv:AST 级包装器剥离

除了字符串级的 stripSafeWrappers,还有 AST 级的 stripWrappersFromArgv,用于从 Tree-sitter 解析出的 argv 数组中剥离包装器:

typescript 复制代码
export function stripWrappersFromArgv(argv: string[]): string[] {
  let a = argv
  for (;;) {
    if (a[0] === 'time' || a[0] === 'nohup') {
      a = a.slice(a[1] === '--' ? 2 : 1)
    } else if (a[0] === 'timeout') {
      const i = skipTimeoutFlags(a)  // 独立 flag 解析器
      if (i < 0 || !a[i] || !/^\d+(?:\.\d+)?[smhd]?$/.test(a[i]!)) return a
      a = a.slice(i + 1)
    } else if (a[0] === 'nice' && a[1] === '-n' && a[2] && /^-?\d+$/.test(a[2])) {
      a = a.slice(a[3] === '--' ? 4 : 3)
    } else {
      return a
    }
  }
}

skipTimeoutFlags 是一个独立的 flag 解析器,枚举 GNU timeout 的所有 flag 形式(长/短、融合/分隔),并使用 TIMEOUT_FLAG_VALUE_RE = /^[A-Za-z0-9_.+-]+$/ 白名单验证 flag 值,防止 timeout -k$(id) 10 ls 类注入。

4.5 BARE_SHELL_PREFIXES:阻止危险前缀建议

bashPermissions.ts 第 196 行定义了一组"裸 shell 前缀",阻止用户为这些命令创建前缀 allow 规则:

typescript 复制代码
const BARE_SHELL_PREFIXES = new Set([
  'sh', 'bash', 'zsh', 'fish', 'csh', 'tcsh', 'ksh', 'dash',  // shell 解释器
  'cmd', 'powershell', 'pwsh',                                   // Windows shell
  'env', 'xargs',                                                // 命令包装器
  'nice', 'stdbuf', 'nohup', 'timeout', 'time',                 // 安全包装器
  'sudo', 'doas', 'pkexec',                                      // 特权提升
])

如果允许 Bash(nice:*),那么 nice rm -rf / 会通过权限检查(剥离 nice 后匹配 rm 的 allow 规则)。因此这些命令被阻止出现在前缀建议中。


5. Layer 4: 路径约束系统 --- pathValidation.ts

5.1 35 种 PathCommand 的路径提取器

pathValidation.ts 定义了 35 种路径相关命令的路径提取器,每种命令都有定制的路径提取逻辑:

typescript 复制代码
type PathCommand =
  | 'cd' | 'ls' | 'find' | 'mkdir' | 'touch' | 'rm' | 'rmdir'
  | 'mv' | 'cp' | 'cat' | 'head' | 'tail' | 'sort' | 'uniq'
  | 'wc' | 'cut' | 'paste' | 'column' | 'tr' | 'file' | 'stat'
  | 'diff' | 'awk' | 'strings' | 'hexdump' | 'od' | 'base64'
  | 'nl' | 'grep' | 'rg' | 'sed' | 'git' | 'jq'
  | 'sha256sum' | 'sha1sum' | 'md5sum'

PATH_EXTRACTORS 为每种命令定义了路径提取函数,处理各自的 flag 语义。例如 find 命令的第一个非 flag 参数是搜索路径,而 grep 的最后一个非 flag 参数是目标文件。

5.2 filterOutFlags:POSIX -- 端标志防注入

filterOutFlags() 实现了 POSIX -- 端标志处理,防止 rm -- -/../file 类绕过:

typescript 复制代码
// `--` 之后的参数都是文件路径,不是 flag
// `rm -- -f` 中的 `-f` 是文件名而非 flag
function filterOutFlags(argv: string[]): string[] {
  const result: string[] = []
  let seenDoubleDash = false
  for (const arg of argv) {
    if (seenDoubleDash) {
      result.push(arg)  // -- 之后全部视为路径
    } else if (arg === '--') {
      seenDoubleDash = true
    } else if (!arg.startsWith('-')) {
      result.push(arg)  // 非 flag 参数
    }
    // flag 参数被跳过
  }
  return result
}

5.3 checkDangerousRemovalPaths:危险路径检测

rmrmdir 命令会检查目标路径是否指向关键系统目录:

typescript 复制代码
function checkDangerousRemovalPaths(paths: string[]): PermissionResult | null {
  const DANGEROUS_PATHS = [
    '/', '/bin', '/boot', '/dev', '/etc', '/lib',
    '/proc', '/root', '/sbin', '/sys', '/usr', '/var',
    '/System', '/Library', '/Applications',  // macOS
    'C:\\Windows', 'C:\\Program Files',       // Windows
  ]
  // 检查路径规范化后是否匹配危险目录
}

5.4 compoundCommandHasCd:cd+write 组合检测

compoundCommandHasCd 检测 cd 与写命令的组合,防止路径解析绕过:

typescript 复制代码
// `cd /etc && rm hosts` --- rm 的目标在 /etc 下,但单独的 `rm hosts` 
// 在权限规则中可能匹配 `Bash(rm:*)` allow 规则
// compoundCommandHasCd 检测到 cd+rm 组合后,拒绝自动放行
function compoundCommandHasCd(commands: SimpleCommand[]): boolean {
  // ...
}

5.5 validateOutputRedirections:重定向目标验证

重定向目标也被纳入路径约束:

typescript 复制代码
// `echo evil > /etc/passwd` --- 重定向目标是写操作
// validateOutputRedirections 检查 > >> < 的目标路径
function validateOutputRedirections(redirects: Redirect[]): PermissionResult | null {
  // ...
}

5.6 AST 路径 vs shell-quote 路径

源码中有两套路径验证函数,反映了从 shell-quote 到 AST 的迁移:

复制代码
| 函数 | 数据来源 | 状态 | 说明 |
|------|---------|------|------|
| validateSinglePathCommandArgv() | AST-derived argv | 优先使用 | Tree-sitter 解析的 argv |
| validateSinglePathCommand() | shell-quote re-parse | 降级使用 | shell-quote 有已知单引号反斜杠 bug |

stripWrappersFromArgv 也在注释中强调需要与 stripSafeWrappers 保持同步:

typescript 复制代码
/**
 * Argv-level counterpart to stripSafeWrappers. Strips the same wrapper
 * commands (timeout, time, nice, nohup) from AST-derived argv. Env vars
 * are already separated into SimpleCommand.envVars so no env-var stripping.
 *
 * KEEP IN SYNC with SAFE_WRAPPER_PATTERNS above --- if you add a wrapper
 * there, add it here too.
 */

6. Layer 5: Sed 约束 --- sedValidation.ts

6.1 双模式白名单架构

sedValidation.ts(685 行)实现了两种安全 sed 模式:

Pattern 1: 行打印命令

typescript 复制代码
function isLinePrintingCommand(expr: string): boolean {
  // -n 抑制自动输出 + p/Np/N,Mp 打印特定行
  // 严格白名单:只允许 -n flag + p 命令
  // 示例:sed -n '5p' file, sed -n '10,20p' file
}

Pattern 2: 替换命令

typescript 复制代码
function isSubstitutionCommand(expr: string): boolean {
  // s/pattern/replacement/flags
  // flags 仅允许:g(全局) p(打印) i/I(忽略大小写) m/M(多行)
  // 不允许 e(执行命令) --- 这是最大的安全风险
}

6.2 containsDangerousOperations:denylist 检查

containsDangerousOperations() 是一个 denylist 检查器,拒绝以下危险操作:

复制代码
| 危险操作 | 示例 | 风险 |
|---------|------|------|
| w/W 命令 | `sed 'w /etc/passwd'` | 写入任意文件 |
| e/E 命令 | `sed 'e rm -rf /'` | 执行 shell 命令 |
| 非 ASCII 字符 | `sed 's/\x{200B}//'` | Unicode 混淆攻击 |
| 花括号 | `sed 's/{a,b}/x/'` | 花括号扩展 |
| 换行符 | `sed 'a\nmalicious'` | 命令注入 |
| 注释 | `sed '# comment\ne evil'` | 注释隐藏命令 |
| 否定符 | `sed '!d'` | 否定语义混淆 |
| 反斜杠技巧 | `sed 's/\\\n//g'` | 行继续符混淆 |
| y 命令 | `sed 'y/abc/xyz/'` | 字符转换(无安全风险但限制) |

6.3 sedCommandIsAllowedByAllowlist:双模式入口

typescript 复制代码
function sedCommandIsAllowedByAllowlist(
  argv: string[],
  allowFileWrites: boolean,  // acceptEdits 模式允许 -i
): boolean {
  // allowFileWrites=false: 只允许 Pattern 1 和 Pattern 2
  // allowFileWrites=true: 额外允许 -i (in-place 编辑)
}

6.4 Sed 模拟执行(原书 4.7.2 节)

原书 4.7.2 节描述了 Sed 模拟执行。源码验证了这一设计------当 sed 命令包含 -i(in-place 编辑)时,BashTool 不直接执行 sed,而是:

  1. sedEditParser.ts 解析 sed 表达式
  2. sedReplacementToJsReplacement() 将 sed 替换模式转换为 JavaScript 正则替换
  3. 读取目标文件内容
  4. 在 JavaScript 中执行替换
  5. writeTextContent() 写回文件

这种"模拟执行"确保了"所见即所得"------JavaScript 正则替换的行为完全确定,不会受到 sed 实现差异或 sed 高级特性(如保持空间、分支)的影响。


7. Layer 6: 只读命令自动放行 --- readOnlyValidation.ts

7.1 多组只读命令白名单

readOnlyValidation.ts(76.3KB)维护了多组只读命令白名单:

复制代码
| 白名单组 | 命令示例 | 来源 |
|---------|---------|------|
| GIT_READ_ONLY_COMMANDS | git status, git log, git diff, git show | Git 只读子命令 |
| GH_READ_ONLY_COMMANDS | gh pr list, gh issue view | GitHub CLI 只读子命令 |
| RIPGREP_READ_ONLY_COMMANDS | rg --json, rg --count | ripgrep 只读 flag |
| PYRIGHT_READ_ONLY_COMMANDS | pyright --outputjson | Pyright 只读模式 |
| DOCKER_READ_ONLY_COMMANDS | docker ps, docker images, docker logs | Docker 只读子命令 |
| EXTERNAL_READONLY_COMMANDS | (用户自定义) | 外部配置 |

7.2 validateFlags:flag 白名单验证

每组只读命令不仅有命令名白名单,还有 flag 白名单:

typescript 复制代码
function validateFlags(
  flags: string[],
  allowedFlags: Set<string>,
): boolean {
  // 只允许白名单中的 flag
  // 防止 `git log --output=/etc/passwd` 类注入
}

7.3 containsVulnerableUncPath:UNC 路径漏洞检测

typescript 复制代码
function containsVulnerableUncPath(args: string[]): boolean {
  // 检测 Windows UNC 路径(\\server\share)
  // 某些命令对 UNC 路径处理存在安全漏洞
}

8. Layer 7: 分类器审批与沙箱决策

8.1 分类器集成

当命令通过前六层但未被任何 allow 规则匹配时,进入分类器审批流程:

typescript 复制代码
// bashPermissions.ts
if (feature('BASH_CLASSIFIER') && isClassifierPermissionsEnabled()) {
  const classifierResult = classifyBashCommand(input.command)
  // 分类器返回 allow/deny/ask + 置信度 + 匹配描述
  // ...
  pendingClassifierCheck: buildPendingClassifierCheck(
    input.command,
    appState.toolPermissionContext,
  )
}

分类器(bashClassifier.ts)使用 LLM 对命令进行分类,返回 ClassifierResult

typescript 复制代码
type ClassifierResult = {
  matches: boolean           // 是否匹配某个分类器规则
  matchedDescription: string | null  // 匹配的描述
  confidence: number         // 置信度
  reason: string             // 决策原因
}

8.2 shouldUseSandbox:沙箱决策

shouldUseSandbox.ts(154 行)决定命令是否在沙箱中执行:

typescript 复制代码
export function shouldUseSandbox(input: { command: string }): boolean {
  // 1. 沙箱是否启用
  if (!SandboxManager.isSandboxingEnabled()) return false

  // 2. 用户是否显式禁用沙箱
  if (input.dangerouslyDisableSandbox) return false

  // 3. 命令是否在用户排除列表中
  if (containsExcludedCommand(input.command)) return false

  return true
}

containsExcludedCommand() 使用与 stripSafeWrappers 相同的固定点迭代策略来匹配排除命令:

typescript 复制代码
function containsExcludedCommand(command: string): boolean {
  const excludedCommands = getExcludedCommands()  // 用户配置
  // 固定点迭代剥离 env vars + wrappers
  let stripped = command
  let prev = ''
  while (stripped !== prev) {
    prev = stripped
    stripped = stripAllLeadingEnvVars(stripped, BINARY_HIJACK_VARS)
    stripped = stripSafeWrappers(stripped)
  }
  // 检查剥离后的命令是否匹配排除列表
  return excludedCommands.some(pattern => matchPattern(stripped, pattern))
}

注意这里使用了 stripAllLeadingEnvVars(激进剥离)配合 BINARY_HIJACK_VARS 阻止列表。注释说明沙箱排除列表"not a security boundary --- permission prompts are"------它只是用户体验优化,真正的安全边界是权限提示。

8.3 沙箱技术概述(原书 4.11 节)

原书 4.11 节提到两种沙箱技术:

  • Linux : bubblewrap --- 命名空间隔离
  • macOS : Seatbelt (sandbox-exec) --- 系统调用过滤

沙箱限制了文件系统访问范围(只允许项目目录)和网络访问,即使命令执行了恶意操作,也无法访问项目外的文件或外发数据。


9. bashSecurity.ts:23 种安全检查

9.1 安全检查 ID 枚举

bashSecurity.ts 定义了 23 种 BASH_SECURITY_CHECK_IDS

复制代码
| 检查 ID | 检测内容 | 风险等级 |
|---------|---------|---------|
| INCOMPLETE_COMMANDS | 以 tab/flag/操作符开头的残缺命令 | 中 |
| COMMAND_SUBSTITUTION | $() / `` / ${} / $[] 命令替换 | 高 |
| PROCESS_SUBSTITUTION | <() >() =() 进程替换 | 高 |
| ZSH_DANGEROUS_COMMANDS | zmodload/emulate/sysopen/zpty 等 15 个 | 高 |
| ZSH_EXPANSION | Zsh 扩展语法 (~[]/=cmd) | 高 |
| JQ_SYSTEM_FUNCTION | jq 的 system() 函数调用 | 高 |
| OBFUSCATED_FLAGS | 混淆的 flag(如 --${var}) | 中 |
| SHELL_METACHARACTERS | ; | & && || 等 shell 元字符 | 中 |
| DANGEROUS_VARIABLES | IFS/PS1/PROMPT_COMMAND 等危险变量 | 高 |
| IFS_INJECTION | IFS 变量注入 | 高 |
| BRACE_EXPANSION | {a,b,c} 花括号扩展 | 中 |
| CONTROL_CHARACTERS | \x00-\x1f 控制字符 | 高 |
| UNICODE_WHITESPACE | \u00A0 等 Unicode 空白 | 高 |
| MID_WORD_HASH | 词中 # 号(注释混淆) | 中 |
| COMMENT_QUOTE_DESYNC | 注释与引号不同步 | 中 |
| ... | (共 23 种) | |

9.2 COMMAND_SUBSTITUTION_PATTERNS:11 种替换模式

typescript 复制代码
const COMMAND_SUBSTITUTION_PATTERNS = [
  /\$\([^)]*\)/,      // $(cmd) --- 命令替换
  /`[^`]*`/,           // `cmd` --- 旧式命令替换
  /\$\{[^}]*\}/,       // ${var} --- 参数扩展(可能包含命令)
  /\$\[[^]]*\]/,       // $[expr] --- 算术扩展
  /\(\)/,              // () --- 子 shell
  /<\([^)]*\)/,        // <(cmd) --- 进程替换(输入)
  />\([^)]*\)/,        // >(cmd) --- 进程替换(输出)
  /=\([^)]*\)/,        // =(cmd) --- Zsh 进程替换
  /~\[[^\]]*\]/,       // ~[expr] --- Zsh 动态目录
  // ... 共 11 种模式
]

9.3 isSafeHeredoc:安全 heredoc 的精确验证

bashSecurity.ts 第 317 行的 isSafeHeredoc() 是一个极其精细的函数------它是 EARLY-ALLOW 路径 ,返回 true 会绕过所有后续验证器。因此它的检查必须可证明安全(provable safe),而非"可能安全"。

唯一允许的模式是:

复制代码
[prefix] $(cat <<'DELIM'
[body lines]
DELIM
) [suffix]

关键约束:

  • 分隔符必须单引号引用('DELIM')或转义(\DELIM),确保 body 是字面文本
  • 关闭分隔符必须独占一行 (或仅尾部空白 + )
  • 关闭分隔符必须是第一个匹配行(精确复制 bash 行为)
  • $( 前必须有非空白前缀(确保在参数位置,不是命令名位置)
  • 剥离 heredoc 后的剩余文本只允许安全字符

函数使用逐行匹配 (非正则 [\s\S]*?)来精确复制 bash 的 heredoc 关闭行为,防止正则跨行匹配跳过第一个分隔符。

9.4 stripSafeRedirections:安全重定向剥离

typescript 复制代码
function stripSafeRedirections(command: string): string {
  // 剥离安全重定向:2>&1, >/dev/null, </dev/null
  // 需要尾部边界防止前缀匹配攻击
  // `2>&1; rm -rf /` 不应被剥离为 `; rm -rf /`
}

10. 命令语义解释 --- commandSemantics.ts

10.1 exit code 语义

commandSemantics.ts(141 行)是 exit code 的语义解释器------某些命令的非零退出码不代表错误:

typescript 复制代码
const COMMAND_SEMANTICS = new Map<string, CommandSemantic>([
  ['grep', { exitCode1IsError: false, message: 'No matches found' }],
  ['rg', { exitCode1IsError: false, message: 'No matches found' }],
  ['find', { exitCode1IsError: false, message: 'Some paths could not be searched' }],
  ['diff', { exitCode1IsError: false, message: 'Files differ' }],
  ['test', { exitCode1IsError: false, message: 'Condition evaluated to false' }],
  // ...
])

10.2 interpretCommandResult

typescript 复制代码
function interpretCommandResult(
  command: string,
  exitCode: number,
): { isError: boolean; message?: string } {
  const baseCmd = heuristicallyExtractBaseCommand(command)
  const semantic = COMMAND_SEMANTICS.get(baseCmd)
  if (semantic && exitCode === 1 && !semantic.exitCode1IsError) {
    return { isError: false, message: semantic.message }
  }
  return { isError: exitCode !== 0 }
}

heuristicallyExtractBaseCommand() 提取最后一条命令的基命令名------对于复合命令 cd /tmp && grep "test" file.txt,提取的是 grep 而非 cd


11. 破坏性命令警告 --- destructiveCommandWarning.ts

11.1 15 种破坏性模式

destructiveCommandWarning.ts(103 行)检测 15 种破坏性命令模式:

复制代码
| 类别 | 模式 | 正则 |
|------|------|------|
| Git | git reset --hard | /git\s+reset\s+--hard/ |
| Git | git push --force | /git\s+push\s+(-f|--force)/ |
| Git | git clean -f | /git\s+clean\s+-[a-z]*f/ |
| Git | git checkout . | /git\s+checkout\s+\./ |
| Git | git stash drop | /git\s+stash\s+drop/ |
| Git | git branch -D | /git\s+branch\s+-D/ |
| Git | git --no-verify | /git\s+.*--no-verify/ |
| Git | git commit --amend | /git\s+commit\s+--amend/ |
| 文件 | rm -rf | /rm\s+-[a-z]*r[a-z]*f/ |
| 文件 | rm -r | /rm\s+-[a-z]*r/ |
| 文件 | rm -f | /rm\s+-[a-z]*f/ |
| 数据库 | DROP TABLE | /DROP\s+TABLE/i |
| 数据库 | TRUNCATE TABLE | /TRUNCATE\s+TABLE/i |
| 数据库 | DELETE FROM | /DELETE\s+FROM/i |
| 基础设施 | kubectl delete | /kubectl\s+delete/ |
| 基础设施 | terraform destroy | /terraform\s+destroy/ |

11.2 纯信息性提示

typescript 复制代码
export function getDestructiveCommandWarning(
  command: string,
): { isDestructive: boolean; warning?: string } {
  for (const pattern of DESTRUCTIVE_PATTERNS) {
    if (pattern.regex.test(command)) {
      return {
        isDestructive: true,
        warning: pattern.warning,
      }
    }
  }
  return { isDestructive: false }
}

关键设计 :破坏性命令警告是纯信息性的,不影响权限逻辑。即使命令匹配破坏性模式,权限决策仍然由七层防御链独立决定。这确保了关注点分离------安全决策和用户提醒是独立的。


12. 模式验证 --- modeValidation.ts

12.1 acceptEdits 模式自动放行

modeValidation.ts(116 行)定义了 acceptEdits 模式下自动放行的 7 种文件系统命令:

typescript 复制代码
const ACCEPT_EDITS_ALLOWED_COMMANDS = new Set([
  'mkdir',   // 创建目录
  'touch',   // 创建空文件
  'rm',      // 删除文件(仍受 pathValidation 约束)
  'rmdir',   // 删除空目录
  'mv',      // 移动/重命名
  'cp',      // 复制
  'sed',     // 流编辑(仍受 sedValidation 约束)
])

12.2 checkPermissionMode

typescript 复制代码
export function checkPermissionMode(
  input: { command: string },
  context: ToolUseContext,
): PermissionResult {
  const mode = context.getAppState().permissionMode

  // bypassPermissions 和 dontAsk 模式跳过
  if (mode === 'bypassPermissions' || mode === 'dontAsk') {
    return { behavior: 'passthrough', message: 'Mode skips permission check' }
  }

  // acceptEdits 模式:检查命令是否在允许列表中
  if (mode === 'acceptEdits') {
    const baseCmd = extractBaseCommand(input.command)
    if (ACCEPT_EDITS_ALLOWED_COMMANDS.has(baseCmd)) {
      return {
        behavior: 'allow',
        decisionReason: { type: 'mode', mode: 'acceptEdits' },
      }
    }
  }

  return { behavior: 'passthrough', message: 'No mode override' }
}

注意:即使 acceptEdits 模式自动放行 rm,路径约束(Layer 4)和 Sed 约束(Layer 5)仍然生效。模式验证只是跳过了分类器审批(Layer 7),前五层防御不受影响。


13. AsyncGenerator 流式执行(原书 4.7.3 节)

13.1 runShellCommand:2 秒阈值的进度轮询

原书 4.7.3 节描述了 AsyncGenerator 驱动的流式执行。BashTool.tsx 中的 runShellCommand 实现了这一设计:

复制代码
执行流程:
1. 立即启动子进程
2. 等待 2 秒
   ├─ 2 秒内完成 → 直接返回结果
   └─ 2 秒后未完成 → 进入进度轮询循环
3. 进度轮询循环
   ├─ 每秒检查进程状态
   ├─ yield 进度事件({ type: 'progress', message: 'Still running...' })
   └─ 检查用户是否按 Ctrl+B
4. Ctrl+B 热切换
   ├─ 前台进程 → 后台 LocalShellTask
   ├─ 释放终端控制权
   └─ 用户可继续输入其他命令
5. 进程结束
   ├─ 收集 stdout/stderr
   ├─ 应用 commandSemantics 解释 exit code
   └─ 返回最终结果

13.2 AsyncGenerator 的设计优势

使用 AsyncGenerator(async function*)而非 Promise 的优势:

typescript 复制代码
async function* runShellCommand(
  command: string,
  options: ShellOptions,
): AsyncGenerator<ToolProgressData | ToolResult> {
  // yield 进度事件 → UI 实时更新
  // yield 最终结果 → 工具系统接收
}
  • 流式进度:用户看到实时进度,而非等待 30 秒后突然出现结果
  • 可中断:用户可以 Ctrl+C 中断,也可以 Ctrl+B 切换到后台
  • 背压控制:Generator 的 pull 模型天然支持背压

14. 命令清洗的完整流程图

将所有防御层串联起来,一条命令从输入到执行的完整清洗流程:

复制代码
用户输入命令
    │
    ▼
┌─ bashToolHasPermission() ─────────────────────────────────────┐
│                                                                │
│  1. Tree-sitter AST 解析                                       │
│     parseCommandRaw() → parseForSecurityFromAst()              │
│     ├─ 预检查(控制字符/Unicode/反斜杠/Zsh/花括号)              │
│     ├─ PARSE_ABORTED → fail-closed (ask)                       │
│     └─ checkSemantics() → eval/exec/source 检查                │
│                                                                │
│  2. 模式验证                                                    │
│     checkPermissionMode()                                      │
│     ├─ acceptEdits: mkdir/touch/rm/... 自动放行                 │
│     └─ bypassPermissions/dontAsk: 跳过                         │
│                                                                │
│  3. 沙箱自动放行                                                │
│     shouldUseSandbox() → checkSandboxAutoAllow()               │
│     ├─ 沙箱启用 + 命令适合沙箱 → allow                          │
│     └─ 命中 deny/ask 规则 → 不放行                              │
│                                                                │
│  4. 精确匹配                                                    │
│     bashToolCheckExactMatchPermission()                        │
│     ├─ deny → 拒绝                                             │
│     ├─ ask → 询问用户                                           │
│     └─ allow → 放行                                             │
│                                                                │
│  5. 前缀/通配符匹配                                              │
│     bashPermissionRule() + matchWildcardPattern()              │
│     ├─ deny: stripAllLeadingEnvVars (激进剥离)                  │
│     │   匹配 deny 规则 → 拒绝                                   │
│     ├─ ask: stripAllLeadingEnvVars (激进剥离)                   │
│     │   匹配 ask 规则 → 询问用户                                 │
│     └─ allow: stripSafeWrappers (保守剥离)                      │
│         匹配 allow 规则 → 放行                                   │
│                                                                │
│  6. 路径约束                                                    │
│     checkPathConstraints()                                     │
│     ├─ validateSinglePathCommandArgv() (AST argv)              │
│     ├─ checkDangerousRemovalPaths() (rm/rmdir 危险路径)         │
│     ├─ validateOutputRedirections() (重定向目标)                │
│     └─ compoundCommandHasCd() (cd+write 组合)                  │
│                                                                │
│  7. Sed 约束                                                    │
│     checkSedConstraints()                                      │
│     ├─ sedCommandIsAllowedByAllowlist() (双模式白名单)          │
│     └─ containsDangerousOperations() (denylist)                │
│                                                                │
│  8. 只读自动放行                                                │
│     validateReadOnlyCommand()                                  │
│     ├─ GIT/GH/RIPGREP/PYRIGHT/DOCKER 白名单                    │
│     ├─ validateFlags() (flag 白名单)                            │
│     └─ containsVulnerableUncPath() (UNC 漏洞)                  │
│                                                                │
│  9. 分类器审批                                                  │
│     classifyBashCommand() + pendingClassifierCheck             │
│     ├─ 分类器 allow → 放行                                      │
│     ├─ 分类器 deny → 拒绝                                       │
│     └─ 分类器 ask / 无分类器 → 询问用户                          │
│                                                                │
│  10. 破坏性命令警告(不影响决策)                                 │
│      getDestructiveCommandWarning()                            │
│      └─ 附带警告信息到结果中                                     │
│                                                                │
└────────────────────────────────────────────────────────────────┘
    │
    ▼
  执行 / 拒绝 / 询问用户
    │
    ▼ (如果执行)
  runShellCommand() AsyncGenerator
    ├─ 启动子进程
    ├─ 2s 阈值 → 进度轮询
    ├─ Ctrl+B → 后台执行
    ├─ commandSemantics 解释 exit code
    └─ 返回结果

15. 原书对照验证表

原书描述 源码验证 一致性 补充说明
七层安全防御流水线 bashToolHasPermission() 中的 10 步检查链 ✅ 一致 源码实际有 10 步,比原书 7 层更细粒度
Layer 1: AST 解析 parseForSecurityFromAst() + checkSemantics() ✅ 一致 源码有六道预检查 + PARSE_ABORTED 对抗处理
Layer 2: 精确匹配 bashToolCheckExactMatchPermission() ✅ 一致 ---
Layer 3: 前缀/通配符 bashPermissionRule() + matchWildcardPattern() ✅ 一致 deny/allow 不对称剥离是源码独有的精细设计
Layer 4: 路径检查 checkPathConstraints() ✅ 一致 35 种 PathCommand + -- 处理 + cd+write 组合检测
Layer 5: Sed 约束 checkSedConstraints() ✅ 一致 双模式白名单 + denylist + 模拟执行
Layer 6: 只读放行 validateReadOnlyCommand() ✅ 一致 6 组白名单 + flag 验证 + UNC 检测
Layer 7: 分类器审批 classifyBashCommand() ✅ 一致 GrowthBook Feature Flag 灰度控制
固定点迭代命令清洗 stripSafeWrappers() 两阶段 while 循环 ✅ 一致 Phase 1 剥离 env vars,Phase 2 剥离 wrappers
Sed 模拟执行 sedReplacementToJsReplacement() + writeTextContent() ✅ 一致 确保"所见即所得"
AsyncGenerator 流式执行 runShellCommand() async function* ✅ 一致 2s 阈值 + 进度轮询 + Ctrl+B 热切换
exit code 语义解释 commandSemantics.ts ✅ 一致 grep/rg(1=无匹配)/find(1=部分成功)/diff(1=有差异)
破坏性命令警告 destructiveCommandWarning.ts ✅ 一致 15 种模式,纯信息性不影响权限
bubblewrap(Linux)/Seatbelt(macOS) SandboxManager ✅ 一致 原书 4.11 节描述
--- stripAllLeadingEnvVars deny/allow 不对称 📝 源码独有 原书未详细描述 deny 激进 / allow 保守策略
--- isSafeHeredoc 精确 heredoc 验证 📝 源码独有 原书未详细描述 EARLY-ALLOW 路径
--- BARE_SHELL_PREFIXES 阻止危险前缀建议 📝 源码独有 原书未提及
--- MAX_SUBCOMMANDS_FOR_SECURITY_CHECK = 50 📝 源码独有 DoS 防护,原书未提及
--- Shadow mode 影子测试 📝 源码独有 Tree-sitter 灰度发布的安全验证机制

16. 设计哲学总结

16.1 Fail-Closed 原则

整个七层防御链贯穿 Fail-Closed 原则:

  • AST 解析失败 → too-complex → ask(不放行)
  • Tree-sitter 超时 → PARSE_ABORTED → ask(不降级到 legacy)
  • 语义检查遇到未知 flag → fail closed(不 fall-through)
  • timeout duration 格式不匹配 → fail closed(不 fall-through)
  • nice 参数包含扩展 → fail closed(不 fall-through)

16.2 纵深防御

七层防御不是冗余------每层防御覆盖不同的攻击向量:

  • Layer 1 防御解析层攻击(Tree-sitter/bash 差异)
  • Layer 2-3 防御权限规则绕过(env var/wrapper 混淆)
  • Layer 4 防御文件系统攻击(路径遍历/危险目录)
  • Layer 5 防御Sed 命令注入(e 命令/w 写入)
  • Layer 6 防御flag 注入(--output=/etc/passwd)
  • Layer 7 防御未知命令(分类器 LLM 判断)

16.3 不对称安全边界

deny 和 allow 使用不同强度的剥离策略,体现了"安全边界不对称"的设计:

  • deny 不可绕过:激进剥离所有 env vars,确保 deny 规则不会被 env var 前缀绕过
  • allow 不可扩大 :保守剥离已知安全 env vars,防止 DOCKER_HOST=evil 类攻击通过 allow 规则

16.4 可观测性优先

每个安全检查都通过 logEvent('tengu_bash_security_check_triggered', ...) 记录遥测,包括:

  • checkId:触发的检查类型
  • subId:子类型(如 INCOMPLETE_COMMANDS 有 3 个子类型)
  • nodeTypeId:AST 节点类型(用于 too-complex 分析)

这些遥测数据用于安全审计和防御策略调优。

16.5 与其他框架对比(原书 4.10 节)

原书 4.10 节将 Claude Code 的 BashTool 安全与其他框架对比:

复制代码
| 框架 | 命令安全策略 | 局限性 |
|------|------------|--------|
| LangChain | 无内置安全 | 完全依赖开发者 |
| OpenAI Code Interpreter | Python 沙箱 | 不支持 shell 命令 |
| Claude Code | 七层防御 + Tree-sitter AST | 复杂但全面 |
| Vercel AI SDK | 无内置安全 | 完全依赖开发者 |

Claude Code 的七层防御链是当前 AI 编程工具中最复杂的命令安全实现,没有之一。


17. 验证清单

  • BashTool 是否真的有 7 层防御? → ,源码实际有 10 步检查,对应原书 7 层
  • 命令验证如何实现? → Tree-sitter AST 解析 + 六道预检查 + 语义检查
  • 路径检查如何实现? → 35 种 PathCommand 路径提取 + -- 处理 + 危险路径检测 + cd+write 组合检测
  • Shell 注入如何防御? → 命令替换/进程替换/Zsh扩展/IFS注入/Unicode空白等 23 种安全检查
  • 超时如何处理? → AsyncGenerator + 2s 阈值 + 进度轮询 + Ctrl+B 热切换
  • 沙箱如何决策? → shouldUseSandbox() 三步检查(启用/禁用/排除)
  • 权限如何决策? → 精确匹配 → 前缀/通配符 → deny/ask/allow 不对称剥离
  • 输出如何截断? → maxResultSizeChars 限制 + 流式进度推送
  • 固定点迭代如何实现? → stripSafeWrappers 两阶段 while 循环直到收敛
  • Sed 模拟执行如何实现? → sedReplacementToJsReplacement() + writeTextContent()

下一篇:4.4-4.6 工具执行管线 --- 九步执行与并发编排

相关推荐
数据知道3 小时前
BGP 劫持是怎么回事?运营商级路由安全科普
网络·安全·网络安全·智能路由器·bgp劫持
2601_956743683 小时前
上海AI Agent智能体开发选型:从工程架构看落地路径解析
大数据·人工智能·ai·架构·开发经验·上海
ApacheSeaTunnel3 小时前
一套替代四套!同程旅行用 Apache SeaTunnel 搭建多模态统一数据通道
大数据·数据库·ai·开源·数据集成·seatunnel·技术分享·数据同步
zhayujie3 小时前
如何设计一个 Agent 友好的 CLI 工具
ai·大模型·agent·cli·harness
俊哥V3 小时前
每日 AI 研究简报 · 2026-07-15
人工智能·ai
土星云SaturnCloud4 小时前
SAM2模型在土星云边缘计算设备上的部署实战(下):从原理到落地全解析
服务器·人工智能·ai·边缘计算
深信达沙箱4 小时前
SDC沙箱安全存储|内鬼外贼拿不走
安全·源代码防泄密·数据防泄漏·源代码加密·源代码防泄密‘’
Designerpupu4 小时前
怎么在PS中做产品精修?传统手工精修 VS Nano Banana AI金属修图
ai·photoshop·美工·产品精修·五金精修·精修·金属精修
国科安芯4 小时前
ASC8T245S 8通道电平转换设计实战:从系统架构到QFN24 Layout再到量产测试
网络·单片机·物联网·安全·fpga开发·系统架构