Skip to content

Commit 2a0603a

Browse files
author
Chris Stockton
committed
feat: fix the vulncheck-filter to parse the text format instead
The existing parser wasn't decoding any values. Once I fixed the decoding it started printing 64 results. After looking into it I realized that govulncheck cmd does some additional aggregation. It turns out that it's non-trivial, rather than try to duplicate that I just parsed the text output instead.
1 parent 52cf3d9 commit 2a0603a

2 files changed

Lines changed: 63 additions & 40 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ sec: | $(TOOL_BIN_DIR)/gosec # Check for security vulnerabilities
136136
$(CHECK_FILES)
137137

138138
vulncheck: $(TOOL_BIN_DIR)/govulncheck # Check for known vulnerabilities
139-
$(TOOL_BIN_DIR)/govulncheck -format json $(CHECK_FILES) | go run ./hack/vulncheck-filter
139+
$(TOOL_BIN_DIR)/govulncheck $(CHECK_FILES) | go run ./hack/vulncheck-filter
140140

141141
unused: | $(TOOL_BIN_DIR)/staticcheck # Look for unused code
142142
@echo "Unused code:"

hack/vulncheck-filter/main.go

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,84 @@
11
package main
22

33
import (
4-
"encoding/json"
4+
"bufio"
5+
"errors"
56
"fmt"
6-
"io"
77
"os"
8+
"slices"
9+
"strings"
810
)
911

1012
// Vulnerabilities with no upstream fix — remove entries once fixed.
1113
var ignore = map[string]string{
1214
"GO-2026-4518": "pgproto3/v2 DoS, no fix available (EOL). Transitive via pgconn v1 + pop/v6.",
1315
}
1416

15-
type message struct {
16-
Finding *struct {
17-
OSV *struct {
18-
ID string `json:"id"`
19-
} `json:"osv"`
20-
} `json:"finding"`
17+
func main() {
18+
if err := run(); err != nil {
19+
fmt.Fprintf(os.Stderr, "vulncheck-filter: %v\n", err)
20+
os.Exit(1)
21+
}
2122
}
2223

23-
func main() {
24-
dec := json.NewDecoder(os.Stdin)
24+
func run() error {
25+
const (
26+
stInit = iota
27+
stVulnOpen
28+
)
2529

26-
var unignored []string
27-
seen := make(map[string]bool)
28-
for {
29-
var m message
30-
if err := dec.Decode(&m); err != nil {
31-
if err == io.EOF {
32-
break
30+
type vuln struct {
31+
ID string `json:"id"`
32+
Text string
33+
}
34+
35+
var (
36+
cur vuln
37+
vulns []*vuln
38+
)
39+
st := stInit
40+
sc := bufio.NewScanner(os.Stdin)
41+
for sc.Scan() {
42+
v := sc.Text()
43+
switch st {
44+
case stInit:
45+
if strings.HasPrefix(v, "Vulnerability ") {
46+
st = stVulnOpen
47+
_, id, ok := strings.Cut(v, ": ")
48+
if !ok {
49+
return errors.New("no longer able to parse format")
50+
}
51+
cur = vuln{
52+
ID: id,
53+
}
54+
}
55+
case stVulnOpen:
56+
cur.Text += v + "\n"
57+
if v == "" {
58+
st = stInit
59+
cpy := cur
60+
vulns = append(vulns, &cpy)
3361
}
34-
// govulncheck JSON stream may contain objects we don't care about; skip decode errors
35-
continue
36-
}
37-
if m.Finding == nil {
38-
continue
39-
}
40-
if m.Finding.OSV == nil {
41-
continue
42-
}
43-
id := m.Finding.OSV.ID
44-
if seen[id] {
45-
continue
4662
}
47-
seen[id] = true
48-
49-
if reason, ok := ignore[id]; ok {
50-
fmt.Fprintf(os.Stderr, "ignoring %s: %s\n", id, reason)
51-
} else {
52-
fmt.Fprintf(os.Stderr, "ERROR: %s (not in ignore list)\n", id)
53-
unignored = append(unignored, id)
63+
}
64+
if err := sc.Err(); err != nil {
65+
return err
66+
}
67+
vulns = slices.DeleteFunc(vulns, func(v *vuln) bool {
68+
reason, ok := ignore[v.ID]
69+
if ok {
70+
fmt.Fprintf(os.Stderr, "ignoring %s: %s\n", v.ID, reason)
5471
}
72+
return ok
73+
})
74+
if len(vulns) == 0 {
75+
return nil
5576
}
5677

57-
if len(unignored) > 0 {
58-
fmt.Fprintf(os.Stderr, "\n%d unignored vulnerability(ies) found\n", len(unignored))
59-
os.Exit(1)
78+
fmt.Fprintf(os.Stderr, "\n")
79+
for idx, vuln := range vulns {
80+
msg := "Vulnerability #%d: %v\n%v"
81+
fmt.Fprintf(os.Stderr, msg, idx+1, vuln.ID, vuln.Text)
6082
}
83+
return fmt.Errorf("%d unignored vulnerability(ies) found", len(vulns))
6184
}

0 commit comments

Comments
 (0)