Skip to content

OSV Schema

核心类型建模 OSV Schema(当前 1.4.0)。

顶层结构

必需 vs 可选

字段必需说明
schema_version当前 1.4.0
id唯一记录标识
modified最后修改时间
published首次发布时间
withdrawn字符串,非 time.Time
aliases如 CVE-2024-XXXX
affected但通常存在
severityCVSS v2 / v3 / v4

osv validate 强制 idschema_version

检查刻意做得浅——它确认记录可解析且带这两个身份字段,而非每个可选字段都合规。失败分三层、按序检查:文件可读性、原始 JSON 语法、再到 OSV 结构反序列化。json.Valid 通过并不保证 UnmarshalFromJson 成功——例如 affected 是字符串而非数组时,JSON 语法合法,但 OSV 解码失败,报为解析错误。解码成功后,idschema_version两个独立 if,非短路:两者都为空时两条错误都会收集(只缺 id 的记录仍会继续检查 schema_version)。affectedseverityreferences 不检查;一条没有 affected 条目的记录照样通过校验。

完整类型关系图

Affected → package → ranges → events

package 对象带三个字段:ecosystem类型化常量之一)、name(包名——对 Maven 是 groupId:artifactId)和 purl(可选的 Package URL 字符串)。purl 仅供参考;SDK 不解析它,故生态专属拆分(如 Maven GAV)应走 nameGetGroupID / GetArtifactID,而非 purl。一条 affected 条目还可自带 severity 切片,作用域仅限该 affected 范围。

一条记录的生命周期

字段速查(按用途)

某个版本是否受影响?—— 事件时间线判定

消费 OSV 数据时最重要的一个算法就是:给定一个具体版本,它脆弱吗? OSV 不用散文回答,而是用每个 range 里那串有序的 events。你从左到右沿时间线走一遍,一路翻转一个"受影响"标志位。

特殊值 introduced: "0" 表示"从最初的版本起"。上面的流程覆盖了三种常见事件(introduced / fixed / last_affected);第四种 limit 标记范围上限,但last_affected 不同——limit 是排他的,即 V >= limit 时清零标志(limit 版本本身不受影响),而 last_affected 是含它的(last_affected 版本受影响,只有 V > last_affected 才清零)。limitGIT 范围之外很少见。SDK 提供逐事件的谓词,方便你自己实现这套判定:

黄金法则在这里为何关键

正因为每个事件恰好携带一个非空键,上面的遍历才能无歧义地对"哪个谓词为真"做 switch。这也是 osv query --events 输出 omitempty JSON 的原因——一个多余的 "fixed": "" 会让两个谓词看起来都为真。

RangeType —— 版本如何比较

上面算法里的 < / >= 比较并非通用的字符串比较。range 的 type 决定排序规则。

RangeType常量版本记号是……
SEMVERRangeTypeSemverSemVer 2.0.0 字符串,按优先级比较
ECOSYSTEMRangeTypeEcosystem由生态排序的不透明字符串(PyPI→PEP 440 等)
GITRangeTypeGitGit 提交哈希,需借助提交图解析

GIT 范围不可按字符串排序

GIT 范围,你不能靠比较哈希字符串来判断是否受影响——你需要仓库的提交祖先关系。把 GIT 范围当成"需要图解析",而不是"像 SEMVER 那样比较"。Range.Repo 字段(仓库 URL)正是把 GIT 范围锚定到该祖先关系的依据;对 SEMVER / ECOSYSTEM 范围它通常为空。

Severity 取分内部机制

severity[].score 存的是 CVSS 向量字符串,不是数字。SDK 暴露三个取分方法,共享同一个惰性解析、带 memoize 的底层值。

取分方法遇到向量字符串时何时用
GetScore()0.0只想要个浮点数,且把 0 当作"不可用"
GetScoreAsFloat()(0, error)必须区分真实的 0 与解析失败
GetScoreAsPointer()nil想用 nil 表示"没有数值分数"

当分数是向量时要给严重程度排序,读 SeveritySlice.GetCVSS3() / GetCVSS2() 并解释向量——见 Skills → severity

序列化:一个结构体,六套标签命名空间

每个核心字段都同时为六个生态打了标签,因此同一个结构体无需适配器即可在 JSON、YAML、配置解码、原生 SQL、MongoDB 与 GORM 之间往返。

数据库策略:列 vs JSON 块

简单标量字段直接落成列。复杂的嵌套切片(AffectedSliceSeveritySliceRange 等)实现了 sql.Scanner + driver.Valuer,因此 GORM 把它们存成单个 JSON 字符串,读取时再复原。

泛型类型参数

OsvSchema[EcosystemSpecific, DatabaseSpecific] 携带两个类型参数,向下流入 AffectedRange,让厂商专有的数据块保持带类型,而不是塌缩成 map[string]any

日常解析用 [any, any](每条 CLI 命令都是如此)。仅当你需要对 ecosystem_specific / database_specific 做带类型访问时,才传入具体结构体。

源文件

所有类型在根包 osv_schema 中:

文件内容
osv_schema.goOsvSchema 顶层类型
package.goPackageEcosystem 常量
affected.goAffectedAffectedSlice
severity.goSeveritySeveritySlice
range.goRange
event.goEvent
references.goReferences
aliases.goAliases
related.goRelated
credits.goCredits
unmarshal.goUnmarshalFromJson / UnmarshalFromJsonFile

Last updated:

Released under the MIT License.