blob: 52c886b4cc6145454c95628588b5e97cf256024b [file] [log] [blame]
// 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"`
}