Skip to content

Commit f8934e9

Browse files
committed
internal/report: add new status NEEDS_REVIEW
Add a new YAML report status, NEEDS_REVIEW, which indicates that a report has been automatically generated but needs to be reviewed by a human later. The goal of this new status is to allow us to quickly publish initial versions of *most* reports that will require review. A report with status NEEDS_REVIEW has slightly stricter requirements than UNREVIEWED reports: - NEEDS_REVIEW reports must have a fixed version for each affected module - NEEDS_REVIEW reports must not have any "unsupported_versions" These stricter requirements prevent us from publishing low-information reports that could affect many users. Auto-generated reports that do not meet these requirements need to be manually reviewed by a human. When a new NEEDS_REVIEW report is committed, the automatically generated commit message includes "Updates #NNN" for the corresponding issue instead of "Fixes #NNN", because additional action is still needed. NEEDS_REVIEW is an internal status only - it is converted to UNREVIEWED when published to OSV. Change-Id: I340279f5a3f73e508b145f613d3d07d71e870aaa Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/626157 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Damien Neil <[email protected]>
1 parent c5e69da commit f8934e9

19 files changed

+399
-195
lines changed

all_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,10 @@ func TestLintReports(t *testing.T) {
140140
// This can happen because the initial quick triage algorithm
141141
// doesn't know about all affected modules - just the one
142142
// listed in the Github issue.
143-
if r.IsUnreviewed() && !r.UnreviewedOK {
143+
if r.IsUnreviewed() && !r.IsExcluded() && !r.UnreviewedOK {
144144
pr, _ := priority.AnalyzeReport(r, rc, modulesToImports)
145145
if pr.Priority == priority.High {
146-
t.Errorf("UNREVIEWED report %s is high priority (should be REVIEWED) - reason: %s", filename, pr.Reason)
146+
t.Errorf("UNREVIEWED report %s is high priority (should be NEEDS_REVIEW or REVIEWED) - reason: %s", filename, pr.Reason)
147147
}
148148
}
149149
// Check that a correct OSV file was generated for each YAML report.

cmd/vulnreport/commit.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ func actionPhrases(status git.Status, r *yamlReport) (reportAction, issueAction
217217
// Update instead of fixing the issue because we still need
218218
// to manually publish the CVE record after submitting the CL.
219219
return addReportAction, updateIssueAction, nil
220+
case r.NeedsReview():
221+
// Update instead of fixing the issue because we still need
222+
// to review the report later.
223+
return addReportAction, updateIssueAction, nil
220224
default:
221225
return addReportAction, fixIssueAction, nil
222226
}

cmd/vulnreport/creator.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,20 +127,23 @@ func reviewStatusOf(iss *issues.Issue, reviewStatus report.ReviewStatus) report.
127127
// If a valid review status is provided, it overrides the priority label.
128128
if reviewStatus != 0 {
129129
if d != reviewStatus {
130-
log.Warnf("issue #%d: should be %s based on label(s) but this was overridden with the -status=%s flag", iss.Number, d, reviewStatus)
130+
log.Warnf("issue #%d: would be %s based on label(s) but this was overridden with the -status=%s flag", iss.Number, d, reviewStatus)
131131
}
132132
return reviewStatus
133133
}
134134
return d
135135
}
136136

137137
func defaultReviewStatus(iss *issues.Issue) report.ReviewStatus {
138-
if iss.HasLabel(labelHighPriority) ||
139-
iss.HasLabel(labelDirect) ||
138+
if iss.HasLabel(labelDirect) ||
140139
iss.HasLabel(labelFirstParty) {
141140
return report.Reviewed
142141
}
143142

143+
if iss.HasLabel(labelHighPriority) {
144+
return report.NeedsReview
145+
}
146+
144147
return report.Unreviewed
145148
}
146149

@@ -161,6 +164,7 @@ func (c *creator) metaToSource(ctx context.Context, meta *reportMeta) report.Sou
161164
}
162165

163166
func (c *creator) rawReport(ctx context.Context, meta *reportMeta) *report.Report {
167+
log.Infof("%s: creating new %s report", meta.id, meta.reviewStatus)
164168
return report.New(c.metaToSource(ctx, meta), c.pxc,
165169
report.WithGoID(meta.id),
166170
report.WithModulePath(meta.modulePath),
@@ -195,12 +199,11 @@ func (c *creator) reportFromMeta(ctx context.Context, meta *reportMeta) (*yamlRe
195199
// The initial quick triage algorithm doesn't know about all
196200
// affected modules, so double check the priority after the
197201
// report is created.
198-
if raw.IsUnreviewed() {
202+
if raw.IsUnreviewed() && !raw.IsExcluded() {
199203
pr, _ := c.reportPriority(raw)
200204
if pr.Priority == priority.High {
201-
log.Warnf("%s: re-generating; vuln is high priority and should be REVIEWED; reason: %s", raw.ID, pr.Reason)
202-
meta.reviewStatus = report.Reviewed
203-
raw = c.rawReport(ctx, meta)
205+
log.Warnf("%s: vuln is high priority and should be NEEDS_REVIEW or REVIEWED; reason: %s", raw.ID, pr.Reason)
206+
raw.ReviewStatus = report.NeedsReview
204207
}
205208
}
206209

@@ -239,7 +242,7 @@ func (c *creator) reportFromMeta(ctx context.Context, meta *reportMeta) (*yamlRe
239242
switch {
240243
case raw.IsExcluded():
241244
// nothing
242-
case raw.IsUnreviewed():
245+
case !raw.IsReviewed():
243246
r.removeUnreachableRefs()
244247
default:
245248
// Regular, full-length reports.

doc/format.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,12 +424,19 @@ type `string`
424424

425425
**required**
426426

427-
The status of this report, either UNREVIEWED or REVIEWED.
427+
The status of this report, either UNREVIEWED, NEEDS_REVIEW, or REVIEWED.
428428

429429
Unreviewed reports are generally auto-generated or nearly so. Their
430430
details have not been verified and no attempt has been made to determine
431431
packages or symbols. These reports must have an advisory link.
432432

433+
Needs review reports are like unreviewed reports in that they are auto-generated,
434+
but we plan to review them in the near future. They have stricter requirements
435+
for version data than unreviewed reports. The NEEDS_REVIEW status allows us to
436+
quickly publish initial versions of most reports that will require review.
437+
NEEDS_REVIEW is an internal status only: it is converted to UNREVIEWED when
438+
published to OSV.
439+
433440
Reviewed reports are reports for which we have made a good faith effort
434441
to determine correct affected versions, packages, and symbols. (In some
435442
cases it is not possible to determine all of these, which is OK). Descriptions

internal/database/new.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"fmt"
99

1010
"golang.org/x/vulndb/internal/osv"
11-
"golang.org/x/vulndb/internal/version"
11+
"golang.org/x/vulndb/internal/osvutils"
1212
)
1313

1414
// New creates a new database from the given entries.
@@ -63,7 +63,7 @@ func (m *ModulesIndex) add(entry osv.Entry) {
6363
module.Vulns = append(module.Vulns, ModuleVuln{
6464
ID: entry.ID,
6565
Modified: entry.Modified,
66-
Fixed: latestFixedVersion(affected.Ranges),
66+
Fixed: osvutils.LatestFixed(affected.Ranges),
6767
})
6868
}
6969
}
@@ -79,25 +79,3 @@ func (v *VulnsIndex) add(entry osv.Entry) error {
7979
}
8080
return nil
8181
}
82-
83-
func latestFixedVersion(ranges []osv.Range) string {
84-
var latestFixed string
85-
for _, r := range ranges {
86-
if r.Type == osv.RangeTypeSemver {
87-
for _, e := range r.Events {
88-
if fixed := e.Fixed; fixed != "" && version.Before(latestFixed, fixed) {
89-
latestFixed = fixed
90-
}
91-
}
92-
// If the vulnerability was re-introduced after the latest fix
93-
// we found, there is no latest fix for this range.
94-
for _, e := range r.Events {
95-
if introduced := e.Introduced; introduced != "" && introduced != "0" && version.Before(latestFixed, introduced) {
96-
latestFixed = ""
97-
break
98-
}
99-
}
100-
}
101-
}
102-
return string(latestFixed)
103-
}

internal/database/new_test.go

Lines changed: 0 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -126,132 +126,3 @@ func TestNew(t *testing.T) {
126126
t.Errorf("New: unexpected diff (-want, +got):\n%v", diff)
127127
}
128128
}
129-
130-
func TestLatestFixedVersion(t *testing.T) {
131-
tests := []struct {
132-
name string
133-
ranges []osv.Range
134-
want string
135-
}{
136-
{
137-
name: "empty",
138-
ranges: []osv.Range{},
139-
want: "",
140-
},
141-
{
142-
name: "no fix",
143-
ranges: []osv.Range{{
144-
Type: osv.RangeTypeSemver,
145-
Events: []osv.RangeEvent{
146-
{
147-
Introduced: "0",
148-
},
149-
},
150-
}},
151-
want: "",
152-
},
153-
{
154-
name: "no latest fix",
155-
ranges: []osv.Range{{
156-
Type: osv.RangeTypeSemver,
157-
Events: []osv.RangeEvent{
158-
{Introduced: "0"},
159-
{Fixed: "1.0.4"},
160-
{Introduced: "1.1.2"},
161-
},
162-
}},
163-
want: "",
164-
},
165-
{
166-
name: "unsorted no latest fix",
167-
ranges: []osv.Range{{
168-
Type: osv.RangeTypeSemver,
169-
Events: []osv.RangeEvent{
170-
{Fixed: "1.0.4"},
171-
{Introduced: "0"},
172-
{Introduced: "1.1.2"},
173-
{Introduced: "1.5.0"},
174-
{Fixed: "1.1.4"},
175-
},
176-
}},
177-
want: "",
178-
},
179-
{
180-
name: "unsorted with fix",
181-
ranges: []osv.Range{{
182-
Type: osv.RangeTypeSemver,
183-
Events: []osv.RangeEvent{
184-
{
185-
Fixed: "1.0.0",
186-
},
187-
{
188-
Introduced: "0",
189-
},
190-
{
191-
Fixed: "0.1.0",
192-
},
193-
{
194-
Introduced: "0.5.0",
195-
},
196-
},
197-
}},
198-
want: "1.0.0",
199-
},
200-
{
201-
name: "multiple ranges",
202-
ranges: []osv.Range{{
203-
Type: osv.RangeTypeSemver,
204-
Events: []osv.RangeEvent{
205-
{
206-
Introduced: "0",
207-
},
208-
{
209-
Fixed: "0.1.0",
210-
},
211-
},
212-
},
213-
{
214-
Type: osv.RangeTypeSemver,
215-
Events: []osv.RangeEvent{
216-
{
217-
Introduced: "0",
218-
},
219-
{
220-
Fixed: "0.2.0",
221-
},
222-
},
223-
}},
224-
want: "0.2.0",
225-
},
226-
{
227-
name: "pseudoversion",
228-
ranges: []osv.Range{{
229-
Type: osv.RangeTypeSemver,
230-
Events: []osv.RangeEvent{
231-
{
232-
Introduced: "0",
233-
},
234-
{
235-
Fixed: "0.0.0-20220824120805-abc",
236-
},
237-
{
238-
Introduced: "0.0.0-20230824120805-efg",
239-
},
240-
{
241-
Fixed: "0.0.0-20240824120805-hij",
242-
},
243-
},
244-
}},
245-
want: "0.0.0-20240824120805-hij",
246-
},
247-
}
248-
249-
for _, test := range tests {
250-
t.Run(test.name, func(t *testing.T) {
251-
got := latestFixedVersion(test.ranges)
252-
if got != test.want {
253-
t.Errorf("latestFixedVersion = %q, want %q", got, test.want)
254-
}
255-
})
256-
}
257-
}

internal/osv/review_status.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ func (r ReviewStatus) String() string {
3030
return statusStrs[r]
3131
}
3232

33-
func ReviewStatusValues() []string {
34-
return statusStrs[1:]
35-
}
36-
3733
func (r ReviewStatus) IsValid() bool {
3834
return int(r) >= 0 && int(r) < len(statusStrs)
3935
}

internal/osvutils/fixed.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package osvutils
6+
7+
import (
8+
"golang.org/x/vulndb/internal/osv"
9+
"golang.org/x/vulndb/internal/version"
10+
)
11+
12+
func LatestFixed(ranges []osv.Range) string {
13+
var latestFixed string
14+
for _, r := range ranges {
15+
if r.Type == osv.RangeTypeSemver {
16+
for _, e := range r.Events {
17+
if fixed := e.Fixed; fixed != "" && version.Before(latestFixed, fixed) {
18+
latestFixed = fixed
19+
}
20+
}
21+
// If the vulnerability was re-introduced after the latest fix
22+
// we found, there is no latest fix for this range.
23+
for _, e := range r.Events {
24+
if introduced := e.Introduced; introduced != "" && introduced != "0" && version.Before(latestFixed, introduced) {
25+
latestFixed = ""
26+
break
27+
}
28+
}
29+
}
30+
}
31+
return string(latestFixed)
32+
}

0 commit comments

Comments
 (0)