ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

LLM生成Python代码库的轻量审计工具:AST解析、动态轨迹与规则扫描实践

2026/8/29 19:06:22 拓冰建站 浏览量
LLM生成Python代码库的轻量审计工具:AST解析、动态轨迹与规则扫描实践 LLM 生成的 Python 代码库越来越多许多团队在评审这类代码时会遇到同一个问题代码能跑但没人能快速说清楚它内部到底做了什么。人工写的项目通常有 commit 历史、issue 讨论和命名演进痕迹而 LLM 生成代码往往是一整块产出缺少中间步骤很多文件甚至直接由一次提示词生成。针对 LLM 生成的 codebase需要一种能“逐步走查”并“规则化审计”的工具帮助开发者快速定位风险、理解执行路径、判断哪些代码真正会被运行。下面围绕这样一个轻量审计工具的设计与实现展开包含环境准备、AST 解析、trace 轨迹追踪、规则设计和报告输出。1. 先理解 LLM 生成 Python 代码库为什么难审计1.1 传统代码审查方法失效的三个原因传统 Python 代码审查通常依赖三类手段查看 git diff、全局搜索、运行测试。面对 LLM 生成的代码这三类手段的效果都会明显下降。第一缺少 commit 历史。人工开发的代码库通过多次提交形成演进路径审查者可以按提交顺序理解设计决策。LLM 生成代码通常只产生一个巨大的初始提交所有代码一次性出现diff 会覆盖几千行逐行审查没有重点。第二存在大量“看起来合理但实际不必要”的代码。模型倾向于生成防御性 if 分支、重复导入、未使用的辅助函数以及不会触发的异常处理逻辑。全文搜索只能找到关键词不能判断这段代码是否真的会被执行。第三静态检查工具会失准。普通 pylint 或 flake8 告警可以处理风格问题但 LLM 经常生成自定义的辅助函数和动态属性访问例如用getattr(obj, name)代替直接调用导致静态分析出现大量误报。更麻烦的是如果模型幻觉调用了一个不存在的 API静态语法检查不一定能发现只有走到执行阶段才会暴露。传统审查方式与 LLM 生成代码的冲突可以用下面这张表概括。传统审查方式面对 LLM 生成代码时的失效点依赖 commit 历史只有一次巨大提交无法理解演进过程全文搜索无法区分存活代码与死代码直接运行测试测试常常缺失或只覆盖 happy path静态代码检查大量误报且无法捕捉 API 幻觉1.2 什么是 step through 和 audit“step through”这个词来自调试器但这里并不是要求工具像 IDE 调试器一样挂断点。审计场景下的 step through 是让工具沿着代码结构或执行轨迹把关键语句逐条展示出来回答一个问题这段代码实际做了哪几步。“audit”则更偏规则化审计通过静态规则和动态轨迹检查代码是否存在硬编码密钥、裸异常、未定义名称、危险 API 调用等问题。两者结合后工具的输出会同时包含两个维度一是代码库的静态结构二是真实执行时经过的路径。这样审查者可以快速看到哪些文件包含风险写法。哪些风险写法真的会被执行。哪些函数虽然存在但根本没有被调用。哪些 API 调用在运行时才能发现失败。1.3 工具目标与边界一个轻量 LLM 代码审计工具的目标可以定义为输入一个 Python 代码库输出一份可阅读的审计报告。报告包含文件清单、AST 结构摘要、调用关系、执行轨迹和高风险规则命中列表。工具边界同样重要。它不能完全替代人工逻辑判断也不能证明代码“没有 bug”。它本质上是一个加速理解的辅助系统把代码库转成更小、更可导航的信息集合再把风险点标出来。注意审计工具不是自动修复工具。它负责把“代码实际是怎么走的”和“哪些地方可能有风险”呈现清楚最终决策仍然由开发者完成。2. 环境准备与项目结构设计2.1 为什么优先选择 Python 标准库实现Python 自带ast、tokenize、sys.trace和argparse可以覆盖审计工具的大部分需求无第三方运行时依赖。ast负责解析 Python 源码生成语法树。tokenize负责拆分源代码为 token 序列可以补充注释、字符串等 AST 不保留的信息。sys.settrace可以挂接行级追踪函数记录代码实际执行的行号。pathlib负责跨平台路径处理。优先使用标准库不是为了炫技而是为了让工具更容易被复制和二次修改。LLM 生成代码速度很快审计工具本身的迭代也应该快。先基于标准库搭建最小闭环后续再接入pyflakes、bandit、semgrep等更重的分析能力是更稳妥的路线。2.2 依赖安装与环境验证当前示例不依赖第三方运行时包建议使用 Python 3.9 或更高版本推荐 3.10因为 3.10 以后ast节点的字段更稳定zoneinfo等内置模块也有改进。验证环境时只需要执行两条命令python --version python -c import ast, tokenize, sys, pathlib; print(stdlib ok)如果后续需要为报告做测试覆盖验证可以安装pytest和coverage。它们不是工具运行时的硬依赖而是工具本身的开发依赖。依赖用途是否必须Python 3.9运行工具与目标代码必须ast解析语法树标准库tokenize行定位与注释扫描标准库syshook 运行轨迹标准库pathlib文件路径处理标准库pytest工具单元测试开发可选coverage测试覆盖率验证开发可选2.3 项目目录结构审计工具建议按模块拆分避免把所有逻辑写进一个文件。目录结构可以参考下面这份llm-audit/ ├── audit/ │ ├── __init__.py │ ├── cli.py │ ├── parser.py │ ├── tracer.py │ ├── rules.py │ └── report.py ├── examples/ │ └── generated_code/ │ └── demo.py ├── tests/ │ └── test_parser.py └── README.md其中parser.py负责 AST 解析和代码结构提取tracer.py负责动态轨迹采集rules.py负责静态风险规则report.py负责生成报告cli.py负责把各模块串起来。这种分层方式的好处是审计能力可以单独测试后续新增规则不需要改动执行轨迹逻辑。3. 第一步用 AST 把代码库变成可操作的树3.1 从文件清单到 AST 解析审计工具的第一步是遍历目标目录找到所有 Python 文件。这个步骤看起来简单但很容易被忽略目录过滤问题。如果直接把虚拟环境目录也遍历进去后面所有分析都会被依赖包噪声淹没。下面是一个基础实现from pathlib import Path import ast DEFAULT_EXCLUDE_DIRS { venv, .venv, site-packages, .git, __pycache__, .mypy_cache, } def find_python_files(root: Path, exclude_dirsNone): exclude_dirs exclude_dirs or DEFAULT_EXCLUDE_DIRS for path in sorted(root.rglob(*.py)): if any(part in exclude_dirs for part in path.parts): continue yield path def parse_python_file(path: Path): try: source path.read_text(encodingutf-8) except UnicodeDecodeError: # 部分旧项目不是 UTF-8 编码退回到 GBK 或其他本地编码 source path.read_text(encodingutf-8, errorsignore) tree ast.parse(source, filenamestr(path)) return tree, source这里的关键点是使用path.rglob(*.py)递归扫描而不是Path.glob(*/*.py)前者可以覆盖任意深度。排除site-packages和venv目录避免把依赖包当成业务代码。读取文件时使用read_text(encodingutf-8)捕获UnicodeDecodeError避免一个小文件让整次审计中断。3.2 提取函数、类与调用关系得到 AST 后需要把它整理成可导航的结构。最常用的是ast.NodeVisitor它能在遍历时访问函数定义、类定义和函数调用节点。import ast class StructureVisitor(ast.NodeVisitor): def __init__(self): self.functions [] self.classes [] self.calls [] def visit_FunctionDef(self, node): self.functions.append({ name: node.name, lineno: node.lineno, end_lineno: getattr(node, end_lineno, node.lineno), }) self.generic_visit(node) visit_AsyncFunctionDef visit_FunctionDef def visit_ClassDef(self, node): self.classes.append({ name: node.name, lineno: node.lineno, }) self.generic_visit(node) def visit_Call(self, node): func node.func if isinstance(func, ast.Name): self.calls.append({ name: func.id, lineno: node.lineno, }) elif isinstance(func, ast.Attribute): obj func.value if isinstance(obj, ast.Name): self.calls.append({ object: obj.id, attribute: func.attr, lineno: node.lineno, }) self.generic_visit(node)这段代码会记录三类信息文件中定义了哪些函数。文件中定义了哪些类。哪些调用点调用了内置名称、模块级名称或对象方法。调用关系提取是后面判断“这个函数有没有被使用”的基础。虽然不需要像依赖图工具那样做全量数据流分析但至少能发现明显的孤立函数。3.3 关联源码行号用 tokenize 建立行定位辅助AST 节点自带lineno和end_lineno可以直接定位到源码行。但 AST 不会保留注释也不会保留字符串中的风险内容。如果要审计“硬编码密码”“敏感信息注释”AST 就不太够用。可以用tokenize扫描源码建立行号到代码内容的映射同时保留注释和字符串 token 的原始文本。import tokenize import io def build_line_map(source): lines source.splitlines() comments {} tokens_by_line {} try: tokens tokenize.generate_tokens(io.StringIO(source).readline) for tok in tokens: token_type tok.type if token_type tokenize.COMMENT: comments[tok.start[0]] tok.string tokens_by_line.setdefault(tok.start[0], []).append(tok.string) except tokenize.TokenError: pass return { lines: lines, comments: comments, tokens_by_line: tokens_by_line, }实际审计规则里需要从行号反查源码时直接使用这个 map。比如轨迹记录到第 42 行就可以通过line_map[lines][41]拿到第 42 行的原始代码再结合comments判断这一行附近是否有风险注释。3.4 常见坑路径过滤、编码和 AST 不保留注释第一个常见坑是路径过滤不严。很多开发者只排除了venv但 LLM 生成的代码可能被放在generated/目录而依赖包位于lib/python3.10/site-packages。如果只用rglob(*.py)报告会被依赖包撑爆。解决办法是在遍历时检查完整路径片段。第二个常见坑是非 UTF-8 编码。Windows 上生成的文件可能是 GBKmacOS 和 Linux 可能是 UTF-8。读取时加errorsignore可以避免崩溃但代价是某些字符丢失。更稳妥的做法是先用charset-normalizer或chardet做编码探测但这类库需要额外安装在最小版本里不纳入。第三个常见坑是 AST 不保留注释。如果审计规则需要检查“代码里是否存在明文口令注释”必须使用tokenize而不是ast。AST 只关注语法结构注释和格式信息在解析阶段就被丢弃。4. 第二步用 trace 实现逐语句执行轨迹4.1 为什么静态 AST 不够静态 AST 能告诉你代码里有什么但无法告诉你程序真正执行时经过哪条分支。LLM 生成代码里经常出现大量“看起来完整”的 if/else 分支其中一部分条件永远不会成立或者某些函数定义了但从不被调用。如果只做 AST 审计报告会包含所有潜在风险点包括大量死代码里的风险。这样会让审查者花费大量时间处理不实际执行的问题。动态轨迹的作用就是把“实际执行路径”过滤出来。需要注意的是动态轨迹只能证明“代码在某一次运行时经过了这些行”不能证明“所有隐藏路径都不存在”。所以动态轨迹更适合作为静态规则的补充而不是替代。4.2 用 sys.settrace 实现行级追踪Python 的sys.settrace允许设置一个全局 trace 函数在函数调用、行执行、返回和异常等事件发生时被调用。利用它可以记录每一行目标代码的执行情况。一个极简的 LineTracer 实现如下import sys from pathlib import Path class LineTracer: def __init__(self, allowed_pathsNone): self.allowed_paths [] for p in allowed_paths or []: self.allowed_paths.append(str(Path(p).resolve())) self.events [] def start(self): sys.settrace(self._trace) def stop(self): sys.settrace(None) def _should_trace(self, filename): if not filename.endswith(.py): return False resolved str(Path(filename).resolve()) for allowed in self.allowed_paths: if resolved.startswith(allowed): return True return False def _trace(self, frame, event, arg): filename frame.f_code.co_filename if not self._should_trace(filename): return None if event in (call, line, return, exception): self.events.append({ event: event, lineno: frame.f_lineno, function: frame.f_code.co_name, module: frame.f_globals.get(__name__, ), filename: filename, }) return self._trace使用方式tracer LineTracer(allowed_paths[examples/generated_code]) tracer.start() try: exec(open(examples/generated_code/demo.py, encodingutf-8).read(), {}) finally: tracer.stop() for event in tracer.events: print(event)这里有几个容易出错的地方trace 函数必须返回自身否则只能追踪当前层级不能进入函数内部。sys.settrace是全局设置会影响当前进程的所有线程。生产环境一定不要在多线程服务进程中直接挂全局 hook最好在独立进程中执行目标代码。exec执行目标代码会污染当前进程的全局变量更安全的做法是用subprocess.run([python, target_file])启动子进程收集输出后再分析轨迹。子进程方式不会让目标代码损坏审计工具自身。4.3 控制追踪范围避免 site-packages动态追踪最大的噪音来源是第三方依赖。如果目标代码调用了 requests、pandas 等库默认的 trace 逻辑会一路追踪到 site-packages产生成千上万条事件。解决办法是设置allowed_paths只允许追踪用户代码目录。上面的_should_trace已经实现了这个逻辑。更严格的做法是只追踪指定模块名或文件前缀。def _should_trace(self, filename): if not filename.endswith(.py): return False resolved str(Path(filename).resolve()) for allowed in self.allowed_paths: if resolved allowed or resolved.startswith(allowed): return True return False这样可以让轨迹只覆盖目标代码库第三方库内部的执行不会进入报告。这在分析“目标代码是否调用了不可信 API”时尤其有用你只关心业务代码的调用点不关心库内部实现。4.4 将轨迹与 AST 节点合并得到轨迹后下一步是把它转成可读的代码步骤。最简单的方式是使用line_map把每个line事件映射回源码行。def build_step_list(events, source_lines): steps [] for event in events: if event[event] line: lineno event[lineno] source source_lines[lineno - 1] if lineno len(source_lines) else steps.append({ lineno: lineno, function: event[function], source: source.strip(), }) elif event[event] call: steps.append({lineno: event[lineno], function: event[function]}) return steps如果同一个函数被调用多次轨迹会重复出现。对审计报告来说可以只保留“首次执行路径”或按函数聚合后的路径。聚合逻辑可以按filename function分组只保留每个函数首次执行的行序列避免一个循环产生几百行重复报告。5. 第三步针对 LLM 生成代码的审计规则5.1 高频风险硬编码密钥、裸异常、未使用导入和幻觉 API 调用LLM 生成的 Python 代码中有四类风险出现频率特别高。第一硬编码密钥。模型训练材料包含大量示例代码生成时容易把api_key sk-xxx写进业务代码然后被提交到仓库。第二裸异常。生成代码经常使用except Exception: pass或except: pass目的是不让程序崩溃但异常信息被完全吞掉线上问题无法排查。第三未使用导入。LLM 生成大文件时开头会导入大量模块很多只在模型想象的某个函数里使用实际函数并未调用。第四幻觉 API 调用。模型调用不存在的库函数或错误的方法名。静态检查不一定能发现因为module.attr的合法性只有在运行或类型分析时才会暴露。动态轨迹加上模块属性检查可以在一定程度上辅助发现。5.2 规则框架与两个可运行规则规则模块可以完全复用ast.NodeVisitor机制。每一条规则只负责一个风险点最终把所有规则的 findings 合并到同一个列表。下面是一个最小规则框架import ast class BaseRule: keyword base-rule severity warning def __init__(self): self.findings [] def add_finding(self, lineno, message): self.findings.append({ rule: self.keyword, severity: self.severity, lineno: lineno, message: message, })基于这个框架实现两条规则。第一条是裸异常扫描class BroadExceptRule(BaseRule): keyword broad-except severity warning def __init__(self): super().__init__() self.visitor self._Visitor(self) class _Visitor(ast.NodeVisitor): def __init__(self, rule): self.rule rule def visit_ExceptHandler(self, node): if node.type is None: self.rule.add_finding(node.lineno, 裸 except 会吞掉所有异常) elif isinstance(node.type, ast.Name) and node.type.id Exception: self.rule.add_finding(node.lineno, 捕获 Exception 但没有记录日志) self.generic_visit(node)第二条是硬编码密钥扫描。这里只做一个保守版本当赋值语句的目标变量名包含 password、secret、api_key、token 时如果赋值内容是字符串常量就记录风险。class HardcodedSecretRule(BaseRule): keyword hardcoded-secret severity error SECRET_HINTS (password, secret, api_key, token) def __init__(self): super().__init__() self.visitor self._Visitor(self) class _Visitor(ast.NodeVisitor): def __init__(self, rule): self.rule rule def visit_Assign(self, node): for target in node.targets: if isinstance(target, ast.Name): name target.id.lower() if any(hint in name for hint in self.rule.SECRET_HINTS): if isinstance(node.value, ast.Constant) and isinstance(node.value.value, str): self.rule.add_finding( node.lineno, f变量 {target.id} 被赋值为字符串疑似硬编码密钥, ) self.generic_visit(node)使用规则时只需要对每个 Python 文件执行一次ast.walk或NodeVisitordef run_rules(tree): findings [] rule_classes [BroadExceptRule, HardcodedSecretRule] for rule_cls in rule_classes: rule rule_cls() rule.visitor.visit(tree) findings.extend(rule.findings) return findings5.3 规则触发条件速查表实际项目中规则触发条件往往需要根据团队规范调整。下面给出一个可以修改的速查表。规则触发条件严重级别建议处理方式硬编码密钥赋值目标含 secret/password/api_key/token值为字符串常量error接入密钥管理服务删除仓库中的明文密钥裸异常except:或except Exception:且没有任何日志记录warning改为捕获具体异常并在 except 块中记录日志未使用导入import 的名称在模块中从未作为 Name 或 Attribute 出现warning删除未使用导入或给出使用理由危险动态调用通过eval、exec、__import__调用运行时代码error尽量替换为安全实现必要时加白名单除零风险表达式中出现除法且除数可能来自外部输入warning增加除数判断或使用 safe_div 工具函数API 幻觉模块中存在module.attr但模块没有该属性且运行轨迹到达该行error用真实 SDK 文档核对调用方式这里的规则触发条件不是最终答案而是审计工具落地时的起点。每个团队都可以根据自己的代码规范和线上事故记录把这些规则扩成几十条。6. 运行与验证CLI、报告和排错6.1 CLI 参数设计为了让工具可以被命令行直接使用需要一个简单的参数入口。argparse足够完成这项工作。import argparse from pathlib import Path def build_parser(): parser argparse.ArgumentParser( progllm-audit, description快速逐步审查 LLM 生成的 Python 代码库, ) parser.add_argument(codebase, typePath, help目标代码库根目录) parser.add_argument(--run, typePath, help用于动态轨迹追踪的入口 Python 文件) parser.add_argument(--exclude, nargs*, default[], help额外排除目录名) parser.add_argument(--output, -o, typePath, defaultPath(audit_report.md)) parser.add_argument(--format, choices[text, markdown], defaultmarkdown) return parser def main(argvNone): args build_parser().parse_args(argv) print(f代码库: {args.codebase}) print(f执行入口: {args.run}) print(f报告输出: {args.output})这里--run不是必须参数。如果目标代码库有明确入口脚本指定它之后审计工具会执行一遍并采集轨迹如果没有入口工具只做静态 AST 和规则扫描。注意运行 LLM 生成的代码可能有风险。动态追踪应在沙箱、容器或隔离虚拟机中执行不要直接在开发机或生产机运行不可信代码。6.2 用示例代码库完整运行下面是一个模拟 LLM 生成的示例文件故意包含硬编码密钥和未使用导入。# examples/generated_code/demo.py import os import sys import requests def load_config(): api_key sk-1234567890secret secret_token token-produced-by-llm return api_key, secret_token def process(items): total 0 for item in items: total item return total / len(items) if __name__ __main__: key, token load_config() print(key) print(process([1, 2, 3]))运行审计命令python -m audit.cli examples/generated_code \ --run examples/generated_code/demo.py \ --output audit_report.md预期执行流程是扫描examples/generated_code下所有.py文件。解析demo.py的 AST提取函数和调用关系。运行规则扫描命中hardcoded-secret。通过子进程执行demo.py采集执行轨迹。把轨迹与 AST 行号合并生成 Markdown 报告。6.3 验证输出与预期报告Markdown 报告的核心结构可以按下面方式生成def generate_markdown(result): lines [] lines.append(# LLM Audit Report) lines.append() lines.append(f代码库: {result[codebase]}) lines.append() lines.append(## 文件清单) for path in result[files]: lines.append(f- {path}) lines.append() lines.append(## 风险发现) for finding in result[findings]: lines.append( f- [{finding[severity]}] f{finding[file]}:{finding[lineno]} f{finding[rule]} {finding[message]} ) lines.append() lines.append(## 执行轨迹) for step in result[steps]: lines.append(f- {step[function]}{step[lineno]}: {step[source]}) lines.append() return \n.join(lines)终端先输出 text 格式验证会比较方便python -m audit.cli examples/generated_code \ --run examples/generated_code/demo.py \ --format text文本格式会直接打印代码库: examples/generated_code 文件: examples/generated_code/demo.py 规则命中: hardcoded-secret at line 8 函数: load_config 已定义 函数: process 已定义 执行轨迹: demo.py:8 key load_config() demo.py:9 print(key) demo.py:10 print(process([1, 2, 3]))这里要注意print(key)本身会暴露密钥。审计工具负责标记风险但真正修复还需要人工完成。6.4 常见问题排查链路工具运行过程中可能出现的常见问题可以按下面这个表格快速定位。问题现象可能原因检查方式解决方案没有找到任何 py 文件路径错误或排除规则过多打印扫描到的文件列表检查 codebase 参数调整 excludeAST 解析报 SyntaxError文件包含 Python 2 语法或文本损坏查看异常中的文件路径和行号跳过该文件或先修复源码trace 事件为 0allowed_paths与运行文件前缀不一致在 tracer 中打印实际 filename使用绝对路径或调整路径前缀匹配规则误报过多静态规则没有语义信息查看是否存在动态属性访问增加白名单机制或降低严重级别报告为空所有检查项都未命中先运行--format text查看中间输出检查规则是否注册成功执行目标代码卡死目标代码包含死循环或等待输入设置 subprocess timeout为动态执行增加超时参数排查时遵循这个顺序先确认输入路径再确认文件编码再确认 AST 是否解析成功再确认路径过滤是否正确最后检查规则和报告模块。日志是关键建议在 parser、tracer、rules 三个模块分别打印统计数量。7. 最佳实践与扩展方向7.1 可复用清单审计前检查与发布前检查使用审计工具时下面这份清单可以直接复用。运行审计前检查确认目标目录没有包含 venv、site-packages 等依赖目录。确认目标代码已经保存没有未写入磁盘的改动。确认动态执行文件入口是安全的或者在沙箱中运行。确认报告输出路径可以被覆盖避免误删已有报告。发布审计报告前检查每条 error 级别发现都必须有人工确认。每个 hardcoded-secret 发现必须追溯是否已提交到 git 历史。每条执行轨迹只代表一次运行不能用来证明“所有路径安全”。报告中的行号是否基于最新代码版本。是否已经排除 false positive并在报告中标注说明。7.2 学习环境与生产环境差异学习环境可以快速跑通生产环境审计则需要更严格的控制。维度学习环境快速验证生产环境审计动态执行本地直接运行--run独立容器或 CI 隔离环境运行路径过滤排除 venv 和 site-packages使用严格白名单只允许业务目录规则集内置少量规则按团队规范扩展并与日志/监控联动报告保存终端文本即可归档到对象存储或审计平台关联 MR权限控制本地只读目标仓库只读授权动态执行最小权限超时管理不设或较大超时必须设置超时和资源限制避免恶意代码影响生产环境中最容易被忽略的是权限控制。审计工具本身是高权限 README 执行器如果它可以直接读取仓库密钥、执行目标代码那么审计工具自身也必须纳入安全审查。7.3 扩展方向增量扫描、语义检索和 CI 集成这个轻量工具的下一步扩展可以从三个方向切入。第一增量扫描。当前工具每次都会重新解析整个代码库当代码库达到数万行时会变慢。可以缓存 AST 解析结果只扫描 git diff 中发生变化的文件以及被这些文件直接影响的函数。这样审计速度会更快也更适合提交前检查。第二语义检索。LLM 生成代码经常出现同名函数、重复实现。可以把 AST 转换为函数级别摘要向量用相似度检索找出“看起来不同但功能相同”的代码块帮助审查者发现重复逻辑。第三CI 集成。将审计工具封装成命令行工具后可以在 GitLab CI 或 GitHub Actions 中运行。在 CI 里执行时只输出与本次变更相关的风险项并且把报告作为 MR 附加上下文。这样审查者不需要下载代码库直接在 MR 页面里就能看到风险列表。最终要记住审计工具解决的是“理解成本”和“风险发现”两个问题。它不能替代人脑判断但可以把 LLM 生成代码库从一团黑盒变成一张可导航的地图。早期版本不需要追求大而全先用标准库实现 AST 解析、动态轨迹和规则扫描再根据实际 review 场景逐步补充规则和报告格式这个最小闭环已经足够让审计效率明显提升。