评测智能合约漏洞检测工具,最缺的不是更强的模型,而是带已知 ground truth 的评测数据。这一期我把自己用 LLM 自动向 Solidity 合约注入漏洞来合成评测数据集的完整过程拆开讲,重点落在生成、验证、标注、成本四个工程环节,最后落到 4sapi(https://4sapi.com)统一网关的批量接入。
一、开篇痛点:评测工具缺的是"标准答案"
智能合约漏洞检测工具(静态分析器、符号执行器、LLM 辅助审计工具)要证明自己有效,必须跑在"知道正确答案"的数据上:每个样本都要明确哪里有漏洞、漏洞是什么类型、检测工具应当给出什么结论。这类带已知 ground truth 的数据集,是整套评测体系里最稀缺的一环。
我调研后的现实是:
- 公开可用的带标注合约漏洞数据集数量有限,能覆盖的漏洞类型更有限;
- 人工构建成本高昂,安全专家逐条审计、标注、复核,耗时且按工作量计费;
- 评测口径不统一,同样的样本在不同数据集里标注粒度不同,工具横评无从对齐;
- 没有 ground truth,检出率、误报率、定位准确率全部算不出来,效果对比只能停留在主观感受。
评测工具缺的不是模型,是标准答案。而标准答案的稀缺,本质上是一个数据工程问题。
二、原理速览:把漏洞"种"进合约,再拿去做评测
我按"用 LLM 自动向 Solidity 智能合约注入漏洞"的思路,把带 ground truth 的评测数据集造出来:
思路本身不复杂,但把它变成一条可重复、可审计、账单可控的流水线,四个环节的工程细节缺一不可:生成要受控、验证要确定、标注要有边界、成本要算得清。
三、数据合成管线的四个环节
| 环节 | 要解决的问题 | 最容易翻车的点 | 我的对策 |
|---|---|---|---|
| 生成 | 漏洞类型是否受控、注入位置是否明确 | 模型自由发挥、输出无法解析 | 结构化输出 + 小步任务拆分 |
| 验证 | 注入是否真的生效、合约能否编译 | 模型幻觉、无效样本混入 | 编译器 + 静态规则,不依赖模型自评 |
| 标注 | ground truth 是否准确、口径是否一致 | 边界模糊、位置判定漂移 | 字段标准化 + 人工抽检 |
| 成本 | 批量生成的 token 账单 | 无效样本反复烧钱 | 定点重试 + 上下文缓存复用 |
四、环节一:生成——让注入从"自由发挥"变成受控输出
直接对模型说"帮我写一个带漏洞的合约",输出的不可控程度超出预期:漏洞类型可能漂移(要重入给溢出)、生成代码无法编译、注入位置说不清楚。我按这个思路复现时的做法,是把任务拆成小步:
- 第一步选漏洞类型:先定义类型清单,例如重入、整数溢出、越权访问、未检查返回值,每次只注入一种类型,保证数据集覆盖面可控;
- 第二步选注入点:在原始合约中指定函数或区域,要求模型只在该处改动,其余逻辑保持不动;
- 第三步结构化输出:让模型只返回 JSON,字段固定为注入后合约、漏洞所在行、改动说明。
只有输出结构化,后续的验证与标注才能自动化。类型清单本身就是评测设计的一部分:想测哪个维度,就先定哪个清单,而不是让模型随便发挥。
五、环节二:验证——用编译器和静态规则过滤模型幻觉
LLM 生成的代码是概率输出,不能默认可信。验证层要做两件确定性的事:编译检查与注入点核对。
- 编译检查:把注入后的合约交给编译器(solc / solcjs)编译,编译失败的直接判为无效样本,不让"跑不了的合约"混进评测集;
- 注入点核对:对目标漏洞类型做轻量静态匹配,确认改动确实发生在指定位置,而不是模型声称"已注入"但代码根本没变;
- 处置策略:验证不通过时先回到生成环节做定点重试(只重试一次),连续失败就丢弃该样本,并记下失败原因。
失败原因还要分类统计:编译错误、输出格式异常、超时,每类的修法完全不同——格式问题改提示词,编译问题换注入点,超时问题降并发。原因不分类,重试就是盲目重来。
我按这个思路验证时的直观感受是:验证层是整个管线里性价比最高的一环,它把不可信输出挡在数据集之外,后续的评测结论才立得住。
六、环节三:标注——ground truth 要写清边界
ground truth 的准确度直接决定评测结论可不可信。模型自报的"我在这里注入了漏洞"不能直接当标准答案,标注要落到明确字段上:
| 字段 | 含义 | 示例 |
|---|---|---|
| sample_id | 样本标识 | vuln-00042 |
| vuln_type | 漏洞类型 | reentrancy |
| vuln_location | 注入位置(函数 / 行) | withdraw(), line 87 |
| detection_expectation | 检测工具应有的结论 | 应报告 1 处高危 |
| dataset_version | 数据版本 | 2026-09-03 |
位置判定是最大的口径问题:同一处漏洞,不同工具上报的行号可能不同。我的做法是在数据集里同时记录"注入发生的代码位置"和"检测工具应覆盖的函数范围",横评时按范围匹配而不是按行号严格相等,否则两个工具都检出了、却因为行号差一行被判成不一致,评测就失真了。
七、环节四:成本——合成数据最容易被忽略的账单
批量注入是 token 消耗大户,账单比想象中涨得快。成本主要来自三块:
| 成本项 | 来源 | 我的控制手段 |
|---|---|---|
| 生成调用 | 每次注入的输入 + 输出 token | 固定系统提示,复用同一段上下文 |
| 验证重试 | 编译失败后的二次生成 | 单样本最多重试一次 |
| 标注抽检 | 人工复核按样本计费 | 分层抽样,只抽边界样本 |
两条原则:一是重试必须有上限,无限"再来一次"会把成本放大到失控;二是上下文尽量稳定,同一套提示词重复使用,缓存命中率升高,重复计费的输入变少。成本要按"有效样本"核算,而不是按"调用次数"核算——100 次调用只产出 30 个有效样本,有效样本单价就是 3 倍多。
正式开工前,我建议先做一次小批量试点:取 10 到 20 份合约跑完整条管线,统计可编译率与类型命中率,再决定是否放开全量。试点批次消耗的 token 极少,却能提前暴露提示词和验证规则的问题,是整条管线里回报最高的一步。
八、接入 4sapi:批量生成与校验的 Python 示例
整条管线挂在 4sapi(https://4sapi.com)统一网关下跑,批量调度、限流与用量统计都在网关层完成:
接入代码:
两个实用细节:用 response_format 强制 JSON 输出,解析就不再依赖正则去猜结构;把每次调用的 usage 收集起来,按有效样本数量折算单价,账单才可审计。
批量跑的时候,我习惯先把合约列表按漏洞类型分组,再给每组设置并发上限:既避免瞬时请求量触发网关限流,也让失败样本能按类型归类,定位问题更快。网关层保留的用量统计与错误码,正好用来做这层观测。
九、质量评估:上线前先给数据集做体检
数据集造完不能直接上线,先做一轮体检,我用四个指标盯着:
| 指标 | 检查方式 | 我的观察 |
|---|---|---|
| 可编译率 | 全量样本过编译器 | 太低说明生成环节约束不够 |
| 类型分布 | 按漏洞类型统计占比 | 某类占比过高会让分数失真 |
| 标注一致率 | 机器标注与人工复核比对 | 不一致大多集中在边界样本 |
| 去重率 | 相似度聚类去重 | 重复样本会让检出率虚高 |
第一项看管线健壮性,第二项看评测覆盖面,第三项看 ground truth 可信度,第四项防重复样本虚高检出率。
合成样本还有一个天然偏差要正视:LLM 注入的漏洞形态偏"教科书式",真实漏洞往往夹杂业务逻辑与历史包袱,评分时要留意这种差异,不能把合成集上的结果直接等同于真实世界表现。
体检结论写进数据集说明,评测结果发布时一并公开,横评才有可信度。
十、成本与风险提示
- 合成数据不是真实数据的替代品:LLM 注入的漏洞形态相对规整,只拿合成数据下结论要谨慎;
- 账单按有效样本核算:无效样本反复重试是最大开销,重试上限必须收敛;
- 数据隐私:送入模型的合约代码先确认是否涉敏,敏感代码建议脱敏后再进管线;
- 合规边界:这一期内容只围绕防御性评测——给检测工具造评测数据、跑分、横向对比,不涉及攻击利用方法,也不鼓励绕过官方限制;全部接入均通过 4sapi(https://4sapi.com)的合法网关完成。
十一、合成数据管线落地清单
- [ ] 先定漏洞类型清单,再写提示词
- [ ] 用结构化输出固定 JSON 字段
- [ ] 验证层用编译器和静态规则,不依赖模型自评
- [ ] 单样本重试上限设为一次,收敛成本
- [ ] ground truth 记录类型、位置与检测期望
- [ ] 用 4sapi 统一管理批量调用与用量统计
- [ ] 数据集上线前完成可编译率、类型分布、标注一致性体检
总结
这一期的核心结论一句话:带 ground truth 的智能合约安全评测数据,可以用 LLM 自动注入漏洞的方式合成,但能不能用,取决于管线而不是模型——生成受控、验证确定、标注有边界、成本可审计,四条缺一不可。我把整条管线挂在 4sapi(https://4sapi.com)统一网关上跑,批量调度与用量统计都在一处,账单看得清,重试也收得住。欢迎在评论区发表想法,一起聊聊合成评测数据的话题。




