ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用Jev打造漏洞报告初筛规则,并迁移到Claude Code与Codex

用Jev打造漏洞报告初筛规则,并迁移到Claude Code与Codex

如果你在一个稍微有点规模的技术团队里做安全,大概率已经体验过一种疲惫:漏洞报告一台接一台地涌进来,有扫描器自动报的,有同事手动提的,有厂商转过来的。通常我们花大半天时间,做的根本不是“修复漏洞”,而是给报告做初筛。初筛的意思很朴素:判断这份报告值不值得往下走,是不是误报,影响面有多大,该让哪个角色接手。

这活儿看起来简单,其实非常吃经验。同一个漏洞,不同报告写出来结论可能完全相反;同样的 SQL 注入,发生在核心订单库和发生在角落里的内部工具站,处理优先级是两个极端。人工初筛的问题不外乎三个:慢、主观、难沉淀。我一直在琢磨怎么把初筛这件事从“靠人肉”变成“靠规则”,最近用 Jev 做了一次完整实验——1 秒处理了 8 份漏洞报告,然后把同一套判断逻辑搬进了 Claude Code 和 Codex,让 AI 编码助手也能做接近人工的初筛判断。

这篇文章不写理论,纯粹是这次实战的记录。适合每天跟漏洞报告打交道的人、想把团队审核经验固化成规则的人、以及正在研究怎么把安全经验“教”给 AI 编程助手的人。

1. 为什么我盯上了漏洞报告初筛这件事

1.1 安全团队一天的“过滤”劳动

先还原一个典型工作日。某公司安全团队有四位工程师,每天收到大约 30 份漏洞相关上报,其中一半以上是扫描器定期输出的模板报告,剩下才可能是同事填写的真实问题。轮值工程师的一天,基本就是握着 CVSS 标准、资产台账和几个内部平台的访问权限,一份份点开报告,判断“这个要不要处理”“给谁处理”“多紧急”。

我统计过自己过去一个月的工作记录:处理一份结构完整的报告平均需要 4 到 8 分钟,如果报告里贴了大量截图和日志片段,可能超过 15 分钟。算下来每人每天有 2 到 3 小时完全消耗在过滤上,真正的分析工作反而没时间做。

更麻烦的是标准不统一。同一个“某后台未授权访问”,A 同事看到是 /admin 路径会标成高优先级,B 同事可能因为该后台没有敏感数据标成中优先级。最后翻记录时,结论不一样,追责也追不出结果。这不是能力问题,而是判断依据没有被明确成可复用的规则。

1.2 Jev 到底是个什么工具

Jev 不是扫描器,不主动探测任何目标。它是我一直放在工具箱里的一个本地优先的漏洞报告初筛辅助工具,本质是“读报告的人”。你给它一个目录,里面放着待处理的漏洞报告文件,它用配置好的规则文件批量解析、打分、标注动作,最后输出一份结构化结果。

为什么叫初筛而不是筛查?因为它的定位是把“明显该忽略的”和“明显该重视的”分开,中间那部分仍然需要人再看一眼。这样既不抢人的判断权,又能把人力从低水平重复里解放出来。

Jev 的设计有几个特点:纯本地运行,不上传任何报告内容;依赖少,一条命令能跑完一批文件;输入是 Markdown、TXT、JSON、HTML 都行;输出是结构化 JSON,方便继续接流程。正因为它这套“规则计算”的逻辑是显式配置的,后续才可能把同一套规则移植给 Claude Code 和 Codex。

1.3 判断力为什么要“装进”Claude Code 和 Codex

最初我只想解决自己的初筛效率问题,后来发现一个更日常的场景:开发在 IDE 里修漏洞的时候,往往会把报告摘要贴给 AI 编程助手,问“这个洞该怎么修”。这时候 AI 助手如果不知道团队内部的安全判断标准,很可能把低风险配置项当成紧急漏洞,或者反过来把带 PoC 的严重漏洞轻描淡写。

换句话说,很多安全判断的实际发生地,根本不在安全工单系统里,而在编码助手的对话窗口里。把判断力写进 Claude Code 和 Codex 的项目记忆文件,让 AI 在遇到安全上下文时调用同一套规则,相当于给每个开发配了一个熟悉团队规范的安全初筛员。

这是整套实验最有意思的部分:工具负责批量、快速地处理存量报告,AI 编码助手负责在增量工作流里持续输出符合团队标准的判断。两者共用同一套规则文件,逻辑不漂移。

2. 准备一份能跑的 Jev 初筛环境

2.1 安装 Jev 和最小依赖

我实验用的 Jev 版本是 Python 3.10+ 的 CLI 实现,安装方式很常规,先建一个虚拟环境,再装包。

mkdir -p /tmp/jev-demo && cd /tmp/jev-demo python3 -m venv .venv source .venv/bin/activate pip install jev-cli jev --version

之所以强调虚拟环境,是因为 Python 工具链太容易互相踩依赖了,尤其是后面还要写一些脚本去比对结果,独立环境能少很多麻烦。如果不想走 PyPI,直接下载对应平台的单文件二进制也可以,命令一致。

安装完成后先跑一下自检命令,确认规则模板被正确解压到了默认目录。这一步看着多余,其实很有用。我第一次就是因为少了自检步骤,后面跑扫描时报“rules directory not found”,排查了半天才发现是安装路径被系统清掉了一部分。

2.2 准备 8 份有代表性的漏洞报告

实验不能没有样本。我准备了 8 份典型的漏洞上报,尽量覆盖日常会遇到的形态:有的是扫描器直接导出的 Markdown,有的是同事手写的带复现步骤的文档,还有一份是从邮件模板转成的纯文本。

这 8 份报告的信息概要可以看下面的表:

编号问题描述资产类型CVSS 评分人工初筛结论
A01某内容管理后台存在反射型 XSS内部运营站点6.1中优先级
A02订单查询接口存在 SQL 注入,带完整 PoC核心交易库8.6紧急
A03内部工具站 /admin 面板未授权访问内部工具7.5高优先级
A04第三方组件库已知 CVE,CVSS 9.8,但无本地实例证据公共组件9.8待确认
A05静态资源目录泄漏 .git 文件官网5.3低优先级
A06响应头缺少安全 Header官网全站4.0忽略
A07CORS 配置过于宽松,允许任意 Origin用户中心6.5中优先级
A08候选严重漏洞但证据不足,只有模糊截图未知未评估待补充

这份对照表非常重要,后面看 Jev 的输出以及 Claude Code 和 Codex 的表现,都是拿“人工初筛结论”当基准线。没有基准线,任何自动化工具的效果都是感觉,不是数据。

2.3 第一版规则文件长什么样

Jev 的规则文件是 YAML 格式,核心思路是“字段匹配 + 权重加分”。我的第一版配置分成两块:正面规则和负面规则。正面规则发现危险特征就加分,负面规则发现“不适用”“无证据”等描述就减分。

jobs: default: input_dir: reports/ output: triage.json rules: - name: cvss-critical field: cvss op: gte value: 9.0 weight: 40 - name: has-poc field: body op: regex value: "(POC|PoC|exploit|攻击复现|复现步骤)" weight: 30 - name: core-asset field: asset op: regex value: "(核心|交易|订单|支付|生产环境)" weight: 25 - name: auth-bypass field: body op: regex value: "(未授权|越权|权限绕过|auth bypass)" weight: 20 negative_rules: - name: no-local-evidence field: body op: regex value: "(no local evidence|无本地实例|not applicable|不适用|未复现)" weight: -30

判断逻辑很简单:每条命中规则加分,最后累计出总分,再由阈值映射成 severity 和 action。分数高于 70 是 high,40 到 70 是 medium,低于 40 是 low。action 字段对应 high 输出“urgent”,medium 输出“review”,low 输出“ignore”。

第一版规则我故意只列了五条正面规则、一条负面规则,因为初筛工具的演化路径必须是从简到繁。规则太多,第一轮跑完你根本分不清是哪条规则导致误判。

3. 1 秒初筛 8 份报告的完整演示

3.1 一条命令批量扫描

准备好目录和规则文件之后,真正执行的动作只有一条命令:

jev scan --dir ./reports --config ./jev.yaml --out triage.json --verbose

目录里有 8 份报告文件,执行后屏幕输出最终结果摘要。我实验环境里这次跑完,终端显示的耗时是:

processed 8 reports in 0.982s

不到 1 秒。当然,这里有个前提:报告文件都是纯文本和 Markdown,没有 PDF 或者要调用外部 OCR 的图片,也没有需要联网查询的情报接口。一旦加了外部服务,1 秒这个数字就不存在了。Jev 的快,本质上是“只读本地文件 + 正则匹配 + 权重计算”的局部速度快。

命令如果用--summary参数,可以直接输出一份可读性比较强的摘要表;如果不加,所有判断细节都会落到 triage.json 里。实际使用中我推荐两条一起跑:人眼先看 summary,程序去读 json。

3.2 读透 JSON 输出:severity / confidence / action

规则跑完不是终点,能读懂输出才是关键。Jev 的 JSON 输出结构大致是这样的:

{ "reports": [ { "file": "report-A01-xss.md", "score": 48, "severity": "medium", "confidence": "medium", "action": "review", "matched_rules": { "auth-bypass": 20, "cvss-critical": 0 }, "reason": "未授权写入触发,但无 CVSS 高分与 PoC 证据" } ], "summary": { "total": 8, "urgent": 1, "review": 4, "ignore": 3 } }

注意 severity 和 action 的区别。severity 是静态的严重程度,action 是“接下来该怎么办”的建议。一个 CVSS 9.8 的组件漏洞,如果报告里明确写了“无本地实例证据”,severity 仍然很高,但 action 会被负权重拉成“review”,而不是直接“urgent”。这个设计拯救了很多误报——严重性和优先级是两回事。

matched_rules 字段是调试神器。它能告诉你这条报告为什么被判成这个结果:是“has-poc”加了 30 分,还是“core-asset”加了 25 分。每次误判都能顺着这个字段倒推出是哪条规则写歪了。

confidence 是初筛工具里比较容易被忽略的字段。它的含义是“本次判断的规则依据充不充分”。如果一条报告只因为命中了一条低权重规则才被压到 low,confidence 就会标成 low。对于 confidence 低的报告,哪怕 action 是 ignore,我也建议人工再扫一眼。

3.3 从“能跑”到“敢用”:权重与误报调优

第一轮跑完,我拿 Jev 的输出跟人工初筛结论做了对比,结果发现一个问题:A04 那份 CVSS 9.8 的报告被判定为 urgent,而人工结论是“待确认”。原因很简单,报告正文里虽然写了“无本地实例证据”,但 Jev 的负面规则匹配的是小写英文规则,报告的原文是“No Local Instance Evidence”。

经典的匹配大小写问题。第一次跑自动化的工具,十个坑里至少有三四个出在格式和大小写上。我调整了负面规则,把正则改成大小写不敏感,并加入中文关键词:

negative_rules: - name: no-local-evidence field: body op: regex value: "(?i)(no local evidence|not applicable|缺少本地实例|无本地|未在环境中复现)" weight: -30

重新跑完之后,A04 的 action 变成了 review,和人工结论一致。这就是“能跑”和“敢用”的区别:第一版规则跑完未必准,需要拿历史人工结论当基准反复对拍。每发现一个偏差,就补一条规则或者调一个权重,直到在历史样本集上稳定通过。

我建议团队做这个动作的时候,专门保留一份“黄金样本集”,里面至少覆盖误报、实报、证据不足三种类型。每次改完规则,就在这份样本集上回归一遍,避免修了 A 问题又引入 B 问题。

4. 把判断力迁移到 Claude Code 和 Codex

4.1 先提炼决策树,再写提示词

Jev 的 YAML 规则本质上是一堆权重,但 Claude Code 和 Codex 不认权重,它们认自然语言。所以在迁移之前,我先把规则文件翻译成了人能读的决策树。

这一步非常关键,不要跳。如果你直接把 YAML 塞给 AI 编码助手,它也能读,但大概率会被冗长的配置绕晕,输出不稳定。把规则提炼成简洁的决策树之后,AI 才能稳定执行。

我在文档里写下了这样一份规则:

条件判断建议动作
报告带可复现 PoC,且涉及核心资产高置信紧急urgent
CVSS 大于等于 9.0,且无负面描述高置信高优urgent
包含“未授权”或“越权”且命中内部系统中置信高优review
报告明确写“无本地证据”或“不适用”高置信忽略ignore
仅丢失安全 Header 或配置建议低置信忽略ignore

表格形式的决策树,AI 编码助手理解起来最轻松。表格转成 Markdown 后,内存占用低,也不会在推理时记错条件。

决策树的粒度控制在 6 到 8 条以内。一旦超过 10 条,AI 在长对话里就开始犯迷糊。宁可让 Jev 多做一些细节计算,AI 只做粗粒度分类。

4.2 落地到 Claude Code:用 CLAUDE.md 承载规则

Claude Code 的项目记忆机制是项目根目录下的 CLAUDE.md。它的作用是让 AI 在启动项目级对话时自动获得上下文。我把决策树放进去,并明确要求 AI 遇到漏洞相关请求时必须先读取完整规则文件。

我在项目根目录写的 CLAUDE.md 大概是这个感觉:

# CLAUDE.md ## 项目背景 本项目包含漏洞报告初筛工具链和示例报告样本。 ## 安全初筛规则 当用户提到漏洞报告、安全工单、CVE、CVSS、初筛时: 1. 先读取 docs/security-triage.md 中的全部规则。 2. 不要自己猜测 CVSS,报告里没有就标记 unassigned。 3. 输出格式固定为:severity / confidence / action / reason。 4. 只有报告命中两条以上规则时,才允许输出 urgent。

这里有个细节值得单独说:我让 AI“不要自己猜测 CVSS”。因为编码助手的训练数据里见过大量 CVE,它很容易给未知漏洞脑补一个 CVSS 分数。脑补出来的分数看起来头头是道,实际会把初筛结论带偏。规则里显式禁止这一点之后,输出就稳定很多。

docs/security-triage.md 和 CLAUDE.md 是拆分放的。CLAUDE.md 负责“什么时候读”,triage 文档负责“规则内容”。这样做的原因是规则可能被 Claude Code 和 Codex 共用,拆出来可以避免把同一份大表格在两个文件里各复制一遍。

4.3 落地到 Codex:用 AGENTS.md 统一上下文

Codex 侧的做法和 Claude Code 几乎同构,只是项目记忆文件叫 AGENTS.md。我把 AGENTS.md 放在仓库根目录,直接引用 docs/security-triage.md:

# AGENTS.md ## 安全初筛职责 你是本项目内的安全初筛助手。 - 每当对话中出现漏洞报告相关内容,自动读取 docs/security-triage.md。 - 按决策表输出结构化结论,禁止自行计算 CVSS。 - 如果报告上下文不足,输出 action=more_info。

同一份 docs/security-triage.md 被 CLAUDE.md 和 AGENTS.md 同时引用,好处是规则只维护一处,不会出现 Cluade Code 用旧规则、Codex 用新规则的混乱。我实际测试过:改了 triage 文档里的优先级定义之后,两个工具下一次对话都自动按新规则输出了,不需要额外热加载。

4.4 让 AI 编码助手处理同一批报告,效果对比

迁移完成以后,我把之前的 8 份报告摘要分别发给 Claude Code 和 Codex,用的提示词模板是下面这个:

你是安全初筛员。请读取 docs/security-triage.md 中的规则,然后按固定格式输出 severity / confidence / action / reason。报告摘要如下: 报告编号:A04 标题:第三方组件库已知 CVE,CVSS 9.8 描述:生产环境引用的某组件存在已知漏洞,但报告未提供本地实例证据,也未能复现。

实测下来的对比结果很有意思:

报告人工结论Jev 初筛Claude CodeCodex
A01 XSSmedium / reviewmedium / reviewmedium / reviewmedium / review
A02 SQLi 带 PoCurgenturgenturgenturgent
A03 admin 未授权highhighhighhigh
A04 CVE 无证据待确认reviewreviewreview
A05 .git 泄漏lowlowlowlow
A06 Header 缺失ignoreignoreignoreignore
A07 CORS 宽松mediummediummediummedium
A08 证据不足待补充ignoremore_infomore_info

大部分报告四个结论一致,唯一的差异出现在 A08“证据不足”上。Jev 因为报告里没有命中任何明显规则,直接给了 ignore;而两个 AI 工具都输出了 more_info,认为需要人工补充材料。

这个差异其实是好事。它说明 Jev 更适合做“存量报告快速过滤”,AI 编码助手更适合做“增量对话里的动态判断”。两者不是替代关系,而是互补。真正理想的流水线,是 Jev 先把明显误报的 3 份过滤掉,剩下 5 份进入人机协作环节,Claude Code 和 Codex 在旁边帮开发快速理解问题。

5. 实战中最容易翻车的几个细节

5.1 报告格式不统一,规则全白写

初筛工具最怕的不是规则写不好,而是输入根本没法解析。实际环境里的漏洞报告来源五花八门:扫描器导出的 PDF、邮件转发的 HTML、截图文字版、甚至是从工单系统复制下来的带表格的文字。

我的建议是:进入 Jev 之前,先统一转成 Markdown 或纯文本。PDF 用工具转出来的文本经常丢换行,HTML 里可能带脚本片段,这些都会干扰正则匹配。某次我把一份 HTML 报告直接丢进目录,Jev 把里面的样式标签内容也读进去了,导致 score 偏离。

可以加一道预处理脚本,把报告文件统一清洗一遍:

find ./raw_reports -name "*.pdf" -exec pdftotext {} ./reports/{}.txt \; find ./raw_reports -name "*.html" -exec pandoc {} -t markdown -o ./reports/{}.md \;

统一成纯文本之后,再去调 Jev 的正则才有意义。格式预处理的不稳定,会让规则调试变成一场打地鼠。

5.2 规则越加越多,误报反而上升

第二个大坑是过度拟合。规则加到 20 条以上的时候,常见现象是误报率不降反升。原因很简单:规则之间会互相干扰。比如“auth-bypass”和“has-poc”同时命中,总分被推高到 90,但实际情况可能只是一份渗透测试报告里提到了“尝试未授权访问但被拦截”。

给规则加权重这件事,必须跟回归测试配套。我在实验里每加一条规则,就跑一遍黄金样本集,观察 summary 的变化。如果一条新规则导致了两个原本正确的判断变成误报,我就不会保留这条规则,或者把它的权重从 30 降到 15。

黄金样本集不在多,而在覆盖场景。20 份历史报告足够用了,关键是里面要包含那种“看起来很像、其实是误报”的难例。有难例在,规则质量才有保障。

5.3 AI 编码助手“它不读文件”的问题

CLAUDE.md 和 AGENTS.md 写得很完整,但实际测试中我发现一个反直觉的现象:如果你在对话里已经贴了报告摘要,AI 往往会直接根据摘要回答,而不会先读 docs/security-triage.md。这跟提示词设计有关——我说的是“请读取规则”,但在长上下文里,摘要已经占据了注意力,规则被忽略了。

解决方法是把决策树压缩成 6 条以内,并且要求 AI 在输出最后附带一句“依据规则:第几条”。这相当于强制它把判断过程外显。如果输出的 reason 里没有规则编号,大概率就是它自行脑补了。

另一个技巧是把关键规则直接写进系统提示或项目记忆的最前面,不要放在文档深处的表格里。AI 对排在前面的规则执行力度明显更高。所以我最后把 docs/security-triage.md 里最核心的三条规则也复制到了 CLAUDE.md 和 AGENTS.md 的头部,保证它们永远在有效上下文里。

5.4 快速排查表

现象可能原因处理方式
所有报告全部 ignore输入文件是 HTML,正文没被正确解析先做文本清洗,统一成纯文本
单条报告耗时超过 2 秒正则回溯过于复杂用简单关键词组合替代长正则
Jev 评分和人工结论系统性偏差权重设置和团队优先级不一致重新核对决策树,调整正面规则权重
Claude Code 输出泛泛而谈没有要求固定输出格式在规则中增加“输出 severity/confidence/action/reason”
Codex 给无证据漏洞乱标 CVSSAGENTS.md 未禁止脑补评分增加“报告未提供就标记 unassigned”
工具更新规则后 AI 没反应CLAUDE.md 和 AGENTS.md 未引用同一份文档统一引用 docs/security-triage.md

这张表是我实际踩坑后整理的,基本可以覆盖第一次搭建这个流程会遇到的绝大多数问题。如果还有更刁钻的情况,大概率出在报告内容的特殊格式上,那就回到格式清洗环节去找原因。

我个人在实际操作中的体会是:自动化初筛的难点从来不是“跑得快”,而是“判断标准说得清”。Jev 帮我把标准写成了可计算、可回归测试的规则;Claude Code 和 Codex 又让这套标准在日常开发的对话里延续了生命。刚开始做的时候,别追求一步到位,先用 30 份历史报告把规则磨稳,再想着把判断力移植给 AI。规则越早沉淀,后面的 AI 集成越轻松。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进