From 7c21232dfa7b3011533671bdd255df448362aba8 Mon Sep 17 00:00:00 2001 From: Michael Kedar Date: Tue, 4 Feb 2025 13:51:07 +1100 Subject: [PATCH] feat: add go struct definitions of OSV record Signed-off-by: Michael Kedar --- bindings/go/osvschema/constants.go | 2 + bindings/go/osvschema/vulnerability.go | 170 +++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 bindings/go/osvschema/vulnerability.go diff --git a/bindings/go/osvschema/constants.go b/bindings/go/osvschema/constants.go index da4b273..b93a9eb 100644 --- a/bindings/go/osvschema/constants.go +++ b/bindings/go/osvschema/constants.go @@ -1,5 +1,7 @@ package osvschema +const SchemaVersion = "1.6.8" + type Ecosystem string const ( diff --git a/bindings/go/osvschema/vulnerability.go b/bindings/go/osvschema/vulnerability.go new file mode 100644 index 0000000..93698f5 --- /dev/null +++ b/bindings/go/osvschema/vulnerability.go @@ -0,0 +1,170 @@ +package osvschema + +import ( + "encoding/json" + "time" +) + +// Package identifies the affected code library or command provided by the +// package. +// +// See: https://ossf.github.io/osv-schema/#affectedpackage-field +type Package struct { + Ecosystem Ecosystem `json:"ecosystem" yaml:"ecosystem"` + Name string `json:"name" yaml:"name"` + Purl string `json:"purl,omitempty" yaml:"purl,omitempty"` +} + +// Event describes a single version that either: +// +// - Introduces a vulnerability: {"introduced": string} +// - Fixes a vulnerability: {"fixed": string} +// - Describes the last known affected version: {"last_affected": string} +// - Sets an upper limit on the range being described: {"limit": string} +// +// Event instances form part of a “timeline” of status changes for the affected +// package described by the Affected struct. +// +// See: https://ossf.github.io/osv-schema/#affectedrangesevents-fields +type Event struct { + Introduced string `json:"introduced,omitempty" yaml:"introduced,omitempty"` + Fixed string `json:"fixed,omitempty" yaml:"fixed,omitempty"` + LastAffected string `json:"last_affected,omitempty" yaml:"last_affected,omitempty"` + Limit string `json:"limit,omitempty" yaml:"limit,omitempty"` +} + +// Range describes the affected range of given version for a specific package. +// +// See: https://ossf.github.io/osv-schema/#affectedranges-field +type Range struct { + Type RangeType `json:"type" yaml:"type"` + Events []Event `json:"events" yaml:"events"` + Repo string `json:"repo,omitempty" yaml:"repo,omitempty"` + DatabaseSpecific map[string]interface{} `json:"database_specific,omitempty" yaml:"database_specific,omitempty"` +} + +// Severity is used to describe the severity of a vulnerability for an affected +// package using one or more quantitative scoring methods. +// +// See: https://ossf.github.io/osv-schema/#severity-field +type Severity struct { + Type SeverityType `json:"type" yaml:"type"` + Score string `json:"score" yaml:"score"` +} + +// Affected describes an affected package version, meaning one instance that +// contains the vulnerability. +// +// See: https://ossf.github.io/osv-schema/#affected-fields +type Affected struct { + Package Package `json:"package,omitempty" yaml:"package,omitempty"` + Severity []Severity `json:"severity,omitempty" yaml:"severity,omitempty"` + Ranges []Range `json:"ranges,omitempty" yaml:"ranges,omitempty"` + Versions []string `json:"versions,omitempty" yaml:"versions,omitempty"` + DatabaseSpecific map[string]interface{} `json:"database_specific,omitempty" yaml:"database_specific,omitempty"` + EcosystemSpecific map[string]interface{} `json:"ecosystem_specific,omitempty" yaml:"ecosystem_specific,omitempty"` +} + +// MarshalJSON implements the json.Marshaler interface. +// +// This method ensures Package is only present if it is not equal to the zero value. +// This is achieved by embedding the Affected struct with a pointer to Package used +// to populate the "package" key in the JSON object. +func (a Affected) MarshalJSON() ([]byte, error) { + type rawAffected Affected // alias Affected to avoid recursion during Marshal + type wrapper struct { + Package *Package `json:"package,omitempty"` + rawAffected + } + raw := wrapper{rawAffected: rawAffected(a)} + if a.Package == (Package{}) { + raw.Package = nil + } else { + raw.Package = &(a.Package) + } + + return json.Marshal(raw) +} + +// Reference links to additional information, advisories, issue tracker entries, +// and so on about the vulnerability itself. +// +// See: https://ossf.github.io/osv-schema/#references-field +type Reference struct { + Type ReferenceType `json:"type" yaml:"type"` + URL string `json:"url" yaml:"url"` +} + +// Credit gives credit for the discovery, confirmation, patch, or other events +// in the life cycle of a vulnerability. +// +// See: https://ossf.github.io/osv-schema/#credits-fields +type Credit struct { + Name string `json:"name" yaml:"name"` + Type CreditType `json:"type,omitempty" yaml:"type,omitempty"` + Contact []string `json:"contact,omitempty" yaml:"contact,omitempty"` +} + +// Vulnerability is the core Open Source Vulnerability (OSV) data type. +// +// The full documentation for the schema is available at +// https://ossf.github.io/osv-schema. +type Vulnerability struct { + SchemaVersion string `json:"schema_version,omitempty" yaml:"schema_version,omitempty"` + ID string `json:"id" yaml:"id"` + Modified time.Time `json:"modified" yaml:"modified"` + Published time.Time `json:"published,omitempty" yaml:"published,omitempty"` + Withdrawn time.Time `json:"withdrawn,omitempty" yaml:"withdrawn,omitempty"` + Aliases []string `json:"aliases,omitempty" yaml:"aliases,omitempty"` + Related []string `json:"related,omitempty" yaml:"related,omitempty"` + Upstream []string `json:"upstream,omitempty" yaml:"upstream,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Details string `json:"details,omitempty" yaml:"details,omitempty"` + Severity []Severity `json:"severity,omitempty" yaml:"severity,omitempty"` + Affected []Affected `json:"affected,omitempty" yaml:"affected,omitempty"` + References []Reference `json:"references,omitempty" yaml:"references,omitempty"` + Credits []Credit `json:"credits,omitempty" yaml:"credits,omitempty"` + DatabaseSpecific map[string]interface{} `json:"database_specific,omitempty" yaml:"database_specific,omitempty"` +} + +// MarshalJSON implements the json.Marshaler interface. +// +// This method ensures times all times are formatted correctly according to the schema. +func (v Vulnerability) MarshalJSON() ([]byte, error) { + type rawVulnerability Vulnerability // alias Vulnerability to avoid recursion during Marshal + type wrapper struct { + Modified string `json:"modified"` + Published string `json:"published,omitempty"` + Withdrawn string `json:"withdrawn,omitempty"` + rawVulnerability + } + raw := wrapper{rawVulnerability: rawVulnerability(v)} + raw.Modified = v.Modified.UTC().Format(time.RFC3339) + if !v.Published.IsZero() { + raw.Published = v.Published.UTC().Format(time.RFC3339) + } + if !v.Withdrawn.IsZero() { + raw.Withdrawn = v.Withdrawn.UTC().Format(time.RFC3339) + } + + return json.Marshal(raw) +} + +// MarshalYAML implements the yaml.Marshaler interface. +// +// This method ensures times all times are formatted correctly. +func (v Vulnerability) MarshalYAML() (interface{}, error) { + type rawVulnerability Vulnerability // alias Vulnerability to avoid recursion during Marshal + raw := rawVulnerability(v) + if !v.Modified.IsZero() { + raw.Modified = v.Modified.UTC() + } + if !v.Published.IsZero() { + raw.Published = v.Published.UTC() + } + if !v.Withdrawn.IsZero() { + raw.Withdrawn = v.Withdrawn.UTC() + } + + return raw, nil +}