AI Agent静态分析:假阴性列表驱动的评估闭环实践 AI Agent 的测试一直是工程化过程中最模糊的环节。传统单元测试覆盖函数逻辑集成测试覆盖系统交互但 Agent 的决策过程可能涉及多轮工具调用、外部环境反馈和约束遵循错误往往要埋在很深的调用链里才会暴露。Lucin 这类项目提供了一条不同思路用静态分析去看 Agent 的可执行逻辑并主动公开一个假阴性列表让使用者明白当前分析器哪些问题能抓到、哪些抓不到。这种做法比单纯宣称“检测准确率高”更有工程价值也值得单独拆开讲清楚。这篇文章会围绕 AI Agent 静态分析讲清楚为什么需要静态分析、假阴性列表在评估体系里处于什么位置、如何用最小可复现案例搭建一个“规则 测试集 报告”的评估闭环以及在实际项目中怎么排查分析结果不准确的问题。文中示例使用 Python 标准库实现不依赖重型框架便于在本地跑通后迁移到自己的项目。1. 理解 AI Agent 静态分析要解决什么问题1.1 Agent 代码与传统代码的静态分析差异传统静态分析面对的是确定性的程序行为。代码里写了subprocess.run静态分析器就能在语法和调用关系层面看到这个调用再根据参数来源判断是否存在风险。这类分析不运行程序只分析源码、字节码或中间表示所以效率高、结果可复现。到了 AI Agent 场景行为分成两层。一层是确定性代码包括工具函数、状态机、权限判断、日志逻辑另一层是模型决策也就是 LLM 根据用户输入和上下文选择调用哪个工具、生成什么参数。后者在静态分析阶段只能看到它可能调用的工具定义和约束看不到它最终会生成哪条调用链。Lucin 这类工具不试图预测模型输出而是把可以静态化的一方拆出来做检查工具定义是否泄漏了危险能力、工具参数是否使用了不可信输入、权限边界是否存在逻辑漏洞、密钥是否硬编码在代码里。所以不要期待静态分析能抓住所有 Agent 故障。它能做的是把确定性问题提前暴露把动态问题留给运行时评估。理解了这层边界后续的假阴性列表才有意义。1.2 静态分析可以覆盖哪些 Agent 缺陷从实际经验看Agent 项目里至少有以下几类问题适合静态分析缺陷类型示例为什么静态分析适合硬编码密钥API Key 直接写在 Agent 配置或代码里简单模式匹配和流分析就能发现工具权限过宽Agent 工具 schema 允许用户传入任意文件路径工具定义和参数 schema 是结构化数据外部输入未校验用户输入直接拼进 SQL、命令或文件路径可以分析污点来源和校验点危险函数暴露把删除文件、执行命令的工具注册给普通 Agent工具注册表可静态枚举重试与超时缺失外部调用没有超时控制可能阻塞整个循环代码逻辑层面可见错误处理缺失工具抛出异常后 Agent 继续执行错误状态异常路径可以静态检查日志过度输出日志里记录了完整用户输入或敏感上下文日志调用点与上下文变量可分析状态机缺失工具调用顺序没有状态约束状态定义和流转规则可检查举一个最小例子。某个 Agent 暴露了一个“删除文件”工具用户发起对话后模型决定调用该工具。模型是否真的调用静态分析管不了但工具是否存在必要的最小权限校验、是否校验参数落在白名单目录是完全可以从代码层检查的。Lucin 这类工具的价值就在这里把 Agent 自身可控的安全边界先守住。1.3 为什么假阴性列表是关键设计假设你开发了一个 Agent 静态分析器怎么证明它有用常见做法是给一个检测率数字。但数字很容易被数据集选择影响如果测试集里全是容易发现的硬编码密钥检测率自然很高。真实环境里的问题往往被漏掉而且你根本不知道它被漏掉了。Lucin 的做法是公布一个假阴性列表也就是明确指出当前分析器有哪些已知缺陷没有检测出来。这是一件反直觉但很正确的事。它产生三个直接效果第一用户知道工具的边界不会因为“静态分析过了”而产生虚假安全感。第二维护者有了明确的迭代路线每个假阴性都可以从列表变成新的测试用例防止同一个漏报再次发生。第三评估过程变得可审计工具做得怎么样不是一句宣传语而是一份可以逐条核对的清单。对工程团队来说假阴性列表就是一个风险登记册。它记录的不仅是工具的不足也是 Agent 系统当前仍存在的、未覆盖的安全或正确性风险。2. 用“假阴性列表”倒逼评估体系2.1 先明确评估指标建立假阴性列表之前先要把评估指标说清楚。静态分析结果和人工标注对比后会出现四种情况实际有缺陷实际无缺陷分析器报出真阳性 TP分析器报出假阳性 FP分析器没报假阴性 FN分析器没报真阴性 TN基于这四类值通常计算三个指标精确率 Precision TP / (TP FP)报出的问题里有多少是真问题。这个指标低说明误报多。召回率 Recall TP / (TP FN)真实问题里有多少被找出来了。这个指标低说明漏报多。F1 2 * Precision * Recall / (Precision Recall)综合分数避免只看单边。对 Agent 安全分析来说精确率和召回率都重要但场景不同侧重点不一样。如果分析器在 CI 里每次提交都跑误报太多会让开发者把结果当成噪声忽略最终形同虚设。如果分析器用于上线前安全审计漏报比误报更致命因为漏报意味着真实风险被静默放过。这里要引入一个关键数字假阴性率 FN / (FN TP)。它和召回率互补召回率 1 - 假阴性率。假阴性列表本质上就是所有 FN 样本的持续记录。2.2 测试集怎么设计要让假阴性列表有说服力测试集不能偷懒。常见的错误是把所有样本都设成“有明显问题的代码”因为这样只考验分析器能不能发现不考验它会不会误判。推荐把测试集分成两类正样本包含真实缺陷的 Agent 代码片段每条都有缺陷类型、缺陷位置、期望命中规则。负样本功能上等价、但已经修复或使用安全写法的代码期望分析器不报警。正样本负责评估召回率和假阴性率负样本负责评估精确率。只有两边都有指标才完整。更接近真实场景的做法是再增加第三类历史回归样本。把曾经漏报过的问题、曾经误报过的场景都固化到测试集里。这样每次规则更新都可以通过跑一遍测试集知道有没有变好、有没有把原来的正确结果弄坏。2.3 假阴性列表的格式和审计流假阴性列表建议结构化存储而不是写在 release notes 的散文段落里。每个条目至少包含这些字段case_id唯一标识方便在测试集中关联。sample_path对应哪个样本文件。rule_id哪条规则应该命中但没命中。defect_type缺陷类型例如“路径穿越”“命令注入”。reason当前为什么漏报例如“缺少数据流分析无法跟踪跨函数参数”。statusopen / fixed / accepted_risk。created_at、updated_at时间信息方便审计。格式可以用 JSON 或 YAML。YAML 看起来更接近人工维护JSON 更容易被工具读取。Lucin 项目公开假阴性列表的内核就是这种审计流规则更新后重跑测试集重新生成列表逐条确认哪些新增、哪些关闭、哪些仍然接受风险。列表不是一次性的而是持续产物。3. 从零搭建一个最小可复现的 Agent 静态分析评估案例3.1 环境准备这里用 Python 从零实现一个最小静态分析规则和评估器目的是把“规则 测试集 假阴性报告”的最小闭环跑通。选 Python 是因为ast标准库足够处理语法树不需要额外安装解析器。学习环境建议依赖版本建议用途Python3.10 或更高使用标准库 ast、pathlib、typingpytest任意较新版本便于后续把测试集作为单测执行git任意版本管理规则、样本和报告基线这个组合在 Windows、macOS、Linux 上都能跑。如果你要分析的是 Java、TypeScript 或其他语言的 Agent 项目思路一样只需要把解析层换成对应语言的 AST 工具。下面示例的重点是评估闭环不是语法解析本身。生产环境还需要额外考虑几件事分析任务的并发调度、仓库级增量扫描、与 CI 平台集成、报告基线存储、权限控制。这些放到第 5 和第 6 节讨论。3.2 示例 Agent 代码结构先构造两个样本。一个是有缺陷的 Agent 工具函数另一个是修复后的安全版本。sample_agent/unsafe_agent.pyimport subprocess from typing import Dict def handle_delete_request(request: Dict[str, str]) - str: file_path request.get(file_path, ) # 直接执行用户提供的路径存在路径穿越和任意文件删除风险 result subprocess.run( [rm, -rf, file_path], capture_outputTrue, textTrue, checkFalse, ) return result.stdout这个函数的问题在于file_path直接来自外部 request没有校验它是否存在于允许删除的目录也没有校验路径是否包含..或绝对路径。规则应该把subprocess.run的参数来源识别出来。sample_agent/safe_agent.pyimport os import subprocess from typing import Dict ALLOWED_BASE_DIR /srv/agent_data def _safe_path(relative_path: str) - str: target os.path.abspath(os.path.join(ALLOWED_BASE_DIR, relative_path)) if os.path.commonpath([target, ALLOWED_BASE_DIR]) ! ALLOWED_BASE_DIR: raise ValueError(invalid path) return target def handle_delete_request_safe(request: Dict[str, str]) - str: relative_path request.get(file_path, ) file_path _safe_path(relative_path) if not os.path.exists(file_path): return file not found result subprocess.run( [rm, -rf, file_path], capture_outputTrue, textTrue, checkFalse, ) return result.stdout安全版本增加了_safe_path白名单校验。即使到达subprocess.run路径也已经被限制在允许目录内。这个样本是负样本期望规则不报警。这里要注意一点安全的代码不是绝对不能调用危险函数而是调用之前有足够强的边界校验。静态分析规则如果只是看到subprocess.run就报警就会把安全版本也标记成缺陷产生误报。3.3 编写静态分析规则用 AST 检查工具函数中的危险调用现在写一条最小规则检查函数体内是否调用subprocess.run并检查调用参数中是否有变量来源于函数入参同时没有经过白名单校验。为了保持示例简短这里用一种相对保守的策略函数体内如果出现调用subprocess.run且该函数参数直接出现在调用处附近的变量名中就报警。这个策略在真实场景里过于粗糙但它足以演示 AST 如何工作也足以让后面出现一个假阴性样本。rules/subprocess_rule.pyimport ast from typing import List, Tuple class SubprocessRule: rule_id agent-tool-subprocess-001 danger_funcs {subprocess.run, subprocess.call, os.system} def check_file(self, code: str) - List[Tuple[int, str]]: findings [] tree ast.parse(code) for node in ast.walk(tree): if not isinstance(node, ast.Call): continue func_name self._full_func_name(node.func) if func_name not in self.danger_funcs: continue arg_names self._collect_arg_names(node) # 如果危险调用的参数里包含函数入参变量则报告 func_def self._enclosing_function(node) if func_def is None: continue params {arg.arg for arg in func_def.args.args} if params arg_names: findings.append((node.lineno, f危险调用 {func_name} 的输入可能来自未校验参数)) return findings def _full_func_name(self, node: ast.AST) - str: if isinstance(node, ast.Attribute): parts [node.attr] current node.value while isinstance(current, ast.Attribute): parts.append(current.attr) current current.value if isinstance(current, ast.Name): parts.append(current.id) return ..join(reversed(parts)) if isinstance(node, ast.Name): return node.id return def _collect_arg_names(self, node: ast.Call) - set: names set() for arg in node.args: if isinstance(arg, ast.Name): names.add(arg.id) for kw in node.keywords: if isinstance(kw.value, ast.Name): names.add(kw.value.id) return names def _enclosing_function(self, node: ast.AST): for parent in ast.walk(node): pass # 简化实现向上查找第一个 FunctionDef current node while hasattr(current, parent): current current.parent return self._find_function_def(node) def _find_function_def(self, node: ast.AST): # 为示例简单处理实际项目需要维护 parent 引用 return None上面示例里_find_function_def没有真正实现因为标准库 ast 默认不维护 parent 引用。为了让规则可用需要先给 AST 节点补充 parent 关系。下面给出可运行的合并版本。analysis/mini_analyzer.pyimport ast from dataclasses import dataclass dataclass class Finding: rule_id: str line: int message: str class ParentCollector(ast.NodeVisitor): def __init__(self): self.parent_map {} def visit(self, node): for child in ast.iter_child_nodes(node): self.parent_map[child] node self.visit(child) def build_parent_map(tree): collector ParentCollector() collector.visit(tree) return collector.parent_map def enclosing_function(node, parent_map): current node while current in parent_map: parent parent_map[current] if isinstance(parent, ast.FunctionDef): return parent current parent return None def check_subprocess_with_unchecked_params(code_text): tree ast.parse(code_text) parent_map build_parent_map(tree) findings [] for node in ast.walk(tree): if not isinstance(node, ast.Call): continue func_name if isinstance(node.func, ast.Attribute): func_name node.func.attr if func_name not in {run, call}: continue func_def enclosing_function(node, parent_map) if func_def is None: continue params {arg.arg for arg in func_def.args.args} call_arg_names set() for arg in node.args: if isinstance(arg, ast.Name): call_arg_names.add(arg.id) for kw in node.keywords: if isinstance(kw.value, ast.Name): call_arg_names.add(kw.value.id) if params call_arg_names: findings.append( Finding( rule_idagent-tool-subprocess-001, linenode.lineno, messagesubprocess 调用参数可能来自未校验的函数入参, ) ) return findings这个简化规则存在一个明显问题它只检查参数名是否直接等于函数入参名。安全样本里file_path经过_safe_path处理后传入但变量名仍然叫file_path所以规则会误报。这正是规则设计里常见的权衡。要降低误报就需要识别参数是否经过校验函数。后面会说明如何改进。3.4 编写测试集运行器和报告生成器评估闭环需要比较规则输出和人工标注。人工标注放在golden.yaml里格式如下version: 1 cases: unsafe_agent.py: expected: true defect: path traversal safe_agent.py: expected: false defect: none然后把规则输出转成 case 级别的“是否报警”再与 expected 对比生成 TP、FP、FN、TN 和假阴性列表。analyze_repo.pyimport argparse import ast import json import sys from pathlib import Path import yaml from analysis.mini_analyzer import check_subprocess_with_unchecked_params def analyze_file(file_path: Path): code_text file_path.read_text(encodingutf-8) findings check_subprocess_with_unchecked_params(code_text) return findings def main(): parser argparse.ArgumentParser() parser.add_argument(--code-dir, requiredTrue) parser.add_argument(--golden, requiredTrue) parser.add_argument(--output, requiredTrue) args parser.parse_args() with open(args.golden, encodingutf-8) as fp: golden yaml.safe_load(fp) tp [] fp [] tn [] fn [] for code_file in sorted(Path(args.code_dir).glob(*.py)): findings analyze_file(code_file) actual bool(findings) expected golden[cases][code_file.name][expected] if actual and expected: tp.append({file: code_file.name, findings: [f.__dict__ for f in findings]}) elif actual and not expected: fp.append({file: code_file.name, findings: [f.__dict__ for f in findings]}) elif not actual and not expected: tn.append({file: code_file.name}) else: fn.append( { file: code_file.name, expected_defect: golden[cases][code_file.name].get(defect), } ) precision len(tp) / (len(tp) len(fp)) if tp or fp else 0 recall len(tp) / (len(tp) len(fn)) if tp or fn else 0 f1 2 * precision * recall / (precision recall) if precision recall else 0 fn_rate len(fn) / (len(tp) len(fn)) if tp or fn else 0 report { summary: { true_positive: len(tp), false_positive: len(fp), true_negative: len(tn), false_negative: len(fn), precision: precision, recall: recall, f1: f1, false_negative_rate: fn_rate, }, tp: tp, fp: fp, tn: tn, fn: fn, } with open(args.output, w, encodingutf-8) as fp: json.dump(report, fp, ensure_asciiFalse, indent2) print(f分析完成报告已生成{args.output}) print(json.dumps(report[summary], ensure_asciiFalse, indent2)) if fn: print(\n当前假阴性列表) for case in fn: print(f- {case[file]}: {case[expected_defect]}) if __name__ __main__: sys.exit(main())这里引入pyyaml作为 YAML 解析依赖。如果不想安装额外依赖也可以把 golden 文件改成 JSON。关键是结构化的标注文件必须存在它是评估闭环的“标准答案”。4. 运行验证和报告解读4.1 运行结果预期在项目根目录执行python analyze_repo.py \ --code-dir sample_agent \ --golden golden.yaml \ --output report.json由于当前规则把安全样本也当成报警预期输出会包含一个 False Positive{ summary: { true_positive: 1, false_positive: 1, true_negative: 0, false_negative: 0, precision: 0.5, recall: 1.0, f1: 0.6666666666666666, false_negative_rate: 0.0 } }这说明一个问题召回率是 100%但精确率只有 50%。规则把安全版本误判成了风险代码。在实际工程里这种结果虽然不会漏掉风险但会让开发者在大量误报中失去耐心。接下来给测试集增加一个真正会漏报的样本unsafe_agent_indirect.pyimport subprocess from typing import Dict def handle_delete_request_indirect(request: Dict[str, str]) - str: file_path request.get(file_path, ) command [rm, -rf, file_path] result subprocess.run(command, capture_outputTrue, textTrue) return result.stdout这个样本同样有风险但危险调用的参数是command不是直接来自入参file_path的变量。当前规则只检查直接参数名因此会漏掉。把它加入golden.yaml后再跑一次python analyze_repo.py \ --code-dir sample_agent \ --golden golden.yaml \ --output report.json结果会多出一个 False Negative即假阴性。report.json的fn列表会包含{ file: unsafe_agent_indirect.py, expected_defect: command injection via path }假阴性率也会变成非零值。到这里你已经把 Lucin 公布假阴性列表的核心机制跑通不是隐藏漏报而是把它明确输出纳入审计。4.2 指标解读要结合场景一组数字不能说明所有问题。实际使用时要回答三个问题问题关注指标结果差时怎么办分析器会不会耽误开发者Precision太高就增加约束条件、补充数据流分析、减少模板匹配分析器能不能守住安全底线Recall / FN rate太低就增加样本、补规则、引入污点分析整体是否可用F1看具体偏向安全审计偏向 recall日常 CI 偏向 precision针对上面示例要同时消除误报和漏报需要升级规则跟踪变量赋值关系识别command是从file_path构造出来的同时识别_safe_path这类校验函数对参数的净化。这已经不是 50 行代码能做的需要引入数据流分析和校验函数模型也就是真实静态分析工具的核心工作。4.3 学习环境与生产环境的差异本地跑通的最小案例只能说明思路。生产环境要做的事情更多维度学习环境生产环境分析范围一个目录整个仓库、MR 变更集、依赖来源规则复杂度AST 直接匹配数据流分析、污点标注、调用上下文标注维护手工写几个样本缺陷报告、历史事故、评审记录驱动执行时机手动执行CI 提交阶段、MR 阶段、定时全量扫描报告处理终端输出与缺陷管理平台对接、CMDB 关联假阴性管理人工翻 JSON基线对比、新增 FN 审批、自动通知性能要求毫秒级分钟级内完成完整仓库扫描从零开始搭这类系统不建议一开始就把目标定成“全量复杂分析”。先跑通一个规则、一份标注、一个报告再逐步增加规则和样本。Lucin 公开假阴性列表的意义也在这里它能让你清楚看到每一步改进到底解决了哪些漏报又新增了哪些新的盲区。5. 常见问题与排查链路5.1 修复了规则假阴性列表为什么没有变化现象修改了mini_analyzer.py中的规则重新运行后发现假阴性列表和上次一模一样。排查顺序确认命令是否重新执行而不是读取旧的report.json。确认样本文件路径是否正确glob(*.py)只扫描code-dir根目录不递归子目录。确认 golden 标注文件里是否新增了对应样本。确认规则的过滤条件是否生效。例如func_name只取attr对于import subprocess后直接写subprocess.run(...)的场景node.func.attr是run可以命中但如果写的是run(...)规则可能命不中。在规则里加临时打印输出 AST 节点结构确认新样本被遍历到了。如果规则确实命中了新样本但假阴性列表仍然存在那么很可能是因为新样本同时被判定为“确实有缺陷但规则没有命中”也就是旧的规则更新只解决了部分路径没有覆盖新增的调用模式。这时候要回到样本逐个调用点分析。表格整理排查链路现象可能原因检查方式处理建议假阴性列表不变读取了旧报告查看输出文件 mtime删除旧 report.json 后重跑假阴性列表不变样本未被扫描打印目录列表递归扫描或调整 code-dir假阴性列表不变规则未覆盖新的语法形态输出 AST 节点信息增加变量赋值跟踪或解析 Attribute 完整路径假阴性列表变了但不对golden 标注过期人工复核样本修正 expected 值并记录变更原因5.2 结果里大量误报怎么办现象规则把大量安全代码标记为风险代码。这在静态分析工具落地时最常见原因通常是检测条件太宽。举例来说只要看到subprocess.run就报警等于没做分析。真实规则必须回答三个问题参数是否来自不可信入口。参数路径上是否经过校验函数。校验函数是否能保证参数安全。调整方向提高触发条件只有危险函数参数与函数入参存在数据流关联时才报警。引入校验函数知识库把_safe_path、sanitize_input、escape_shell这类函数标记为净化节点。用负样本测试集验证每减少一次误报都要确认召回率没有下降。不要把误报率压到零作为目标。完全消灭误报的结果往往是规则过弱真正的问题也发现不了。合理做法是设置阈值关键规则允许少量误报但必须保持在可解释、可快速关闭的范围内。5.3 Agent 的动态行为无法被静态分析怎么办现象某些缺陷依赖 LLM 的运行时决策。例如模型在特定 prompt 下选择了危险工具这在代码里根本看不到。这不是静态分析的失败而是评估手段错位。正确补充方式有两类运行时回放评估记录模型接收到的输入、工具调用序列、返回结果检查是否符合预期。模拟工具失败在评估环境中让某个工具返回异常观察 Agent 是否进入错误分支。静态分析负责可静态化的部分运行时评估负责动态行为两者合起来才是一个完整的 Agent 评测体系。Lucin 的假阴性列表如果出现在运行时评估中也会非常有用它可以让团队知道哪些交互序列没有被测试覆盖。5.4 如何确认分析器没有“作弊”给分析器做评估时最隐蔽的问题是数据泄漏。如果测试集样本文件名、函数名、注释里都写着“unsafe”规则直接匹配路径或名称就能“猜对”这种结果没有实际价值。防范方式测试集不要固定放在规则可以硬编码路径的位置。尽量混淆文件名让规则只能通过真实语法特征判断。在测试集里加入与文件名提示相反的样本例如safe_looking_unsafe.py。每次更新测试集时不要只把旧报告复制到新版本重新人工复核。可以在报告里增加一个字段记录规则命中依据例如命中的 AST 行号和表达式方便审计。6. 从 Lucin 的发布方式里能学到的最佳实践6.1 假阴性列表的发布格式建议公开的假阴性列表要做到可读、可跟踪、可争议。建议采用如下结构schema_version: 1.0 analyzer_version: 0.3.2 generated_at: 2025-06-15T10:00:00Z summary: total_cases: 128 false_negative_count: 7 false_negative_rate: 0.054 cases: - id: FN-2025-001 sample_path: tests/fixtures/agent_tool_path_confusion.py rule_id: agent-tool-subprocess-001 defect_type: path traversal reason: 规则只追踪直接调用参数未处理中间列表变量 status: open created_at: 2025-06-10 - id: FN-2025-002 sample_path: tests/fixtures/agent_tool_after_sanitizer_missed.py rule_id: agent-tool-subprocess-002 defect_type: command injection reason: 校验函数知识库缺少对 os.path.commonpath 的识别 status: fixed fixed_version: 0.4.0 updated_at: 2025-06-14额外要说明两点一是status: accepted_risk的条目必须写明为什么接受风险例如“该路径只能在本地开发环境触发生产环境没有对应工具注册”二是列表要能支持 diff每次发布时给出新增和关闭的条目否则大家只会看到总数字看不到改进过程。6.2 把假阴性清单接进 CI 质量门禁假阴性列表不能只躺在仓库里它应该成为 CI 的输入。一个可行的发布流程是代码提交触发全量静态分析。分析结果与基线假阴性列表对比。只有满足条件才允许合并没有新增已命中的严重问题新增假阴性需要单独审批修复旧假阴性的记录自动更新。每次发布都导出false_negatives.yaml作为构建产物。这样做的效果是每一条已知漏报都有负责人和关闭条件不会无限积累成一本没人看的册子。对一个静态分析工具来说持续维护假阴性列表比临时提高一个检测率数字更能建立信任。6.3 静态分析与 Agent Evals 结合扩展Lucin 关注的是静态分析但它的方法论可以迁移到更广的 Agent 评估体系中。推荐从以下几个方向扩展把假阴性样本转成 eval 回归用例漏报的代码样本可以变成自动化的单测验证规则修复后行为符合预期。把工具定义也纳入评估不只分析代码还要分析工具 schema 是否泄露了过强能力是否缺少参数约束。把 prompt 模板纳入静态检查检查变量是否被插值进系统提示词可能存在格式串注入。运行时 evals 与静态分析结果映射把静态报告中的函数和运行时 trace 中的工具调用关联形成“代码风险到线上行为”的闭环。真正有意义的 Agent 评测不是单一工具能完成的。静态分析给出静态边界行为测试给出动态结果假阴性列表把两者之间尚未解决的空白清楚地标记出来。6.4 给团队落地时的最小清单如果你要在团队里引入类似 Lucin 的实践可以先做这六件事选择一个真实 Agent 项目列出它的工具函数清单。编写三条最关心的静态规则优先覆盖硬编码密钥、危险工具参数、未校验外部输入。整理 20 到 30 个正样本和负样本先小规模验证指标。定义假阴性列表的 YAML 格式把第一轮漏报全部登记。把分析器接入 CI并设置“新增假阴性需要审批”的门禁。每两周重跑测试集更新列表把修复记录同步到团队周会。这套做法不需要一开始就做得很重。最小闭环的价值在于它能让你在看到第一份假阴性列表时立刻知道自己的 Agent 安全分析处在什么水平哪些问题是规则能守住的哪些必须用运行时评测补上。