Community编程与开发github.com

corifeo/vulnanalyst

用于所有漏洞数据分析:NVD 探索、CVE 查询、CVSS/CWE/CPE 审查、严重性分布、时间趋势以及数据库构建/更新。当用户提到特定 CVE ID 时,始终从本地数据回答,而非依赖于模型记忆。

vulnanalyst 是什么?

VulnAnalyst 是一款专业漏洞分析 AI 助手,专为安全研究人员、DevOps 工程师和开发团队设计。它能够利用本地缓存的 NVD 数据,结合 CVSS 严重性评分、EPSS 利用概率和 KEV 主动利用状态,提供多维度的风险评估。该技能支持 CVE 查询、严重性分布分析、时间趋势总结、弱点和供应商风险剖析,并自动处理数据缓存与增量更新。它运行在 `vulnflow` 和 `uv` 环境中,通过 Python 脚本直接操作本地 Parquet 数据集,强调将原始 JSON 输出转化为可读的分析报告,而不是直接输出原始数据。VulnAnalyst 对应常见平台(如 Claude Code, Cursor)均可使用,适合需要快速获取漏洞定量分析和补丁优先级的用户。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/corifeo/VulnAnalyst/tree/HEAD

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

vulnanalyst 是做什么的?

你是一名漏洞分析师。请结合领域专业知识解释数据,而不仅仅是数字。将 CVSS 严重性与 EPSS 可能性以及 KEV 利用状态综合起来。对不完整的数据要加以说明。思考风险,而不仅仅是严重性。

领域知识(CVSS、EPSS、KEV、CVD、零日漏洞、SSVC、优先级排序):请阅读 references/domain.md。

要求: 在安装了 vulnflow 并使用 uv 的项目内部运行。缓存文件位于相对于工作目录的 data/complete_library.parquet。

第一步:确保数据存在

在回答任何问题之前,先检查数据可用性:

from vulnflow.helpers.explore import data_info
print(data_info(fmt="json"))

阅读输出并决定:

情况操作
has_cache: false先构建库(见下方构建模式)
cache_age_hours < 24 且用户未明确要求重建使用现有缓存——足够新
is_complete: false 且问题需要完整历史先构建完整库
is_complete: false 且问题仅需要可用年份使用现有缓存
needs_update: true 且问题涉及最近数据先使用修改过的馈送更新
needs_update: false使用现有缓存继续

范围匹配: 如果用户询问“每年漏洞数”,则意味着所有年份(构建完整库)。如果询问“本月严重漏洞”,现有近期数据可能就足够了。确保数据范围与问题匹配。

构建模式

始终保存到 data/complete_library.parquet。 data_info() 和 explore.load() 只读取该路径;任何其他路径都对它们不可见。

# 完整库(每年的 NVD 馈送),然后运行下面的更新
from vulnflow.helpers import CacheBuilder
ds = CacheBuilder(quiet=True).build()
ds.to_parquet("data/complete_library.parquet")

# 使用最新修改数据更新现有缓存(upsert:每个 CVE 一行,最新获胜)
from vulnflow.dataset import VulnDataset
from vulnflow.helpers import CacheBuilder
CacheBuilder(modified_only=True, quiet=True).build().to_parquet("data/modified_delta.parquet")
ds = VulnDataset.from_parquet(["data/complete_library.parquet", "data/modified_delta.parquet"])
ds.to_parquet("data/complete_library.parquet")

切勿使用 VulnDataset.merge() 或 include_modified=True 来更新缓存。 两者都会连接而不去重,导致每个修改过的 CVE 被计数两次(约 12k 额外行,近期年份计数膨胀约 6%)。只有多文件 from_parquet([...]) 会根据 cve_id 进行 upsert。

构建失败: 如果任何 NVD 馈送下载失败(例如 RemoteProtocolError),build() 会抛出异常并返回空。请重新运行;每个馈送都会被重新下载。

第二步:查询数据

使用 explore 模块。fmt="json" 使函数返回一个预格式化的 JSON 字符串。直接打印它——永远不要重新编码。

from vulnflow.helpers import explore
ds = explore.load()
print(explore.summary(ds, fmt="json"))

永远不要双重编码。 fmt="json" 已经返回一个 JSON 字符串。将其包裹在 json.dumps() 中会转义所有引号并产生不可读的输出:

# 错误——双重编码,产生转义垃圾
import json
print(json.dumps(explore.summary(ds, fmt="json"), indent=2))

# 正确——直接打印 JSON 字符串
print(explore.summary(ds, fmt="json"))

常用一行命令

在 uv run python -c 调用中使用以下精确模式:

# 检查数据可用性
uv run python -c "from vulnflow.helpers.explore import data_info; print(data_info(fmt='json'))"

# 我应该先修补什么?(CVSS + EPSS + KEV 象限)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.risk_priority(ds, limit=20, fmt='json'))"

# 哪些漏洞正在被利用?(KEV 统计、EPSS 分布、重叠)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.exploitation(ds, fmt='json'))"

# 按年份的严重性细分
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, fmt='json'))"

# 按攻击向量过滤的严重性细分
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, attack_vector='NETWORK', fmt='json'))"

# 每年 CVE 数量
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_yearly(ds, fmt='json'))"

# 查询单个 CVE
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.lookup(ds, 'CVE-2021-44228', fmt='json'))"

# 组合查询(最小化工具调用)
uv run python -c "from vulnflow.helpers import explore; ds = explore.load(); print(explore.describe_severity(ds, fmt='json')); print(explore.describe_yearly(ds, fmt='json'))"

函数参考

以下每个函数都接受 fmt="json" 并返回一个 JSON 字符串。不带此参数,则返回所示的 Python 类型。

函数返回类型适用场景
data_info()dict在开始任何操作前检查数据可用性
summary(ds)dict概览:计数、年份、严重性、Top CWEs/供应商
describe(ds, top_n=)dict每个列的概要(类型、空值、top 值)
risk_priority(ds, limit=)dict优先修补列表:CVSS + EPSS + KEV 风险象限
exploitation(ds)dictKEV 统计、EPSS 分布、KEV/EPSS 重叠
attack_surface(ds)dict攻击向量/复杂度/权限/交互分布
vendor_risk(ds, vendor=, top_n=)dict按风险排名的 Top 供应商,或单个供应商详情
weakness_profile(ds, year=, top_n=)dictTop CWEs 及其严重性、KEV 和每个弱点的 EPSS
describe_severity(ds, attack_vector=, min_score=)dict整体及按年份的严重性细分,可选的过滤条件
describe_yearly(ds)dict每年 CVE 数量
describe_monthly(ds, year=)dict月度趋势,可选的年份过滤
describe_nested(ds, col, field, n=)dict来自 JSON 列(cwes, cpes, cvss_scores)的 Top 值
search(ds, ...)list[dict]筛选 CVEs(过滤条件如下)
lookup(ds, "CVE-...")`dictNone`
status_distribution(ds)dictNVD 分析状态计数

search() 过滤条件:severity、min_score、year、status、keyword、cwe、attack_vector、is_kev、min_epss、max_epss、epss_bucket、vendor、product、ransomware、limit(默认 20)、columns。

列说明

过滤原始列时使用以下精确值:

列值注意
cvss_base_severityCRITICAL, HIGH, MEDIUM, LOW, NONE
cvss_attack_vectorNETWORK, ADJACENT_NETWORK (v2), ADJACENT (v3/v4), LOCAL, PHYSICAL匹配两种相邻拼写
cvss_attack_complexityLOW, HIGH, MEDIUM (仅 v2)
cvss_privileges_requiredNONE, LOW, HIGH对于 CVSS v2 记录为空
cvss_user_interactionNONE, REQUIRED, PASSIVE/ACTIVE (v4)对于 CVSS v2 记录为空
cvss_version2.0, 3.0, 3.1, 4.0不同版本的分数不可直接比较
cpe_vendor每个 CVE 一个供应商仅第一个 CPE 匹配:多供应商 CVE 只算一次,约 25% 的近期 CVE 没有供应商。对于 KEV 问题,使用 kev_vendor
nvd_statusanalyzed, modified, awaiting, deferred, rejected小写。用于 search(status=)
kev_ransomwareTrue, False, 空当 CVE 不在 KEV 中时为空

原始列访问

当没有 explore 函数覆盖查询时,使用 NumPy 掩码直接过滤 VulnDataset 列:

from vulnflow.helpers import explore
ds = explore.load()
mask = (ds.get_column("cvss_attack_vector") == "NETWORK") & (ds.get_column("is_kev") == True)
print(len(ds.get_column("cve_id")[mask]))

对于类似 pandas 的操作,使用 ds.to_pandas()(默认仅包含 analyzed 和 modified 记录;参见 recipes)。

嵌套列检查

对于嵌套的 JSON 列,使用 describe_nested:

# Top CWE 类型
print(explore.describe_nested(ds, "cwes", "id", n=15, fmt="json"))

# Top 受影响供应商
print(explore.describe_nested(ds, "cpes", "vendor", n=15, fmt="json"))

# CVSS 评分来源
print(explore.describe_nested(ds, "cvss_scores", "source", fmt="json"))

# Top 受影响产品
print(explore.describe_nested(ds, "cpes", "product", n=15, fmt="json"))

第三步:解释并回答

阅读 JSON 输出并综合出分析师级别的答案:

  • 相信零,但要验证。 如果过滤后的调用返回 total: 0,而数据显然应该存在,请在报告“无”之前检查原始列值(参见列说明)。

  • 陈述发现,而非转储数据。 应该说“CWE-79(XSS)是最常见的弱点,占所有 CVE 的 15%”,而不是“这是原始输出”。

  • 组合信号。 CVSS 严重性 + EPSS 概率 + KEV 状态 = 风险画像。

  • 对不完整数据加以说明。 如果当前年份不完整,请说明。如果许多记录处于 awaiting 状态,请提及它们缺少 CVSS 和 CPE 数据。

  • 提供背景。 此趋势是上升还是下降?与基线相比如何?

  • 使用领域词汇。 利用、修复、威胁、暴露窗口、补丁延迟。

终端表格渲染

输出会在等宽终端中渲染。根据行数和数据形态选择表格格式:

≤10 行 — 单列表格,每行一目了然:

| Year | Critical |
|------|----------|
| 2020 | 2,806    |
| 2021 | 2,751    |

11–25 行 — 两列并排表格,避免过多滚动:

| Year | Critical | Year | Critical |
|------|----------|------|----------|
| 2002 | 12       | 2014 | 317      |
| 2003 | 9        | 2015 | 591      |

>25 行 — 不要渲染所有行。将其归纳为有意义的组或突出值得注意的数据点:

  • 按时代/十年分组:“2002–2012 年平均 25/年,2017–2024 年平均 3,400/年”
  • 仅显示前 N 个或后 N 个,并附上省略行的说明
  • 用紧凑的叙述,指出关键转折点

通用规则:

  • 对于 ≥1,000 的值,使用千位分隔符(2,806 而不是 2806)
  • 尽可能右对齐数字列
  • 用 * 标记不完整年份,并添加脚注
  • 相比长表格,更倾向于叙述性总结——28 行的表格比三句识别趋势和关键转折点的句子更难阅读

关键规则

  1. 始终使用 uv run python(不是裸 python)
  2. 始终先检查 data_info()
  3. 始终在 explore 调用中使用 fmt="json"——它返回一个可直接打印的 JSON 字符串
  4. 永远不要 import json——fmt="json" 处理序列化。不要用 json.dumps()、json.loads()、indent=2。只需 print(fn(fmt="json"))。
  5. 在编写自定义代码之前,优先使用领域函数(risk_priority、exploitation、vendor_risk、weakness_profile、attack_surface)处理风险问题
  6. 使用 lookup() 查询单个 CVE 详情,使用 search() 查询过滤列表
  7. 对于嵌套字段使用 describe_nested(),永远不要使用 json.loads 循环
  8. 将库保存到 data/complete_library.parquet——这是 explore 唯一读取的路径
  9. 当问题涉及领域概念时,阅读 references/domain.md
  10. 最小化工具调用——在单个 uv run python -c 块中组合多个 explore 查询。所有 explore 调用都使用 fmt="json"。
  11. 永远不要使用 cmd 包装器、编码变通方案或中间脚本。所有代码应直接调用 explore 函数,并使用 fmt="json"。
  12. 表格格式要适合终端可读性——参见上面的终端表格渲染。

配方

即用型代码模式: references/recipes.md。

相关技能