返回博客

LLM注入合约漏洞合成安全评测数据集:生成验证标注全流程

人工智能8115
LLM注入合约漏洞合成安全评测数据集:生成验证标注全流程

评测智能合约漏洞检测工具,最缺的不是更强的模型,而是带已知 ground truth 的评测数据。这一期我把自己用 LLM 自动向 Solidity 合约注入漏洞来合成评测数据集的完整过程拆开讲,重点落在生成、验证、标注、成本四个工程环节,最后落到 4sapi(https://4sapi.com)统一网关的批量接入。

一、开篇痛点:评测工具缺的是"标准答案"

智能合约漏洞检测工具(静态分析器、符号执行器、LLM 辅助审计工具)要证明自己有效,必须跑在"知道正确答案"的数据上:每个样本都要明确哪里有漏洞、漏洞是什么类型、检测工具应当给出什么结论。这类带已知 ground truth 的数据集,是整套评测体系里最稀缺的一环。

我调研后的现实是:

评测工具缺的不是模型,是标准答案。而标准答案的稀缺,本质上是一个数据工程问题。

二、原理速览:把漏洞"种"进合约,再拿去做评测

我按"用 LLM 自动向 Solidity 智能合约注入漏洞"的思路,把带 ground truth 的评测数据集造出来:

text
干净合约样本

    v
漏洞注入生成(LLM 批量调用,输出注入后合约 + 目标位置)

    v
编译与静态验证(逐条确认注入生效、合约可编译)

    v
ground truth 标注(漏洞类型 + 位置 + 检测期望)

    v
评测数据集(喂给漏洞检测工具打分)

思路本身不复杂,但把它变成一条可重复、可审计、账单可控的流水线,四个环节的工程细节缺一不可:生成要受控、验证要确定、标注要有边界、成本要算得清。

三、数据合成管线的四个环节

环节要解决的问题最容易翻车的点我的对策
生成漏洞类型是否受控、注入位置是否明确模型自由发挥、输出无法解析结构化输出 + 小步任务拆分
验证注入是否真的生效、合约能否编译模型幻觉、无效样本混入编译器 + 静态规则,不依赖模型自评
标注ground truth 是否准确、口径是否一致边界模糊、位置判定漂移字段标准化 + 人工抽检
成本批量生成的 token 账单无效样本反复烧钱定点重试 + 上下文缓存复用

四、环节一:生成——让注入从"自由发挥"变成受控输出

直接对模型说"帮我写一个带漏洞的合约",输出的不可控程度超出预期:漏洞类型可能漂移(要重入给溢出)、生成代码无法编译、注入位置说不清楚。我按这个思路复现时的做法,是把任务拆成小步:

只有输出结构化,后续的验证与标注才能自动化。类型清单本身就是评测设计的一部分:想测哪个维度,就先定哪个清单,而不是让模型随便发挥。

五、环节二:验证——用编译器和静态规则过滤模型幻觉

LLM 生成的代码是概率输出,不能默认可信。验证层要做两件确定性的事:编译检查与注入点核对。

失败原因还要分类统计:编译错误、输出格式异常、超时,每类的修法完全不同——格式问题改提示词,编译问题换注入点,超时问题降并发。原因不分类,重试就是盲目重来。

我按这个思路验证时的直观感受是:验证层是整个管线里性价比最高的一环,它把不可信输出挡在数据集之外,后续的评测结论才立得住。

六、环节三:标注——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)统一网关下跑,批量调度、限流与用量统计都在网关层完成:

text
我的合成管线

    v
4sapi 网关(https://4sapi.com)
    │  统一鉴权 / 限流 / 批量调度 / 用量统计
    v
LLM 接口(OpenAI 兼容格式)

接入代码:

python
import json
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["4SAPI_API_KEY"],
    base_url="https://4sapi.com/v1",  # 统一网关地址
)

VULN_TYPES = ["reentrancy", "integer-overflow", "access-control", "unchecked-call"]


def inject_one(vuln_type, source):
    """向一份 Solidity 源码注入指定类型漏洞,返回结构化 JSON。"""
    prompt = (
        "下面是一份 Solidity 合约源码,请在其中注入一处 "
        f"'{vuln_type}' 类型漏洞,其余逻辑保持不动。\n"
        "只输出 JSON,字段固定为:"
        '{"injected_contract": "...", "vuln_line": 数字, "change_reason": "..."}\n'
        f"合约源码:\n{source}"
    )
    resp = client.chat.completions.create(
        model="gpt-4o",  # 按网关可用模型调整
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"},
        temperature=0.2,
    )
    return json.loads(resp.choices[0].message.content), resp.usage


def verify_contract(source: str) -> bool:
    """本地编译校验:编译通过才算有效样本。"""
    # 实际调用 solc / solcjs 编译,这里仅作示意
    return True


def build_sample(vuln_type, source):
    raw, usage = inject_one(vuln_type, source)
    if not verify_contract(raw.get("injected_contract", "")):
        return None, usage  # 编译失败,丢弃该样本
    return {
        "source": source,
        "injected_contract": raw["injected_contract"],
        "vuln_type": vuln_type,
        "vuln_line": raw["vuln_line"],
    }, usage


def track_cost(usages):
    """把逐次调用的 token 用量汇总成账单。"""
    return {
        "prompt_tokens": sum(u.prompt_tokens for u in usages),
        "completion_tokens": sum(u.completion_tokens for u in usages),
        "total_tokens": sum(u.total_tokens for u in usages),
    }

两个实用细节:用 response_format 强制 JSON 输出,解析就不再依赖正则去猜结构;把每次调用的 usage 收集起来,按有效样本数量折算单价,账单才可审计。

批量跑的时候,我习惯先把合约列表按漏洞类型分组,再给每组设置并发上限:既避免瞬时请求量触发网关限流,也让失败样本能按类型归类,定位问题更快。网关层保留的用量统计与错误码,正好用来做这层观测。

九、质量评估:上线前先给数据集做体检

数据集造完不能直接上线,先做一轮体检,我用四个指标盯着:

指标检查方式我的观察
可编译率全量样本过编译器太低说明生成环节约束不够
类型分布按漏洞类型统计占比某类占比过高会让分数失真
标注一致率机器标注与人工复核比对不一致大多集中在边界样本
去重率相似度聚类去重重复样本会让检出率虚高

第一项看管线健壮性,第二项看评测覆盖面,第三项看 ground truth 可信度,第四项防重复样本虚高检出率。

合成样本还有一个天然偏差要正视:LLM 注入的漏洞形态偏"教科书式",真实漏洞往往夹杂业务逻辑与历史包袱,评分时要留意这种差异,不能把合成集上的结果直接等同于真实世界表现。

体检结论写进数据集说明,评测结果发布时一并公开,横评才有可信度。

十、成本与风险提示

十一、合成数据管线落地清单

总结

这一期的核心结论一句话:带 ground truth 的智能合约安全评测数据,可以用 LLM 自动注入漏洞的方式合成,但能不能用,取决于管线而不是模型——生成受控、验证确定、标注有边界、成本可审计,四条缺一不可。我把整条管线挂在 4sapi(https://4sapi.com)统一网关上跑,批量调度与用量统计都在一处,账单看得清,重试也收得住。欢迎在评论区发表想法,一起聊聊合成评测数据的话题。

标签:智能合约漏洞注入合成数据集安全评测4sapi

推荐阅读

探索更多前沿洞察与行业干货。