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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
# frozen_string_literal: true
module Gitlab
module Ci
module Reports
module Security
class Finding
include ::VulnerabilityFindingHelpers
attr_reader :compare_key
attr_reader :confidence
attr_reader :identifiers
attr_reader :flags
attr_reader :links
attr_reader :location
attr_reader :evidence
attr_reader :metadata_version
attr_reader :name
attr_reader :old_location
attr_reader :project_fingerprint
attr_reader :report_type
attr_reader :scanner
attr_reader :scan
attr_reader :severity
attr_accessor :uuid
attr_accessor :overridden_uuid
attr_reader :remediations
attr_reader :details
attr_reader :signatures
attr_reader :project_id
attr_reader :original_data
delegate :file_path, :start_line, :end_line, to: :location
alias_method :cve, :compare_key
def initialize(compare_key:, identifiers:, flags: [], links: [], remediations: [], location:, evidence:, metadata_version:, name:, original_data:, report_type:, scanner:, scan:, uuid:, confidence: nil, severity: nil, details: {}, signatures: [], project_id: nil, vulnerability_finding_signatures_enabled: false) # rubocop:disable Metrics/ParameterLists
@compare_key = compare_key
@confidence = confidence
@identifiers = identifiers
@flags = flags
@links = links
@location = location
@evidence = evidence
@metadata_version = metadata_version
@name = name
@original_data = original_data
@report_type = report_type
@scanner = scanner
@scan = scan
@severity = severity
@uuid = uuid
@remediations = remediations
@details = details
@signatures = signatures
@project_id = project_id
@vulnerability_finding_signatures_enabled = vulnerability_finding_signatures_enabled
@project_fingerprint = generate_project_fingerprint
end
def to_hash
%i[
compare_key
confidence
identifiers
flags
links
location
evidence
metadata_version
name
project_fingerprint
raw_metadata
report_type
scanner
scan
severity
uuid
details
signatures
description
message
cve
solution
].each_with_object({}) do |key, hash|
hash[key] = public_send(key) # rubocop:disable GitlabSecurity/PublicSend
end
end
def primary_identifier
identifiers.first
end
def update_location(new_location)
@old_location = location
@location = new_location
end
def unsafe?(severity_levels, report_types)
severity.to_s.in?(severity_levels) && (report_types.blank? || report_type.to_s.in?(report_types) )
end
def eql?(other)
return false unless report_type == other.report_type && primary_identifier_fingerprint == other.primary_identifier_fingerprint
if @vulnerability_finding_signatures_enabled
matches_signatures(other.signatures, other.uuid)
else
location.fingerprint == other.location.fingerprint
end
end
def hash
if @vulnerability_finding_signatures_enabled && !signatures.empty?
highest_signature = signatures.max_by(&:priority)
report_type.hash ^ highest_signature.signature_hex.hash ^ primary_identifier_fingerprint.hash
else
report_type.hash ^ location.fingerprint.hash ^ primary_identifier_fingerprint.hash
end
end
def valid?
scanner.present? && primary_identifier.present? && location.present? && uuid.present?
end
def keys
@keys ||= identifiers.reject(&:type_identifier?).flat_map do |identifier|
location_fingerprints.map do |location_fingerprint|
FindingKey.new(location_fingerprint: location_fingerprint, identifier_fingerprint: identifier.fingerprint)
end
end.push(uuid)
end
def primary_identifier_fingerprint
primary_identifier&.fingerprint
end
def <=>(other)
if severity == other.severity
compare_key <=> other.compare_key
else
::Enums::Vulnerability.severity_levels[other.severity] <=>
::Enums::Vulnerability.severity_levels[severity]
end
end
def scanner_order_to(other)
return 1 unless scanner
return -1 unless other&.scanner
scanner <=> other.scanner
end
def has_signatures?
signatures.present?
end
def false_positive?
flags.any?(&:false_positive?)
end
def remediation_byte_offsets
remediations.map(&:byte_offsets).compact
end
def raw_metadata
@raw_metadata ||= original_data.to_json
end
def description
original_data['description']
end
def message
original_data['message']
end
def solution
original_data['solution']
end
def location_data
original_data['location']
end
def assets
original_data['assets'] || []
end
# Returns either the max priority signature hex
# or the location fingerprint
def location_fingerprint
location_fingerprints.first
end
private
def generate_project_fingerprint
Digest::SHA1.hexdigest(compare_key)
end
def location_fingerprints
@location_fingerprints ||= signature_hexes << location&.fingerprint
end
# Returns the signature hexes in reverse priority order
def signature_hexes
return [] unless @vulnerability_finding_signatures_enabled && signatures.present?
signatures.sort_by(&:priority).map(&:signature_hex).reverse
end
end
end
end
end
end
|