/
affects.go
103 lines (96 loc) · 3.11 KB
/
affects.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package rules
import (
"fmt"
"github.com/tidwall/gjson"
"regexp"
)
func CheckAffectedProduct(json *string) []ValidationError {
if gjson.Get(*json, `cveMetadata.state`).String() != "PUBLISHED" {
// REJECTED records do not list affected products
return nil
}
var errors []ValidationError
// Check if a product version exists that is marked affected or unknown
data := gjson.Get(*json, `containers.cna.affected.#.versions.#.status`)
affectedProductFound := false
for _, affect := range data.Array() {
for _, status := range affect.Array() {
status := status.String()
if status == "affected" || status == "unknown" {
affectedProductFound = true
break
}
}
if affectedProductFound {
break
}
}
// Check if a defaultStatus exists that is set to affected or unknown
if !affectedProductFound {
data := gjson.Get(*json, `containers.cna.affected.#.defaultStatus`)
data.ForEach(func(key, value gjson.Result) bool {
status := value.String()
if status == "affected" || status == "unknown" {
affectedProductFound = true
return false // stop iterating
}
return true
})
}
if !affectedProductFound {
errors = append(errors, ValidationError{
Text: "No affected product found",
JsonPath: "containers.cna.affected",
})
}
return errors
}
// Invalid version string examples:
// - "n/a" or "unspecified"
// - anything that includes whitespace, e.g. "v12.07 and earlier"
// - special characters like "<" or ","
var validVersionRe = regexp.MustCompile(`^(\*|[a-zA-Z0-9]+[-_:\.a-zA-Z0-9]*)$`)
// CheckInvalidVersion returns an array of detected version-related ValidationError findings.
// It checks that the affected.versions sub-fields are used correctly.
func CheckInvalidVersion(json *string) []ValidationError {
if gjson.Get(*json, `cveMetadata.state`).String() != "PUBLISHED" {
// REJECTED records do not list affected products
return nil
}
var errors []ValidationError
versionFields := []string{
"containers.cna.affected.#.versions.#.version",
"containers.cna.affected.#.versions.#.lessThan",
"containers.cna.affected.#.versions.#.lessThanOrEqual",
}
for _, versionField := range versionFields {
data := gjson.Get(*json, versionField)
for _, affectedVersions := range data.Array() {
for _, version := range affectedVersions.Array() {
version := version.String()
if !validVersionRe.MatchString(version) {
errors = append(errors, ValidationError{
Text: fmt.Sprintf("Invalid version string: \"%s\"", version),
JsonPath: versionField,
})
}
}
}
}
// "at" version strings are nested in another "changes" array
data := gjson.Get(*json, `containers.cna.affected.#.versions.#.changes.#.at`)
for _, affectedVersions := range data.Array() {
for _, changes := range affectedVersions.Array() {
for _, atVersion := range changes.Array() {
version := atVersion.String()
if !validVersionRe.MatchString(version) {
errors = append(errors, ValidationError{
Text: fmt.Sprintf("Invalid version string: \"%s\"", version),
JsonPath: "containers.cna.affected.#.versions.#.changes.#.at",
})
}
}
}
}
return errors
}