x/vulndb: support issues keyed by GHSA in `vulnreport create`
Command `vulnreport create` can now be run on Github issues of the form `x/vulndb: potential Go vuln in some/pkg: GHSA-some-ghsa-id`.
Introduces new function in `internal/ghsa` to fetch security advisories by GHSA id via Github API, which re-uses some factored out logic from existing `List` function.
Moves (and extends) functionality to convert security advisories into reports from `internal/worker` to `internal/report` so it can be used by both the worker and the vulnreport command.
Fixes golang/go#52361
Change-Id: I6902e8db4801245908b4a112b047ca5cc62db996
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/400495
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Julie Qiu <julieqiu@google.com>
diff --git a/cmd/vulnreport/main.go b/cmd/vulnreport/main.go
index 0470668..a594e95 100644
--- a/cmd/vulnreport/main.go
+++ b/cmd/vulnreport/main.go
@@ -142,7 +142,7 @@
if err != nil {
return err
}
- // Parse CVE ID from GitHub issue.
+ // Parse CVE or GHSA ID from GitHub issue.
parts := strings.Fields(iss.Title)
var modulePath string
for _, p := range parts {
@@ -151,15 +151,24 @@
break
}
}
- cveID := parts[len(parts)-1]
- if !strings.HasPrefix(cveID, "CVE") {
- return fmt.Errorf("expected last element of title to be the CVE ID; got %q", iss.Title)
+ id := parts[len(parts)-1]
+ var r *report.Report
+ switch {
+ case strings.HasPrefix(id, "CVE"):
+ cve, err := cvelistrepo.FetchCVE(ctx, repoPath, id)
+ if err != nil {
+ return err
+ }
+ r = report.CVEToReport(cve, modulePath)
+ case strings.HasPrefix(id, "GHSA"):
+ ghsa, err := ghsa.FetchGHSA(ctx, ghToken, id)
+ if err != nil {
+ return err
+ }
+ r = report.GHSAToReport(ghsa, modulePath)
+ default:
+ return fmt.Errorf("expected last element of title to be the CVE ID or GHSA ID; got %q", iss.Title)
}
- cve, err := cvelistrepo.FetchCVE(ctx, repoPath, cveID)
- if err != nil {
- return err
- }
- r := report.CVEToReport(cve, modulePath)
addTODOs(r)
return r.Write(fmt.Sprintf("reports/GO-2021-%04d.yaml", issueNumber))
}
@@ -189,6 +198,9 @@
if r.Links.Commit == "" {
r.Links.Commit = todo
}
+ if len(r.Links.Context) == 0 {
+ r.Links.Context = []string{todo}
+ }
if len(r.Versions) == 0 {
r.Versions = []report.VersionRange{{
Introduced: todo,
diff --git a/internal/ghsa/ghsa.go b/internal/ghsa/ghsa.go
index 5af39b9..47fdf5d 100644
--- a/internal/ghsa/ghsa.go
+++ b/internal/ghsa/ghsa.go
@@ -68,52 +68,96 @@
return s.ID
}
+// A gqlSecurityAdvisory represents a GitHub security advisory structured for
+// GitHub's GraphQL schema. The fields must be exported to be populated by
+// Github's Client.Query function.
+type gqlSecurityAdvisory struct {
+ ID string
+ Identifiers []Identifier
+ Summary string
+ Description string
+ Origin string
+ Permalink githubv4.URI
+ PublishedAt time.Time
+ UpdatedAt time.Time
+ Vulnerabilities struct {
+ Nodes []struct {
+ Package struct {
+ Name string
+ Ecosystem string
+ }
+ FirstPatchedVersion struct{ Identifier string }
+ // TODO(https://go.dev/issue/52550): uncomment when
+ // https://support.github.com/ticket/personal/0/1599280
+ // is fixed.
+ //Severity githubv4.SecurityAdvisorySeverity
+ UpdatedAt time.Time
+ VulnerableVersionRange string
+ }
+ PageInfo struct {
+ HasNextPage bool
+ }
+ } `graphql:"vulnerabilities(first: 100, ecosystem: $go)"` // include only Go vulns
+}
+
+// securityAdvisory converts a gqlSecurityAdvisory into a SecurityAdvisory.
+// Errors if the security advisory was updated before it was published, or if
+// there are more than 100 vulnerabilities associated with the advisory.
+func (sa *gqlSecurityAdvisory) securityAdvisory() (*SecurityAdvisory, error) {
+ if sa.PublishedAt.After(sa.UpdatedAt) {
+ return nil, fmt.Errorf("%s: published at %s, after updated at %s", sa.ID, sa.PublishedAt, sa.UpdatedAt)
+ }
+ if sa.Vulnerabilities.PageInfo.HasNextPage {
+ return nil, fmt.Errorf("%s has more than 100 vulns", sa.ID)
+ }
+ s := &SecurityAdvisory{
+ ID: sa.ID,
+ Identifiers: sa.Identifiers,
+ Summary: sa.Summary,
+ Description: sa.Description,
+ Origin: sa.Origin,
+ Permalink: sa.Permalink.URL.String(),
+ PublishedAt: sa.PublishedAt,
+ UpdatedAt: sa.UpdatedAt,
+ }
+ for _, v := range sa.Vulnerabilities.Nodes {
+ s.Vulns = append(s.Vulns, &Vuln{
+ Package: v.Package.Name,
+ // TODO(https://go.dev/issue/52550): uncomment when
+ // https://support.github.com/ticket/personal/0/1599280
+ // is fixed.
+ //Severity: v.Severity,
+ EarliestFixedVersion: v.FirstPatchedVersion.Identifier,
+ VulnerableVersionRange: v.VulnerableVersionRange,
+ UpdatedAt: v.UpdatedAt,
+ })
+ }
+ return s, nil
+}
+
+func newGitHubClient(ctx context.Context, accessToken string) *githubv4.Client {
+ ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken})
+ tc := oauth2.NewClient(ctx, ts)
+ return githubv4.NewClient(tc)
+}
+
// List returns all SecurityAdvisories that affect Go,
// published or updated since the given time.
-// The withCVE argument controls whether to select advisories that are
-// connected to CVEs.
+// If withCVE is true, selects only advisories that are
+// connected to CVEs, otherwise selects only advisories without CVEs.
func List(ctx context.Context, accessToken string, since time.Time, withCVE bool) ([]*SecurityAdvisory, error) {
- ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken})
- tc := oauth2.NewClient(context.Background(), ts)
- client := githubv4.NewClient(tc)
+ client := newGitHubClient(ctx, accessToken)
var query struct { // the GraphQL query
SAs struct {
- Nodes []struct {
- ID string
- Identifiers []Identifier
- Summary string
- Description string
- Origin string
- Permalink githubv4.URI
- PublishedAt time.Time
- UpdatedAt time.Time
- Vulnerabilities struct {
- Nodes []struct {
- Package struct {
- Name string
- Ecosystem string
- }
- FirstPatchedVersion struct{ Identifier string }
- // TODO(https://go.dev/issue/52550): uncomment when
- // https://support.github.com/ticket/personal/0/1599280
- // is fixed.
- //Severity githubv4.SecurityAdvisorySeverity
- UpdatedAt time.Time
- VulnerableVersionRange string
- }
- PageInfo struct {
- HasNextPage bool
- }
- } `graphql:"vulnerabilities(first: 100, ecosystem: $go)"` // include only Go vulns
- }
+ Nodes []gqlSecurityAdvisory
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"securityAdvisories(updatedSince: $since, first: 100, after: $cursor)"`
}
- vars := map[string]interface{}{
+ vars := map[string]any{
"cursor": (*githubv4.String)(nil),
"go": githubv4.SecurityAdvisoryEcosystemGo,
"since": githubv4.DateTime{Time: since},
@@ -127,39 +171,15 @@
return nil, err
}
for _, sa := range query.SAs.Nodes {
- if sa.PublishedAt.After(sa.UpdatedAt) {
- return nil, fmt.Errorf("%s: published at %s, after updated at %s", sa.ID, sa.PublishedAt, sa.UpdatedAt)
- }
if withCVE != isCVE(sa.Identifiers) {
continue
}
if len(sa.Vulnerabilities.Nodes) == 0 {
continue
}
- if sa.Vulnerabilities.PageInfo.HasNextPage {
- return nil, fmt.Errorf("%s has more than 100 vulns", sa.ID)
- }
- s := &SecurityAdvisory{
- ID: sa.ID,
- Identifiers: sa.Identifiers,
- Summary: sa.Summary,
- Description: sa.Description,
- Origin: sa.Origin,
- Permalink: sa.Permalink.URL.String(),
- PublishedAt: sa.PublishedAt,
- UpdatedAt: sa.UpdatedAt,
- }
- for _, v := range sa.Vulnerabilities.Nodes {
- s.Vulns = append(s.Vulns, &Vuln{
- Package: v.Package.Name,
- // TODO(https://go.dev/issue/52550): uncomment when
- // https://support.github.com/ticket/personal/0/1599280
- // is fixed.
- //Severity: v.Severity,
- EarliestFixedVersion: v.FirstPatchedVersion.Identifier,
- VulnerableVersionRange: v.VulnerableVersionRange,
- UpdatedAt: v.UpdatedAt,
- })
+ s, err := sa.securityAdvisory()
+ if err != nil {
+ return nil, err
}
sas = append(sas, s)
}
@@ -171,6 +191,26 @@
return sas, nil
}
+// FetchGHSA returns the SecurityAdvisory for the given Github Security
+// Advisory ID.
+func FetchGHSA(ctx context.Context, accessToken, ghsaID string) (_ *SecurityAdvisory, err error) {
+ client := newGitHubClient(ctx, accessToken)
+
+ var query struct {
+ SA gqlSecurityAdvisory `graphql:"securityAdvisory(ghsaId: $id)"`
+ }
+ vars := map[string]any{
+ "id": githubv4.String(ghsaID),
+ "go": githubv4.SecurityAdvisoryEcosystemGo,
+ }
+
+ if err := client.Query(ctx, &query, vars); err != nil {
+ return nil, err
+ }
+
+ return query.SA.securityAdvisory()
+}
+
func isCVE(ids []Identifier) bool {
for _, id := range ids {
if id.Type == "CVE" {
diff --git a/internal/ghsa/ghsa_test.go b/internal/ghsa/ghsa_test.go
index d147844..57af6fa 100644
--- a/internal/ghsa/ghsa_test.go
+++ b/internal/ghsa/ghsa_test.go
@@ -16,7 +16,7 @@
var githubTokenFile = flag.String("ghtokenfile", "",
"path to file containing GitHub access token")
-func TestList(t *testing.T) {
+func mustGetAccessToken(t *testing.T) string {
if *githubTokenFile == "" {
t.Skip("-ghtokenfile not provided")
}
@@ -24,7 +24,11 @@
if err != nil {
t.Fatal(err)
}
- accessToken := strings.TrimSpace(string(bytes))
+ return strings.TrimSpace(string(bytes))
+}
+
+func TestList(t *testing.T) {
+ accessToken := mustGetAccessToken(t)
// There were at least three relevant SAs since this date.
since := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
const withoutCVEs = false
@@ -42,3 +46,24 @@
}
}
}
+
+func TestFetchGHSA(t *testing.T) {
+ accessToken := mustGetAccessToken(t)
+ // Real GHSA that should be found.
+ const ghsaID string = "GHSA-g9mp-8g3h-3c5c"
+ got, err := FetchGHSA(context.Background(), accessToken, ghsaID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := ghsaID
+ var gotID string
+ for _, id := range got.Identifiers {
+ if id.Type == "GHSA" {
+ gotID = id.Value
+ break
+ }
+ }
+ if gotID != want {
+ t.Errorf("got GHSA with id %q, want %q", got.ID, want)
+ }
+}
diff --git a/internal/report/ghsa.go b/internal/report/ghsa.go
new file mode 100644
index 0000000..8872e1d
--- /dev/null
+++ b/internal/report/ghsa.go
@@ -0,0 +1,122 @@
+// Copyright 2021 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 report
+
+import (
+ "fmt"
+ "strings"
+
+ "golang.org/x/vulndb/internal"
+ "golang.org/x/vulndb/internal/ghsa"
+)
+
+// GHSAToReport creates a Report struct from a given GHSA SecurityAdvisory and modulePath.
+func GHSAToReport(sa *ghsa.SecurityAdvisory, modulePath string) *Report {
+ u := sa.UpdatedAt
+ r := &Report{
+ Module: modulePath,
+ Description: sa.Description,
+ Published: sa.PublishedAt,
+ LastModified: &u,
+ Links: Links{Context: []string{sa.Permalink}},
+ }
+ var cves, ghsas []string
+ for _, id := range sa.Identifiers {
+ switch id.Type {
+ case "CVE":
+ cves = append(cves, id.Value)
+ case "GHSA":
+ ghsas = append(ghsas, id.Value)
+ }
+ }
+ r.CVEs = cves
+ r.GHSAs = ghsas
+ if len(sa.Vulns) == 0 {
+ return r
+ }
+ r.Package = sa.Vulns[0].Package
+ r.Versions = versions(sa.Vulns[0].EarliestFixedVersion, sa.Vulns[0].VulnerableVersionRange)
+ for _, v := range sa.Vulns[1:] {
+ var a Additional
+ a.Package = v.Package
+ a.Versions = versions(v.EarliestFixedVersion, v.VulnerableVersionRange)
+ r.AdditionalPackages = append(r.AdditionalPackages, a)
+ }
+ r.Fix()
+ return r
+}
+
+// versions extracts the versions in which a vulnerability was introduced and
+// fixed from a Github Security Advisory's EarliestFixedVersion and
+// VulnerableVersionRange fields, and wraps them in a []VersionRange.
+//
+// If the vulnRange cannot be parsed, or the earliestFixed and vulnRange are
+// incompatible, populate the relevant fields with a TODO for a human to handle.
+func versions(earliestFixed, vulnRange string) []VersionRange {
+ // Don't try to be fully general here. Handle the common cases (which, as of
+ // March 2022, are the only cases), and let a person handle the others.
+ items, err := parseVulnRange(vulnRange)
+ if err != nil {
+ return []VersionRange{{
+ Introduced: fmt.Sprintf("TODO (got error %q)", err),
+ }}
+ }
+
+ var intro, fixed string
+
+ // Most common case: a single "<" item with a version that matches earliestFixed.
+ if len(items) == 1 && items[0].op == "<" && items[0].version == earliestFixed {
+ intro = "v0.0.0"
+ fixed = "v" + earliestFixed
+ }
+
+ // Two items, one >= and one <, with the latter matching earliestFixed.
+ if len(items) == 2 && items[0].op == ">=" && items[1].op == "<" && items[1].version == earliestFixed {
+ intro = "v" + items[0].version
+ fixed = "v" + earliestFixed
+ }
+
+ // A single "<=" item with no fixed version.
+ if len(items) == 1 && items[0].op == "<=" && earliestFixed == "" {
+ intro = "v0.0.0"
+ }
+
+ if intro == "" {
+ intro = fmt.Sprintf("TODO (earliest fixed %q, vuln range %q)", earliestFixed, vulnRange)
+ }
+
+ // Unset intro if vuln was always present.
+ if intro == "v0.0.0" {
+ intro = ""
+ }
+
+ return []VersionRange{{Introduced: intro, Fixed: fixed}}
+}
+
+type vulnRangeItem struct {
+ op, version string
+}
+
+// parseVulnRange splits the contents of a GitHub Security Advisory's
+// VulnerableVersionRange field into separate items.
+func parseVulnRange(s string) ([]vulnRangeItem, error) {
+ // A GHSA vuln range is a comma-separated list of items of the form "OP VERSION"
+ // where OP is one of "<", ">", "<=" or ">=" and VERSION is a semantic
+ // version.
+ var items []vulnRangeItem
+ parts := strings.Split(s, ",")
+ for _, p := range parts {
+ p = strings.TrimSpace(p)
+ if p == "" {
+ continue
+ }
+ before, after, found := internal.Cut(p, " ")
+ if !found {
+ return nil, fmt.Errorf("invalid vuln range item %q", p)
+ }
+ items = append(items, vulnRangeItem{strings.TrimSpace(before), strings.TrimSpace(after)})
+ }
+ return items, nil
+}
diff --git a/internal/report/ghsa_test.go b/internal/report/ghsa_test.go
new file mode 100644
index 0000000..d94f454
--- /dev/null
+++ b/internal/report/ghsa_test.go
@@ -0,0 +1,93 @@
+// Copyright 2022 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 report
+
+import (
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/vulndb/internal/ghsa"
+)
+
+func TestGHSAToReport(t *testing.T) {
+ updatedTime := time.Date(2022, 01, 01, 01, 01, 00, 00, time.UTC)
+ sa := &ghsa.SecurityAdvisory{
+ ID: "G1_blah",
+ Identifiers: []ghsa.Identifier{{Type: "GHSA", Value: "G1"}, {Type: "CVE", Value: "C1"}},
+ UpdatedAt: updatedTime,
+ Permalink: "https://github.com/permalink/to/G1",
+ Description: "a description",
+ Vulns: []*ghsa.Vuln{{
+ Package: "aPackage",
+ EarliestFixedVersion: "1.2.3",
+ VulnerableVersionRange: "< 1.2.3",
+ }},
+ }
+ got := GHSAToReport(sa, "aModule")
+ want := &Report{
+ Module: "aModule",
+ Package: "aPackage",
+ Versions: []VersionRange{
+ {Fixed: "v1.2.3"},
+ },
+ LastModified: &updatedTime,
+ Description: "a description",
+ GHSAs: []string{"G1"},
+ CVEs: []string{"C1"},
+ Links: Links{Context: []string{"https://github.com/permalink/to/G1"}},
+ }
+
+ if diff := cmp.Diff(*got, *want); diff != "" {
+ t.Errorf("mismatch (-want, +got):\n%s", diff)
+ }
+}
+func TestParseVulnRange(t *testing.T) {
+ for _, test := range []struct {
+ in string
+ want []vulnRangeItem
+ }{
+ {"", nil},
+ {"< 1.2.3", []vulnRangeItem{{"<", "1.2.3"}}},
+ {"< 4.3.2, >= 1.2.3", []vulnRangeItem{
+ {"<", "4.3.2"},
+ {">=", "1.2.3"},
+ }},
+ } {
+ got, err := parseVulnRange(test.in)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !cmp.Equal(got, test.want, cmp.AllowUnexported(vulnRangeItem{})) {
+ t.Errorf("%q:\ngot %+v\nwant %+v", test.in, got, test.want)
+ }
+ }
+}
+
+func TestVersions(t *testing.T) {
+ for _, test := range []struct {
+ earliestFixed string
+ vulnRange string
+ intro, fixed string
+ }{
+ {"1.0.0", "< 1.0.0", "", "v1.0.0"},
+ {"", "<= 1.4.2", "", ""},
+ {"1.1.3", ">= 1.1.0, < 1.1.3", "v1.1.0", "v1.1.3"},
+ {
+ "1.2.3", "<= 2.3.4",
+ `TODO (earliest fixed "1.2.3", vuln range "<= 2.3.4")`, "",
+ },
+ } {
+ got := versions(test.earliestFixed, test.vulnRange)
+ want := []VersionRange{{
+ Introduced: test.intro,
+ Fixed: test.fixed,
+ }}
+ if !cmp.Equal(got, want) {
+ t.Errorf("%q, %q:\ngot %+v\nwant %+v",
+ test.earliestFixed, test.vulnRange, got, want)
+ }
+ }
+}
diff --git a/internal/worker/worker.go b/internal/worker/worker.go
index f29175f..a2e9b7b 100644
--- a/internal/worker/worker.go
+++ b/internal/worker/worker.go
@@ -22,7 +22,6 @@
"golang.org/x/sync/errgroup"
"golang.org/x/time/rate"
vulnc "golang.org/x/vuln/client"
- "golang.org/x/vulndb/internal"
"golang.org/x/vulndb/internal/cvelistrepo"
"golang.org/x/vulndb/internal/cveschema"
"golang.org/x/vulndb/internal/derrors"
@@ -363,7 +362,7 @@
func newGHSABody(sr storeRecord) (string, error) {
sa := sr.(*store.GHSARecord).GHSA
- r := ghsaToReport(sa)
+ r := report.GHSAToReport(sa, "")
rs, err := r.ToString()
if err != nil {
return "", err
@@ -384,89 +383,6 @@
return b.String(), nil
}
-func ghsaToReport(sa *ghsa.SecurityAdvisory) *report.Report {
- u := sa.UpdatedAt
- r := &report.Report{
- GHSAs: []string{sa.PrettyID()},
- Description: sa.Description,
- Published: sa.PublishedAt,
- LastModified: &u,
- }
- if len(sa.Vulns) == 0 {
- return r
- }
- r.Package = sa.Vulns[0].Package
- r.Versions = versions(sa.Vulns[0].EarliestFixedVersion, sa.Vulns[0].VulnerableVersionRange)
- for _, v := range sa.Vulns[1:] {
- var a report.Additional
- a.Package = v.Package
- a.Versions = versions(v.EarliestFixedVersion, v.VulnerableVersionRange)
- r.AdditionalPackages = append(r.AdditionalPackages, a)
- }
- return r
-}
-
-func versions(earliestFixed, vulnRange string) []report.VersionRange {
- // Don't try to be fully general here. Handle the common cases (which, as of
- // March 2022, are the only cases), and let a person handle the others.
- items, err := parseVulnRange(vulnRange)
- if err != nil {
- return []report.VersionRange{{
- Introduced: fmt.Sprintf("TODO (got error %q)", err),
- }}
- }
-
- var intro, fixed string
-
- // Most common case: a single "<" item with a version that matches earliestFixed.
- if len(items) == 1 && items[0].op == "<" && items[0].version == earliestFixed {
- intro = "v0.0.0"
- fixed = "v" + earliestFixed
- }
-
- // Two items, one >= and one <, with the latter matching earliestFixed.
- if len(items) == 2 && items[0].op == ">=" && items[1].op == "<" && items[1].version == earliestFixed {
- intro = "v" + items[0].version
- fixed = "v" + earliestFixed
- }
-
- // A single "<=" item with no fixed version.
- if len(items) == 1 && items[0].op == "<=" && earliestFixed == "" {
- intro = "v0.0.0"
- }
-
- if intro == "" {
- intro = fmt.Sprintf("TODO (earliest fixed %q, vuln range %q)", earliestFixed, vulnRange)
- }
- return []report.VersionRange{{Introduced: intro, Fixed: fixed}}
-}
-
-type vulnRangeItem struct {
- op, version string
-}
-
-// parseVulnRange splits the contents of a GitHub Security Advisory's
-// VulnerableVersionRange field into separate items.
-func parseVulnRange(s string) ([]vulnRangeItem, error) {
- // A GHSA vuln range is a comma-separated list of items of the form "OP VERSION"
- // where OP is one of "<", ">", "<=" or ">=" and VERSION is a semantic
- // version.
- var items []vulnRangeItem
- parts := strings.Split(s, ",")
- for _, p := range parts {
- p = strings.TrimSpace(p)
- if p == "" {
- continue
- }
- before, after, found := internal.Cut(p, " ")
- if !found {
- return nil, fmt.Errorf("invalid vuln range item %q", p)
- }
- items = append(items, vulnRangeItem{strings.TrimSpace(before), strings.TrimSpace(after)})
- }
- return items, nil
-}
-
func vulnTable(vs []*ghsa.Vuln) string {
var b strings.Builder
fmt.Fprintf(&b, "| Unit | Fixed | Vulnerable Ranges |\n")
diff --git a/internal/worker/worker_test.go b/internal/worker/worker_test.go
index 73ceb72..13531a7 100644
--- a/internal/worker/worker_test.go
+++ b/internal/worker/worker_test.go
@@ -24,7 +24,6 @@
"golang.org/x/vulndb/internal/ghsa"
"golang.org/x/vulndb/internal/gitrepo"
"golang.org/x/vulndb/internal/issues"
- "golang.org/x/vulndb/internal/report"
"golang.org/x/vulndb/internal/worker/log"
"golang.org/x/vulndb/internal/worker/store"
)
@@ -273,11 +272,13 @@
` + "```" + `
package: aPackage
versions:
- - introduced: v0.0.0
- fixed: v1.2.3
+ - fixed: v1.2.3
description: a description
ghsas:
- G1
+links:
+ context:
+ - https://github.com/permalink/to/G1
` + "```"
@@ -421,51 +422,3 @@
}
}
}
-
-func TestParseVulnRange(t *testing.T) {
- for _, test := range []struct {
- in string
- want []vulnRangeItem
- }{
- {"", nil},
- {"< 1.2.3", []vulnRangeItem{{"<", "1.2.3"}}},
- {"< 4.3.2, >= 1.2.3", []vulnRangeItem{
- {"<", "4.3.2"},
- {">=", "1.2.3"},
- }},
- } {
- got, err := parseVulnRange(test.in)
- if err != nil {
- t.Fatal(err)
- }
- if !cmp.Equal(got, test.want, cmp.AllowUnexported(vulnRangeItem{})) {
- t.Errorf("%q:\ngot %+v\nwant %+v", test.in, got, test.want)
- }
- }
-}
-
-func TestVersions(t *testing.T) {
- for _, test := range []struct {
- earliestFixed string
- vulnRange string
- intro, fixed string
- }{
- {"1.0.0", "< 1.0.0", "v0.0.0", "v1.0.0"},
- {"", "<= 1.4.2", "v0.0.0", ""},
- {"1.1.3", ">= 1.1.0, < 1.1.3", "v1.1.0", "v1.1.3"},
- {
- "1.2.3", "<= 2.3.4",
- `TODO (earliest fixed "1.2.3", vuln range "<= 2.3.4")`, "",
- },
- } {
- got := versions(test.earliestFixed, test.vulnRange)
- want := []report.VersionRange{{
- Introduced: test.intro,
- Fixed: test.fixed,
- }}
- if !cmp.Equal(got, want) {
- t.Errorf("%q, %q:\ngot %+v\nwant %+v",
- test.earliestFixed, test.vulnRange, got, want)
- }
- }
-}