[ blog · tutorial ]9 min read

面向 AI Agent 的依赖漏洞扫描 API:一次调用拿到 OSV.dev 公告、CVSS 分数与修复版本

Sarah Choy发布于 2026年9月1日约 9 分钟阅读
面向 AI Agent 的依赖漏洞扫描 API:一次调用拿到 OSV.dev 公告、CVSS 分数与修复版本

OSV.dev 是开源领域最好的免费漏洞数据库,它的 API 几乎给了你一切,唯独少了你做决策时最需要的那个字段:分数。本文讲怎么把漏洞公告变成一个可以直接用来判断的阈值。

一句话总结

  • OSV.dev 把 GitHub Advisory Database、Go 漏洞库、RustSec、PyPA 与各发行版安全跟踪库统一到同一套 CC-BY-4.0 schema 中——这是开源依赖数据最合适的来源。
  • OSV 只提供 CVSS 向量串,不给分数。而构建卡点需要的是数字,所以本接口按官方公式从向量算出 CVSS v3.1 基础分。
  • 一次 POST 可检查最多 50 个包、覆盖 13 个生态,固定 5 积分——批满时每个包 0.1 积分,且只在成功时计费。
  • 请从 lockfile 提交确切的已安装版本,不要提交范围:OSV 是拿具体版本去比对受影响范围的,因此范围本身没有唯一答案。
  • 版本更新并不等于干净。lodash 4.17.21 仍带有只在 4.18.0 中修复的公告——这恰恰说明为什么要查,而不是假设。

每份漏洞公告里都缺的那个字段

OSV.dev 是开源漏洞数据这些年来最好的一件事。Google 做它是为了解决一个实实在在的烂摊子:GitHub Advisory Database、Go 漏洞库、RustSec、PyPA,以及各大 Linux 发行版的安全跟踪库,都在用各自的 schema、各自的版本语义描述同一类问题。OSV 把这些全部归一化,以 CC-BY-4.0 发布,无需 API key,毫秒级响应。

它同样也不会告诉你事情有多严重。一条 OSV 公告带有 CVSS 向量串——CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H——但不带这个向量算出来的分数。于是每个想定下「高于 7.0 就中断构建」这类规则的团队,最后要么自己实现一遍 CVSS 规范,要么放弃,改成匹配严重程度字符串。

这是个小缺口,后果却不小,因为严重程度标签并不是阈值。「HIGH」不是一个你能拿去做比较的数字。

这个接口返回什么

依赖漏洞扫描一次 POST 接收最多 50 个包,按包返回每一条未撤回的公告,含 CVE 编号、计算得出的 CVSS v3.1 基础分、CWE 弱点分类,以及能够解决它的确切版本——外加整批按严重程度汇总的计数。

import httpx, os
API = "https://www.apipick.com/api"
HEADERS = {"x-api-key": os.environ["APIPICK_KEY"]}

def scan(packages):
    r = httpx.post(f"{API}/scan-dependencies",
                   headers=HEADERS, json={"packages": packages})
    r.raise_for_status()
    return r.json()

report = scan([
    {"ecosystem": "npm",  "name": "lodash",   "version": "4.17.15"},
    {"ecosystem": "PyPI", "name": "requests", "version": "2.19.0"},
])

print(report["summary"])
# {'packages_scanned': 2, 'vulnerable_packages': 2,
#  'total_vulnerabilities': 16, 'highest_cvss_score': 8.1,
#  'by_severity': {'critical': 0, 'high': 5, 'medium': 9, ...}}

用一个数字来卡发布

因为分数是数值型的,策略就是一次比较。注意其中对 None 的显式处理:没有 v3 向量的公告分数为 null,把它强制当成 0,正是未评分的严重漏洞溜过卡点的方式。

THRESHOLD = 7.0

blocking = [
    (pkg["name"], v)
    for pkg in report["results"]
    for v in pkg["vulnerabilities"]
    if v["cvss_score"] is not None and v["cvss_score"] >= THRESHOLD
    or v["cvss_score"] is None and v["severity"] in ("HIGH", "CRITICAL")
]

if blocking:
    for name, v in blocking:
        fix = ", ".join(v["fixed_versions"]) or "no fix published"
        print(f"{name}: {v['id']} {v['severity']} {v['cvss_score']} -> {fix}")
    raise SystemExit(1)

编码 Agent 这个场景

更有意思的使用方并不是 CI,而是一开始就在写依赖清单的那个 Agent。能调用这个接口的编码 Agent,会在写下某个版本之前先核查,并在同一轮回答里直接给出修复版本,而不是先产出一个依赖升级、一小时后再被 CI 里的扫描器打回。

工具定义就是一个含三个字段的对象,schema 以 OpenAI 和 Anthropic 两种格式托管在 GET /api/scan-dependencies/tool-schema,可以直接拉取而不必手写。当 Agent 需要了解某个刚发现的 CVE 的更完整背景——KEV 状态、EPSS 概率、厂商公告——可以搭配 Cybersecurity Search 一起用。

版本更新不等于版本干净

人们很容易把「升级到最新」当成修复然后翻篇。但去扫一下 lodash@4.17.21——大多数人心目中那个安全版本——公告依然会返回,而且只在一个尚不存在的 4.18.0 中修复。这不是数据出错,这正是数据在尽职。值得养成的习惯是:查你真正要发布的那个版本,而不是你以为没问题的那个版本。

自己接,还是直接调

自己对接 OSVAPI Pick
批量行为querybatch 只返回 id——每条公告还要再跑一趟一次调用,返回完整公告内容
CVSS 分数自己实现 v3.1 规范已计算并返回
修复版本按包过滤受影响范围矩阵按包解析好
部分失败一个坏包拖垮整批就地报告,其余照常返回
生态名称区分大小写:PyPI、crates.io,不是 pypi常见别名已归一化
成本免费,外加你要维护的代码5 积分/次,只在成功时计

数据从哪来

公告来自 OSV.dev,以 CC-BY-4.0 发布,覆盖 npm、PyPI、Go、Maven、NuGet、RubyGems、crates.io、Packagist、Hex、Pub、CRAN、SwiftURL 与 ConanCenter。我们补上的是分数计算、按包汇总,以及容错:单个查询失败的包会带上 error 字段返回,其余包一切照常,同时响应会设置 packages_failed,让你知道这份汇总是不完整,而不是干净。

免费 key 附带 100 积分、无需绑卡,相当于二十次装满 50 个包的扫描。把一个 CI 任务或一个 Agent 指过来,看看你的 lockfile 里一直带着什么。

常见问题

它和 npm audit 或 GitHub Dependabot 告警有什么不同?

那两者都绑定在单一生态和单一工作流上。npm audit 只懂 npm,Dependabot 只会对它监听的仓库提 PR。而这是一个普通的 HTTP 接口,可以在任何地方回答关于 13 个生态中任意包的问题——CI 脚本、对话中的编码 Agent、供应商评审表格、Slack 机器人都行。它不会替你开 PR、也不管理你的仓库;它只回答「这个确切版本是否受影响、什么版本能修」,剩下的交给你自己的工具去决定。

为什么要自己算 CVSS 分数,而不是直接返回 OSV 给的内容?

OSV 公告带的是 CVSS 向量串,例如 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H,但不带算出来的数字。而「高于 7.0 就中断构建」这类策略需要的正是数字,于是每个使用方最后都在重复实现同一份规范。我们按官方公式从向量算出 CVSS v3.1 基础分,以 cvss_score 返回,让你的卡点是一次数值比较,而不是对严重程度标签做字符串匹配。

如果某条公告没有 CVSS v3 向量会怎样?

有些公告只带 v4 向量,有些生态公告则完全不带向量。这种情况下 cvss_score 为 null,severity 回退到发布方自己的定性评级,若也没有则为 UNKNOWN。请在卡点逻辑中显式处理 null,而不要把它强制当成 0,否则一条未评分的严重公告就会一路放行。

可以提交 ^4.17.0 这样的版本范围吗?

不行。请按 lockfile 中的写法提交确切的已安装版本。OSV 判断某个版本是否受影响,靠的是拿它去比对公告中的受影响范围,所以需要一个具体的点来测试。范围本身没有唯一答案——4.17.0 和 4.17.21 完全可能落在同一条公告的两侧。

扫描一个大型项目要花多少钱?

无论一次带多少个包,单次调用都是 5 积分,上限 50 个包。一份 200 个包的 lockfile 就是 4 次调用,合计 20 积分。积分只在响应成功时扣除,因此 OSV 故障或请求格式错误都不花钱。

本文涉及的 API

Sarah Choy
作者
Sarah Choy
CEO, API Pick

Sarah Choy 是 API Pick 的 CEO,专注于为 AI Agent 与 LLM 工作流构建可用于生产的 API。