Skip to content

Commit 4789343

Browse files
committed
internal/genericosv: fetch GHSAs from github instead of osv.dev
Fetch GHSA OSV from github.com/github/advisory-database instead of osv.dev, as osv.dev sometimes makes edits to the OSV or has an older version of it. Unfortunately this requires making two HTTP requests: the first to determine the published year/month of the GHSA from api.github.com, and the second to pull the OSV from the GHSA database git repo. There is no way (that I am aware of) to make a direct API call to get GHSAs in OSV format. Change-Id: I8bfd580b1e8ee38f9bc6b8afb08415e0de1a3040 Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/597735 Reviewed-by: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 61369c8 commit 4789343

File tree

3 files changed

+70
-22
lines changed

3 files changed

+70
-22
lines changed

cmd/vulnreport/find_aliases.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func (a *aliasFinder) fetch(ctx context.Context, alias string) (report.Source, e
159159
// Doesn't work for test environment yet.
160160
f = a.gc.(*ghsa.Client)
161161
} else {
162-
f = genericosv.NewFetcher()
162+
f = genericosv.NewGHSAFetcher()
163163
}
164164
case idstr.IsCVE(alias):
165165
f = cve5.NewFetcher()

internal/genericosv/fetch.go

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"fmt"
1313
"io"
1414
"net/http"
15+
"time"
1516

1617
"golang.org/x/vulndb/internal/report"
1718
)
@@ -20,41 +21,86 @@ import (
2021
type Entry Vulnerability
2122

2223
func NewFetcher() report.Fetcher {
23-
return &client{http.DefaultClient, "https://api.osv.dev/v1"}
24+
return &osvDevClient{http.DefaultClient, osvDevAPI}
2425
}
2526

26-
// Fetch returns the OSV entry from the osv.dev API for the
27-
// given ID.
28-
func (c *client) Fetch(_ context.Context, id string) (report.Source, error) {
29-
return c.fetch(id)
27+
func NewGHSAFetcher() report.Fetcher {
28+
return &githubClient{http.DefaultClient, githubAPI}
3029
}
3130

32-
type client struct {
31+
const (
32+
osvDevAPI = "https://api.osv.dev/v1/vulns"
33+
githubAPI = "https://api.github.com/advisories"
34+
)
35+
36+
// Fetch returns the OSV entry from the osv.dev API for the given ID.
37+
func (c *osvDevClient) Fetch(_ context.Context, id string) (report.Source, error) {
38+
url := fmt.Sprintf("%s/%s", c.url, id)
39+
return get[Entry](c.Client, url)
40+
}
41+
42+
type githubClient struct {
3343
*http.Client
3444
url string
3545
}
3646

37-
func (c *client) fetch(id string) (*Entry, error) {
38-
url := fmt.Sprintf("%s/vulns/%s", c.url, id)
39-
req, err := http.NewRequest(http.MethodGet, url, nil)
47+
// Fetch returns the OSV entry directly from the Github advisory repo
48+
// (https://github.com/github/advisory-database).
49+
//
50+
// This unfortunately requires two HTTP requests, the first to figure
51+
// out the published date of the GHSA, and the second to fetch the OSV.
52+
//
53+
// This is because the direct Github API returns a non-OSV format,
54+
// and the OSV files are available in a Github repo whose directory
55+
// structure is determined by the published year and month of each GHSA.
56+
func (c *githubClient) Fetch(_ context.Context, id string) (report.Source, error) {
57+
url := fmt.Sprintf("%s/%s", c.url, id)
58+
sa, err := get[struct {
59+
Published *time.Time `json:"published_at,omitempty"`
60+
}](c.Client, url)
4061
if err != nil {
4162
return nil, err
4263
}
43-
resp, err := c.Do(req)
64+
if sa.Published == nil {
65+
return nil, fmt.Errorf("could not determine direct URL for GHSA OSV (need published date)")
66+
}
67+
githubURL := toGithubURL(id, sa.Published)
68+
return get[Entry](c.Client, githubURL)
69+
}
70+
71+
func toGithubURL(id string, published *time.Time) string {
72+
const base = "https://raw.githubusercontent.com/github/advisory-database/main/advisories/github-reviewed"
73+
year := published.Year()
74+
month := published.Month()
75+
return fmt.Sprintf("%s/%d/%02d/%s/%s.json", base, year, month, id, id)
76+
}
77+
78+
type osvDevClient struct {
79+
*http.Client
80+
url string
81+
}
82+
83+
func get[T any](cli *http.Client, url string) (*T, error) {
84+
var zero *T
85+
req, err := http.NewRequest(http.MethodGet, url, nil)
86+
if err != nil {
87+
return zero, err
88+
}
89+
resp, err := cli.Do(req)
4490
if err != nil {
45-
return nil, err
91+
return zero, err
4692
}
4793
defer resp.Body.Close()
4894
if resp.StatusCode != http.StatusOK {
49-
return nil, fmt.Errorf("HTTP GET %s returned unexpected status code %d", url, resp.StatusCode)
95+
return zero, fmt.Errorf("HTTP GET %s returned unexpected status code %d", url, resp.StatusCode)
5096
}
51-
var osv Entry
97+
v := new(T)
5298
body, err := io.ReadAll(resp.Body)
5399
if err != nil {
54-
return nil, err
100+
return zero, err
55101
}
56-
if err := json.Unmarshal(body, &osv); err != nil {
57-
return nil, err
102+
if err := json.Unmarshal(body, v); err != nil {
103+
return zero, err
58104
}
59-
return &osv, nil
105+
return v, nil
60106
}

internal/genericosv/fetch_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
package genericosv
66

77
import (
8+
"context"
89
"net/http"
910
"net/http/httptest"
1011
"testing"
1112

1213
"github.com/google/go-cmp/cmp"
1314
)
1415

15-
func newTestClient(expectedEndpoint, fakeResponse string) *client {
16+
func newTestClient(expectedEndpoint, fakeResponse string) *osvDevClient {
1617
handler := func(w http.ResponseWriter, r *http.Request) {
1718
if r.Method == http.MethodGet &&
1819
r.URL.Path == "/"+expectedEndpoint {
@@ -22,12 +23,13 @@ func newTestClient(expectedEndpoint, fakeResponse string) *client {
2223
w.WriteHeader(http.StatusBadRequest)
2324
}
2425
s := httptest.NewServer(http.HandlerFunc(handler))
25-
return &client{s.Client(), s.URL}
26+
return &osvDevClient{s.Client(), s.URL}
2627
}
2728

2829
func TestFetch(t *testing.T) {
29-
c := newTestClient("vulns/ID-123", `{"id":"ID-123"}`)
30-
got, err := c.fetch("ID-123")
30+
ctx := context.Background()
31+
c := newTestClient("ID-123", `{"id":"ID-123"}`)
32+
got, err := c.Fetch(ctx, "ID-123")
3133
if err != nil {
3234
t.Fatal(err)
3335
}

0 commit comments

Comments
 (0)