internal,static: add module path search for vulnerabilities
Adds support for searching by mobule path prefix. For example a search
for 'net' will match with vulns for paths net, net/http, net/http/cgi.
For golang/go#54802.
Change-Id: I89543fd02d8861b8676fe4c552f7f57e436e945e
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/432418
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/internal/frontend/search.go b/internal/frontend/search.go
index 1d3b8c1..01cf346 100644
--- a/internal/frontend/search.go
+++ b/internal/frontend/search.go
@@ -121,6 +121,10 @@
if action != nil || err != nil {
return action, err
}
+ action, err = searchVulnModule(ctx, mode, cq, vulnClient)
+ if action != nil || err != nil {
+ return action, err
+ }
var symbol string
if len(filters) > 0 {
symbol = filters[0]
@@ -353,7 +357,7 @@
return fmt.Sprintf("/vuln/%s?q", query)
}
requestedPath := path.Clean(query)
- if !strings.Contains(requestedPath, "/") {
+ if !strings.Contains(requestedPath, "/") || mode == searchModeVuln {
return ""
}
_, err := ds.GetUnitMeta(ctx, requestedPath, internal.UnknownModulePath, version.Latest)
@@ -366,6 +370,36 @@
return fmt.Sprintf("/%s", requestedPath)
}
+func searchVulnModule(ctx context.Context, mode, cq string, client vulnc.Client) (_ *searchAction, err error) {
+ if mode != searchModeVuln {
+ return nil, nil
+ }
+ allEntries, err := vulnList(ctx, client)
+ if err != nil {
+ return nil, err
+ }
+ prefix := cq + "/"
+ var entries []OSVEntry
+EntryLoop:
+ for _, entry := range allEntries {
+ for _, aff := range entry.Affected {
+ for _, imp := range aff.EcosystemSpecific.Imports {
+ if imp.Path == cq || strings.HasPrefix(imp.Path, prefix) {
+ entries = append(entries, entry)
+ continue EntryLoop
+ }
+ }
+ }
+ }
+ // Sort from most to least recent.
+ sort.Slice(entries, func(i, j int) bool { return entries[i].ID > entries[j].ID })
+ return &searchAction{
+ title: fmt.Sprintf("%s - Vulnerability Reports", cq),
+ template: "vuln/list",
+ page: &VulnListPage{Entries: entries},
+ }, nil
+}
+
func searchVulnAlias(ctx context.Context, mode, cq string, vulnClient vulnc.Client) (_ *searchAction, err error) {
defer derrors.Wrap(&err, "searchVulnAlias(%q, %q)", mode, cq)
diff --git a/internal/frontend/search_test.go b/internal/frontend/search_test.go
index d1e9bbf..c335323 100644
--- a/internal/frontend/search_test.go
+++ b/internal/frontend/search_test.go
@@ -112,6 +112,11 @@
wantTemplate: "vuln/list",
},
{
+ name: "vuln module path",
+ query: "q=golang.org/x/net&m=vuln",
+ wantTemplate: "vuln/list",
+ },
+ {
// We turn on vuln mode if the query matches a vuln alias.
name: "vuln alias not vuln mode",
query: "q=GHSA-aaaa-bbbb-cccc",
@@ -617,6 +622,69 @@
}
}
+func TestSearchVulnModulePath(t *testing.T) {
+ vc := newVulndbTestClient(testEntries)
+ for _, test := range []struct {
+ name string
+ mode string
+ query string
+ wantPage *VulnListPage
+ wantURL string
+ wantErr bool
+ }{
+ {
+ name: "not vuln mode",
+ mode: searchModePackage,
+ query: "doesn't matter",
+ wantPage: nil,
+ wantURL: "",
+ wantErr: false,
+ },
+ {
+ name: "no match",
+ mode: searchModeVuln,
+ query: "example",
+ wantPage: &VulnListPage{Entries: nil},
+ },
+ {
+ name: "prefix match",
+ mode: searchModeVuln,
+ query: "example.com/org",
+ wantPage: &VulnListPage{Entries: []OSVEntry{
+ {testEntries[7]},
+ }},
+ },
+ {
+ name: "path match",
+ mode: searchModeVuln,
+ query: "example.com/org/path",
+ wantPage: &VulnListPage{Entries: []OSVEntry{
+ {testEntries[7]},
+ }},
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ gotAction, err := searchVulnModule(context.Background(), test.mode, test.query, vc)
+ if (err != nil) != test.wantErr {
+ t.Fatalf("got %v, want error %t", err, test.wantErr)
+ }
+ var wantAction *searchAction
+ if test.wantURL != "" {
+ wantAction = &searchAction{redirectURL: test.wantURL}
+ } else if test.wantPage != nil {
+ wantAction = &searchAction{
+ title: test.query + " - Vulnerability Reports",
+ template: "vuln/list",
+ page: test.wantPage,
+ }
+ }
+ if !cmp.Equal(gotAction, wantAction, cmp.AllowUnexported(searchAction{}), cmpopts.IgnoreUnexported(VulnListPage{})) {
+ t.Errorf("\ngot %+v\nwant %+v", gotAction, wantAction)
+ }
+ })
+ }
+}
+
func TestElapsedTime(t *testing.T) {
now := sample.NowTruncated()
testCases := []struct {
diff --git a/internal/frontend/vulns.go b/internal/frontend/vulns.go
index e797a20..144dd6e 100644
--- a/internal/frontend/vulns.go
+++ b/internal/frontend/vulns.go
@@ -221,14 +221,22 @@
}
func newVulnListPage(ctx context.Context, client vulnc.Client) (*VulnListPage, error) {
+ entries, err := vulnList(ctx, client)
+ if err != nil {
+ return nil, err
+ }
+ // Sort from most to least recent.
+ sort.Slice(entries, func(i, j int) bool { return entries[i].ID > entries[j].ID })
+ return &VulnListPage{Entries: entries}, nil
+}
+
+func vulnList(ctx context.Context, client vulnc.Client) ([]OSVEntry, error) {
const concurrency = 4
ids, err := client.ListIDs(ctx)
if err != nil {
return nil, err
}
- // Sort from most to least recent.
- sort.Slice(ids, func(i, j int) bool { return ids[i] > ids[j] })
entries := make([]OSVEntry, len(ids))
sem := make(chan struct{}, concurrency)
@@ -250,7 +258,7 @@
if err := g.Wait(); err != nil {
return nil, err
}
- return &VulnListPage{Entries: entries}, nil
+ return entries, nil
}
// aliasLinks generates links to reference pages for vuln aliases.
diff --git a/internal/frontend/vulns_test.go b/internal/frontend/vulns_test.go
index e278626..9614c07 100644
--- a/internal/frontend/vulns_test.go
+++ b/internal/frontend/vulns_test.go
@@ -71,6 +71,19 @@
{ID: "GO-1991-05", Details: "e"},
{ID: "GO-1991-23", Details: "f"},
{ID: "GO-1991-30", Details: "g"},
+ {
+ ID: "GO-1991-31",
+ Details: "h",
+ Affected: []osv.Affected{{
+ EcosystemSpecific: osv.EcosystemSpecific{
+ Imports: []osv.EcosystemSpecificImport{
+ {
+ Path: "example.com/org/path",
+ },
+ },
+ },
+ }},
+ },
}
func TestNewVulnListPage(t *testing.T) {
diff --git a/static/frontend/vuln/list/list.css b/static/frontend/vuln/list/list.css
index 155f944..13a63a6 100644
--- a/static/frontend/vuln/list/list.css
+++ b/static/frontend/vuln/list/list.css
@@ -4,6 +4,10 @@
* license that can be found in the LICENSE file.
*/
+/* Hide the search form in the header. */
+.go-SearchForm {
+ display: none;
+}
.VulnList-title {
font-size: 1.25rem;
font-weight: 400;
@@ -11,3 +15,10 @@
.VulnList-details {
margin-bottom: 1.75rem;
}
+.VulnList-details p {
+ word-break: break-word;
+}
+.VulnList-search {
+ max-width: 32rem;
+ margin-bottom: 1rem;
+}
diff --git a/static/frontend/vuln/list/list.min.css b/static/frontend/vuln/list/list.min.css
index 962b716..03cbced 100644
--- a/static/frontend/vuln/list/list.min.css
+++ b/static/frontend/vuln/list/list.min.css
@@ -3,5 +3,5 @@
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
-.VulnList-title{font-size:1.25rem;font-weight:400}.VulnList-details{margin-bottom:1.75rem}
+.go-SearchForm{display:none}.VulnList-title{font-size:1.25rem;font-weight:400}.VulnList-details{margin-bottom:1.75rem}.VulnList-details p{word-break:break-word}.VulnList-search{max-width:32rem;margin-bottom:1rem}
/*# sourceMappingURL=list.min.css.map */
diff --git a/static/frontend/vuln/list/list.min.css.map b/static/frontend/vuln/list/list.min.css.map
index 610b42a..af6960b 100644
--- a/static/frontend/vuln/list/list.min.css.map
+++ b/static/frontend/vuln/list/list.min.css.map
@@ -1,7 +1,7 @@
{
"version": 3,
"sources": ["list.css"],
- "sourcesContent": ["/*\n * Copyright 2021 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n.VulnList-title {\n font-size: 1.25rem;\n font-weight: 400;\n}\n.VulnList-details {\n margin-bottom: 1.75rem;\n}\n"],
- "mappings": ";;;;;AAMA,gBACE,kBACA,gBAEF,kBACE",
+ "sourcesContent": ["/*\n * Copyright 2021 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n/* Hide the search form in the header. */\n.go-SearchForm {\n display: none;\n}\n.VulnList-title {\n font-size: 1.25rem;\n font-weight: 400;\n}\n.VulnList-details {\n margin-bottom: 1.75rem;\n}\n.VulnList-details p {\n word-break: break-word;\n}\n.VulnList-search {\n max-width: 32rem;\n margin-bottom: 1rem;\n}\n"],
+ "mappings": ";;;;;AAOA,eACE,aAEF,gBACE,kBACA,gBAEF,kBACE,sBAEF,oBACE,sBAEF,iBACE,gBACA",
"names": []
}
diff --git a/static/frontend/vuln/list/list.tmpl b/static/frontend/vuln/list/list.tmpl
index 913e731..4f90665 100644
--- a/static/frontend/vuln/list/list.tmpl
+++ b/static/frontend/vuln/list/list.tmpl
@@ -15,22 +15,47 @@
<a href="/vuln" data-gtmc="breadcrumb link">Vulnerability Database</a>
</li>
<li>
- <a href="/vuln/list" data-gtmc="breadcrumb link" aria-current="location">All Reports</a>
+ <a href="/vuln/list" data-gtmc="breadcrumb link" {{if not .Query}}aria-current="location"{{end}}>All Reports</a>
</li>
+ {{with .Query}}
+ <li>
+ <a href="/search?q={{.}}&m=vuln" data-gtmc="breadcrumb link" aria-current="location">{{.}}</a>
+ </li>
+ {{end}}
</ol>
</nav>
- <h1 class="Vuln-title">Vulnerability Reports</h1>
- {{range .Entries}}
- <h2 class="VulnList-title">
- <a href="/vuln/{{.ID}}">{{.ID}}</a>
- </h2>
- <div class="VulnList-details">
- {{template "vuln-details" .}}
- </div>
+ <h1 class="Vuln-title">Vulnerability Reports{{with .Query}} – {{.}}{{end}}</h1>
+ <form
+ class="go-InputGroup VulnList-search"
+ action="/search"
+ data-gtmc="search vuln"
+ aria-label="Search by GO ID, alias, or import path"
+ role="search"
+ >
+ <input name="q" class="go-Input" placeholder="Search by GO ID, alias, or import path" value="{{.Query}}" />
+ <input name="m" value="vuln" hidden />
+ <button class="go-Button">Submit</button>
+ </form>
+ {{if not .Entries}}
+ <p>No reports found. <a href="/vuln/list">View all reports.</a></p>
+ {{else}}
+ {{range .Entries}}
+ <h2 class="VulnList-title">
+ <a href="/vuln/{{.ID}}">{{.ID}}</a>
+ </h2>
+ <div class="VulnList-details">
+ {{template "vuln-details" .}}
+ </div>
+ {{end}}
{{end}}
- <div><br>If you don't see an existing, public Go vulnerability in a publicly importable package in our database,
- <a href="https://github.com/golang/vulndb/issues/new?assignees=&labels=Needs+Triage%2CDirect+External+Report&template=new_third_party_vuln.yml&title=x%2Fvulndb%3A+potential+Go+vuln+in+%3Cpackage%3E">
- please let us know.
- </a>
- </div>
+ <p>
+ {{if and .Query .Entries}}
+ Didn't find what you were looking for? <a href="/vuln/list">View all reports.</a>
+ {{else}}
+ If you don't see an existing, public Go vulnerability in a publicly importable package in our database,
+ <a href="https://github.com/golang/vulndb/issues/new?assignees=&labels=Needs+Triage%2CDirect+External+Report&template=new_third_party_vuln.yml&title=x%2Fvulndb%3A+potential+Go+vuln+in+%3Cpackage%3E">
+ please let us know.
+ </a>
+ {{end}}
+ </p>
{{end}}
diff --git a/static/frontend/vuln/main/main.css b/static/frontend/vuln/main/main.css
index 0e7c514..18fddf2 100644
--- a/static/frontend/vuln/main/main.css
+++ b/static/frontend/vuln/main/main.css
@@ -4,7 +4,11 @@
* license that can be found in the LICENSE file.
*/
- .VulnMain-title {
+/* Hide the search form in the header. */
+.go-SearchForm {
+ display: none;
+}
+.VulnMain-title {
font-size: 1.25rem;
font-weight: 400;
margin: 0 0 0.5rem;
@@ -15,6 +19,9 @@
.VulnMain-details {
margin-bottom: 1.75rem;
}
+.VulnMain-details p {
+ word-break: break-word;
+}
.VulnMain-search {
max-width: 32rem;
}
diff --git a/static/frontend/vuln/main/main.min.css b/static/frontend/vuln/main/main.min.css
index a7553d3..64c1740 100644
--- a/static/frontend/vuln/main/main.min.css
+++ b/static/frontend/vuln/main/main.min.css
@@ -3,5 +3,5 @@
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
-.VulnMain-title{font-size:1.25rem;font-weight:400;margin:0 0 .5rem}.VulnMain-recent{margin-top:.5rem}.VulnMain-details{margin-bottom:1.75rem}.VulnMain-search{max-width:32rem}.VulnMain h2{margin:1.75rem 0 .5rem}
+.go-SearchForm{display:none}.VulnMain-title{font-size:1.25rem;font-weight:400;margin:0 0 .5rem}.VulnMain-recent{margin-top:.5rem}.VulnMain-details{margin-bottom:1.75rem}.VulnMain-details p{word-break:break-word}.VulnMain-search{max-width:32rem}.VulnMain h2{margin:1.75rem 0 .5rem}
/*# sourceMappingURL=main.min.css.map */
diff --git a/static/frontend/vuln/main/main.min.css.map b/static/frontend/vuln/main/main.min.css.map
index 45ad19d..776ea3b 100644
--- a/static/frontend/vuln/main/main.min.css.map
+++ b/static/frontend/vuln/main/main.min.css.map
@@ -1,7 +1,7 @@
{
"version": 3,
"sources": ["main.css"],
- "sourcesContent": ["/*\n * Copyright 2021 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n .VulnMain-title {\n font-size: 1.25rem;\n font-weight: 400;\n margin: 0 0 0.5rem;\n}\n.VulnMain-recent {\n margin-top: 0.5rem;\n}\n.VulnMain-details {\n margin-bottom: 1.75rem;\n}\n.VulnMain-search {\n max-width: 32rem;\n}\n.VulnMain h2 {\n margin: 1.75rem 0 0.5rem;\n}\n"],
- "mappings": ";;;;;AAMC,gBACC,kBACA,gBARF,iBAWA,iBACE,iBAEF,kBACE,sBAEF,iBACE,gBAEF,aApBA",
+ "sourcesContent": ["/*\n * Copyright 2021 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n/* Hide the search form in the header. */\n.go-SearchForm {\n display: none;\n}\n.VulnMain-title {\n font-size: 1.25rem;\n font-weight: 400;\n margin: 0 0 0.5rem;\n}\n.VulnMain-recent {\n margin-top: 0.5rem;\n}\n.VulnMain-details {\n margin-bottom: 1.75rem;\n}\n.VulnMain-details p {\n word-break: break-word;\n}\n.VulnMain-search {\n max-width: 32rem;\n}\n.VulnMain h2 {\n margin: 1.75rem 0 0.5rem;\n}\n"],
+ "mappings": ";;;;;AAOA,eACE,aAEF,gBACE,kBACA,gBAZF,iBAeA,iBACE,iBAEF,kBACE,sBAEF,oBACE,sBAEF,iBACE,gBAEF,aA3BA",
"names": []
}
diff --git a/static/frontend/vuln/main/main.tmpl b/static/frontend/vuln/main/main.tmpl
index 6f17e1f..a4a9f70 100644
--- a/static/frontend/vuln/main/main.tmpl
+++ b/static/frontend/vuln/main/main.tmpl
@@ -21,10 +21,10 @@
class="go-InputGroup VulnMain-search"
action="/search"
data-gtmc="search vuln"
- aria-label="Search GO IDs"
+ aria-label="Search by GO ID, alias, or import path"
role="search"
>
- <input name="q" class="go-Input" placeholder="Search GO IDs" />
+ <input name="q" class="go-Input" placeholder="Search by GO ID, alias, or import path" />
<input name="m" value="vuln" hidden />
<button class="go-Button">Submit</button>
</form>
diff --git a/tests/screentest/testcases.ci.txt b/tests/screentest/testcases.ci.txt
index 723c107..36ab039 100644
--- a/tests/screentest/testcases.ci.txt
+++ b/tests/screentest/testcases.ci.txt
@@ -28,3 +28,13 @@
pathname /cmd/go@go1.15.0
capture viewport
capture viewport 540x1080
+
+test vuln search
+pathname /search?q=github.com%2Fbeego&m=vuln
+capture viewport
+capture viewport 540x1080
+
+test vuln no results
+pathname /search?q=github.com%2Fnoresults&m=vuln
+capture viewport
+capture viewport 540x1080
diff --git a/tests/screentest/testdata/ci/vuln-540x1080.a.png b/tests/screentest/testdata/ci/vuln-540x1080.a.png
index c437c47..c02abe3 100644
--- a/tests/screentest/testdata/ci/vuln-540x1080.a.png
+++ b/tests/screentest/testdata/ci/vuln-540x1080.a.png
Binary files differ
diff --git a/tests/screentest/testdata/ci/vuln-list-540x1080.a.png b/tests/screentest/testdata/ci/vuln-list-540x1080.a.png
index e1498c7..48def1e 100644
--- a/tests/screentest/testdata/ci/vuln-list-540x1080.a.png
+++ b/tests/screentest/testdata/ci/vuln-list-540x1080.a.png
Binary files differ
diff --git a/tests/screentest/testdata/ci/vuln-list.a.png b/tests/screentest/testdata/ci/vuln-list.a.png
index eb8bf03..1c4d5ff 100644
--- a/tests/screentest/testdata/ci/vuln-list.a.png
+++ b/tests/screentest/testdata/ci/vuln-list.a.png
Binary files differ
diff --git a/tests/screentest/testdata/ci/vuln-no-results-540x1080.a.png b/tests/screentest/testdata/ci/vuln-no-results-540x1080.a.png
new file mode 100644
index 0000000..0c083ff
--- /dev/null
+++ b/tests/screentest/testdata/ci/vuln-no-results-540x1080.a.png
Binary files differ
diff --git a/tests/screentest/testdata/ci/vuln-no-results.a.png b/tests/screentest/testdata/ci/vuln-no-results.a.png
new file mode 100644
index 0000000..8b53be8
--- /dev/null
+++ b/tests/screentest/testdata/ci/vuln-no-results.a.png
Binary files differ
diff --git a/tests/screentest/testdata/ci/vuln-search-540x1080.a.png b/tests/screentest/testdata/ci/vuln-search-540x1080.a.png
new file mode 100644
index 0000000..c5dc810
--- /dev/null
+++ b/tests/screentest/testdata/ci/vuln-search-540x1080.a.png
Binary files differ
diff --git a/tests/screentest/testdata/ci/vuln-search.a.png b/tests/screentest/testdata/ci/vuln-search.a.png
new file mode 100644
index 0000000..d824138
--- /dev/null
+++ b/tests/screentest/testdata/ci/vuln-search.a.png
Binary files differ
diff --git a/tests/screentest/testdata/ci/vuln.a.png b/tests/screentest/testdata/ci/vuln.a.png
index 64dc206..6238360 100644
--- a/tests/screentest/testdata/ci/vuln.a.png
+++ b/tests/screentest/testdata/ci/vuln.a.png
Binary files differ
diff --git a/tests/screentest/testdata/vulndb/index.json b/tests/screentest/testdata/vulndb/index.json
index c197360..3fe0721 100644
--- a/tests/screentest/testdata/vulndb/index.json
+++ b/tests/screentest/testdata/vulndb/index.json
@@ -1,4 +1,6 @@
{
"github.com/BeeGo/beego": "2022-08-23T19:54:38Z",
- "github.com/tidwall/gjson": "2022-08-23T19:54:38Z"
+ "github.com/tidwall/gjson": "2022-08-23T19:54:38Z",
+ "stdlib": "2022-08-23T19:54:38Z",
+ "toolchain": "2022-08-23T19:54:38Z"
}