| // Copyright 2023 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // Package database provides functionality for reading, writing, and | |
| // validating Go vulnerability databases according to the v1 schema. | |
| package database | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "slices" | |
| "golang.org/x/exp/maps" | |
| "golang.org/x/vulndb/internal/osv" | |
| ) | |
| // Database represents a Go Vulnerability Database in the v1 schema. | |
| type Database struct { | |
| // DB represents the index/db.json endpoint. | |
| DB DBMeta | |
| // Modules represents the index/modules.json endpoint. | |
| Modules ModulesIndex | |
| // Vulns represents the index/vulns.json endpoint. | |
| Vulns VulnsIndex | |
| // Entries represents the ID/GO-YYYY-XXXX.json endpoints. | |
| Entries []osv.Entry | |
| } | |
| // DBMeta contains metadata about the database itself. | |
| type DBMeta struct { | |
| // Modified is the time the database was last modified, calculated | |
| // as the most recent time any single OSV entry was modified. | |
| Modified osv.Time `json:"modified"` | |
| } | |
| // ModulesIndex is a map from module paths to module metadata. | |
| // It marshals into and unmarshals from the format published in | |
| // index/modules.json, which is a JSON array of objects. | |
| type ModulesIndex map[string]*Module | |
| func (m *ModulesIndex) UnmarshalJSON(data []byte) error { | |
| var modules []*Module | |
| if err := json.Unmarshal(data, &modules); err != nil { | |
| return err | |
| } | |
| for _, module := range modules { | |
| path := module.Path | |
| if _, ok := (*m)[path]; ok { | |
| return fmt.Errorf("module %q appears twice in modules.json", path) | |
| } | |
| (*m)[path] = module | |
| } | |
| return nil | |
| } | |
| func (m ModulesIndex) MarshalJSON() ([]byte, error) { | |
| modules := maps.Values(m) | |
| slices.SortStableFunc(modules, func(m1, m2 *Module) int { | |
| switch { | |
| case m1.Path < m2.Path: | |
| return -1 | |
| case m1.Path > m2.Path: | |
| return 1 | |
| default: | |
| return 0 | |
| } | |
| }) | |
| for _, module := range modules { | |
| slices.SortStableFunc(module.Vulns, func(v1, v2 ModuleVuln) int { | |
| switch { | |
| case v1.ID < v2.ID: | |
| return -1 | |
| case v1.ID > v2.ID: | |
| return 1 | |
| default: | |
| return 0 | |
| } | |
| }) | |
| } | |
| return json.Marshal(modules) | |
| } | |
| // Module contains metadata about a Go module that has one | |
| // or more vulnerabilities in the database. | |
| type Module struct { | |
| // Path is the module path. | |
| Path string `json:"path"` | |
| // Vulns is a list of vulnerabilities that affect this module. | |
| Vulns []ModuleVuln `json:"vulns"` | |
| } | |
| // ModuleVuln contains metadata about a vulnerability that affects | |
| // a certain module (as used by the ModulesIndex). | |
| type ModuleVuln struct { | |
| // ID is a unique identifier for the vulnerability. | |
| // The Go vulnerability database issues IDs of the form | |
| // GO-<YEAR>-<ENTRYID>. | |
| ID string `json:"id"` | |
| // Modified is the time the vuln was last modified. | |
| Modified osv.Time `json:"modified"` | |
| // Fixed is the latest version that introduces a fix for the | |
| // vulnerability, in SemVer 2.0.0 format, with no leading "v" prefix. | |
| // (This is technically the earliest version V such that the | |
| // vulnerability does not occur in any version later than V.) | |
| // | |
| // This field can be used to determine if a version is definitely | |
| // not affected by a vulnerability (if the version is greater than | |
| // or equal to the fixed version), but the full OSV entry must | |
| // be downloaded to determine if a version less than the fixed | |
| // version is affected. | |
| // | |
| // This field is optional, and should be empty if there is no | |
| // known fixed version. | |
| // | |
| // Example: | |
| // Suppose a vulnerability is present in all versions | |
| // up to (not including) version 1.5.0, is re-introduced in version | |
| // 2.0.0, and fixed again in version 2.4.0. The "Fixed" version | |
| // would be 2.4.0. | |
| // The fixed version tells us that any version greater than or equal | |
| // to 2.4.0 is not affected, but we would need to look at the OSV | |
| // entry to determine if any version less than 2.4.0 was affected. | |
| Fixed string `json:"fixed,omitempty"` | |
| } | |
| // VulnsIndex is a map from vulnerability IDs to vulnerability metadata. | |
| // It marshals into and unmarshals from the format published in | |
| // index/vulns.json, which is a JSON array of objects. | |
| type VulnsIndex map[string]*Vuln | |
| func (v VulnsIndex) MarshalJSON() ([]byte, error) { | |
| vulns := maps.Values(v) | |
| slices.SortStableFunc(vulns, func(v1, v2 *Vuln) int { | |
| switch { | |
| case v1.ID < v2.ID: | |
| return -1 | |
| case v1.ID > v2.ID: | |
| return 1 | |
| default: | |
| return 0 | |
| } | |
| }) | |
| return json.Marshal(vulns) | |
| } | |
| func (v *VulnsIndex) UnmarshalJSON(data []byte) error { | |
| var vulns []*Vuln | |
| if err := json.Unmarshal(data, &vulns); err != nil { | |
| return err | |
| } | |
| for _, vuln := range vulns { | |
| id := vuln.ID | |
| if _, ok := (*v)[id]; ok { | |
| return fmt.Errorf("id %q appears twice in vulns.json", id) | |
| } | |
| (*v)[id] = vuln | |
| } | |
| return nil | |
| } | |
| // Vuln contains metadata about a vulnerability in the database, | |
| // as used by the VulnsIndex. | |
| type Vuln struct { | |
| // ID is a unique identifier for the vulnerability. | |
| // The Go vulnerability database issues IDs of the form | |
| // GO-<YEAR>-<ENTRYID>. | |
| ID string `json:"id"` | |
| // Modified is the time the vulnerability was last modified. | |
| Modified osv.Time `json:"modified"` | |
| // Aliases is a list of IDs for the same vulnerability in other | |
| // databases. | |
| Aliases []string `json:"aliases,omitempty"` | |
| } |