| // 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" |
| |
| "golang.org/x/exp/maps" |
| "golang.org/x/exp/slices" |
| "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"` |
| } |