mihari 7.2.0 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -17,6 +17,90 @@ module Mihari
17
17
  # Artifact model
18
18
  #
19
19
  class Artifact < ActiveRecord::Base
20
+ # @!attribute [r] id
21
+ # @return [Integer, nil]
22
+
23
+ # @!attribute [rw] data
24
+ # @return [String]
25
+
26
+ # @!attribute [rw] data_type
27
+ # @return [String]
28
+
29
+ # @!attribute [rw] source
30
+ # @return [String, nil]
31
+
32
+ # @!attribute [rw] query
33
+ # @return [String, nil]
34
+
35
+ # @!attribute [rw] metadata
36
+ # @return [Hash, nil]
37
+
38
+ # @!attribute [rw] created_at
39
+ # @return [DateTime]
40
+
41
+ # @!attribute [r] alert
42
+ # @return [Mihari::Models::Alert]
43
+
44
+ # @!attribute [r] rule
45
+ # @return [Mihari::Models::Rule]
46
+
47
+ # @!attribute [rw] autonomous_system
48
+ # @return [Mihari::Models::AutonomousSystem, nil]
49
+
50
+ # @!attribute [rw] geolocation
51
+ # @return [Mihari::Models::Geolocation, nil]
52
+
53
+ # @!attribute [rw] whois_record
54
+ # @return [Mihari::Models::WhoisRecord, nil]
55
+
56
+ # @!attribute [rw] cpes
57
+ # @return [Array<Mihari::Models::CPE>]
58
+
59
+ # @!attribute [rw] dns_records
60
+ # @return [Array<Mihari::Models::DnsRecord>]
61
+
62
+ # @!attribute [rw] ports
63
+ # @return [Array<Mihari::Models::Port>]
64
+
65
+ # @!attribute [rw] reverse_dns_names
66
+ # @return [Array<Mihari::Models::ReverseDnsName>]
67
+
68
+ # @!attribute [rw] vulnerabilities
69
+ # @return [Array<Mihari::Models::Vulnerability>]
70
+
71
+ # @!attribute [r] alert
72
+ # @return [Mihari::Models::Alert]
73
+
74
+ # @!attribute [r] rule
75
+ # @return [Mihari::Models::Rule]
76
+
77
+ # @!attribute [rw] autonomous_system
78
+ # @return [Mihari::Models::AutonomousSystem, nil]
79
+
80
+ # @!attribute [rw] geolocation
81
+ # @return [Mihari::Models::Geolocation, nil]
82
+
83
+ # @!attribute [rw] whois_record
84
+ # @return [Mihari::Models::WhoisRecord, nil]
85
+
86
+ # @!attribute [rw] cpes
87
+ # @return [Array<Mihari::Models::CPE>]
88
+
89
+ # @!attribute [rw] dns_records
90
+ # @return [Array<Mihari::Models::DnsRecord>]
91
+
92
+ # @!attribute [rw] ports
93
+ # @return [Array<Mihari::Models::Port>]
94
+
95
+ # @!attribute [rw] reverse_dns_names
96
+ # @return [Array<Mihari::Models::ReverseDnsName>]
97
+
98
+ # @!attribute [rw] vulnerabilities
99
+ # @return [Array<Mihari::Models::Vulnerability>]
100
+
101
+ # @!attribute [rw] tags
102
+ # @return [Array<Mihari::Models::Tag>]
103
+
20
104
  belongs_to :alert
21
105
 
22
106
  has_one :autonomous_system, dependent: :destroy
@@ -90,173 +174,24 @@ module Mihari
90
174
  artifact.created_at < decayed_at
91
175
  end
92
176
 
93
- #
94
- # Enrich whois record
95
- #
96
- # @param [Mihari::Enrichers::Whois] enricher
97
- #
98
- def enrich_whois(enricher = Enrichers::Whois.new)
99
- return unless can_enrich_whois?
100
-
101
- self.whois_record = Services::WhoisRecordBuilder.call(domain, enricher: enricher)
102
- end
103
-
104
- #
105
- # Enrich DNS records
106
- #
107
- # @param [Mihari::Enrichers::GooglePublicDNS] enricher
108
- #
109
- def enrich_dns(enricher = Enrichers::GooglePublicDNS.new)
110
- return unless can_enrich_dns?
111
-
112
- self.dns_records = Services::DnsRecordBuilder.call(domain, enricher: enricher)
113
- end
114
-
115
- #
116
- # Enrich reverse DNS names
117
- #
118
- # @param [Mihari::Enrichers::Shodan] enricher
119
- #
120
- def enrich_reverse_dns(enricher = Enrichers::Shodan.new)
121
- return unless can_enrich_reverse_dns?
122
-
123
- self.reverse_dns_names = Services::ReverseDnsNameBuilder.call(data, enricher: enricher)
124
- end
125
-
126
- #
127
- # Enrich geolocation
128
- #
129
- # @param [Mihari::Enrichers::IPInfo] enricher
130
- #
131
- def enrich_geolocation(enricher = Enrichers::MMDB.new)
132
- return unless can_enrich_geolocation?
133
-
134
- self.geolocation = Services::GeolocationBuilder.call(data, enricher: enricher)
135
- end
136
-
137
- #
138
- # Enrich AS
139
- #
140
- # @param [Mihari::Enrichers::IPInfo] enricher
141
- #
142
- def enrich_autonomous_system(enricher = Enrichers::MMDB.new)
143
- return unless can_enrich_autonomous_system?
144
-
145
- self.autonomous_system = Services::AutonomousSystemBuilder.call(data, enricher: enricher)
146
- end
147
-
148
- #
149
- # Enrich ports
150
- #
151
- # @param [Mihari::Enrichers::Shodan] enricher
152
- #
153
- def enrich_ports(enricher = Enrichers::Shodan.new)
154
- return unless can_enrich_ports?
155
-
156
- self.ports = Services::PortBuilder.call(data, enricher: enricher)
157
- end
158
-
159
- #
160
- # Enrich CPEs
161
- #
162
- # @param [Mihari::Enrichers::Shodan] enricher
163
- #
164
- def enrich_cpes(enricher = Enrichers::Shodan.new)
165
- return unless can_enrich_cpes?
166
-
167
- self.cpes = Services::CPEBuilder.call(data, enricher: enricher)
168
- end
169
-
170
- #
171
- # Enrich vulnerabilities
172
- #
173
- # @param [Mihari::Enrichers::Shodan] enricher
174
- #
175
- def enrich_vulnerabilities(enricher = Enrichers::Shodan.new)
176
- return unless can_enrich_vulnerabilities?
177
-
178
- self.vulnerabilities = Services::VulnerabilityBuilder.call(data, enricher: enricher)
177
+ def enrichable?
178
+ !callable_enrichers.empty?
179
179
  end
180
180
 
181
- #
182
- # Enrich all the enrichable relationships of the artifact
183
- #
184
- def enrich_all
185
- enrich_autonomous_system mmdb
186
- enrich_dns
187
- enrich_geolocation mmdb
188
- enrich_reverse_dns shodan
189
- enrich_whois
190
- enrich_ports shodan
191
- enrich_cpes shodan
192
- enrich_vulnerabilities shodan
181
+ def enrich
182
+ callable_enrichers.each { |enricher| enricher.result self }
193
183
  end
194
184
 
195
- ENRICH_METHODS_BY_ENRICHER = {
196
- Enrichers::Whois => %i[
197
- enrich_whois
198
- ],
199
- Enrichers::MMDB => %i[
200
- enrich_autonomous_system
201
- enrich_geolocation
202
- ],
203
- Enrichers::Shodan => %i[
204
- enrich_ports
205
- enrich_cpes
206
- enrich_reverse_dns
207
- enrich_vulnerabilities
208
- ],
209
- Enrichers::GooglePublicDNS => %i[
210
- enrich_dns
211
- ]
212
- }.freeze
213
-
214
185
  #
215
- # Enrich by name of enricher
216
- #
217
- # @param [Mihari::Enrichers::Base] enricher
186
+ # @return [String, nil]
218
187
  #
219
- def enrich_by_enricher(enricher)
220
- methods = ENRICH_METHODS_BY_ENRICHER[enricher.class] || []
221
- methods.each { |method| send(method, enricher) if respond_to?(method) }
222
- end
223
-
224
- def can_enrich_whois?
225
- %w[domain url].include?(data_type) && whois_record.nil?
226
- end
227
-
228
- def can_enrich_dns?
229
- %w[domain url].include?(data_type) && dns_records.empty?
230
- end
231
-
232
- def can_enrich_reverse_dns?
233
- data_type == "ip" && reverse_dns_names.empty?
234
- end
235
-
236
- def can_enrich_geolocation?
237
- data_type == "ip" && geolocation.nil?
238
- end
239
-
240
- def can_enrich_autonomous_system?
241
- data_type == "ip" && autonomous_system.nil?
242
- end
243
-
244
- def can_enrich_ports?
245
- data_type == "ip" && ports.empty?
246
- end
247
-
248
- def can_enrich_cpes?
249
- data_type == "ip" && cpes.empty?
250
- end
251
-
252
- def can_enrich_vulnerabilities?
253
- data_type == "ip" && vulnerabilities.empty?
254
- end
255
-
256
- def enrichable?
257
- enrich_methods = methods.map(&:to_s).select { |method| method.start_with?("can_enrich_") }
258
- enrich_methods.map(&:to_sym).any? do |method|
259
- send(method) if respond_to?(method)
188
+ def domain
189
+ case data_type
190
+ when "domain"
191
+ data
192
+ when "url"
193
+ host = Addressable::URI.parse(data).host
194
+ (DataType.type(host) == "ip") ? nil : host
260
195
  end
261
196
  end
262
197
 
@@ -272,6 +207,15 @@ module Mihari
272
207
 
273
208
  private
274
209
 
210
+ #
211
+ # @return [Array<Mihari::Enrichers::Base>]
212
+ #
213
+ def callable_enrichers
214
+ @callable_enrichers ||= Mihari.enrichers.map(&:new).select do |enricher|
215
+ enricher.callable?(self)
216
+ end
217
+ end
218
+
275
219
  def set_data_type
276
220
  self.data_type = DataType.type(data)
277
221
  end
@@ -279,26 +223,6 @@ module Mihari
279
223
  def set_rule_id
280
224
  @set_rule_id ||= nil
281
225
  end
282
-
283
- def mmdb
284
- @mmdb ||= Enrichers::MMDB.new
285
- end
286
-
287
- def shodan
288
- @shodan ||= Enrichers::Shodan.new
289
- end
290
-
291
- #
292
- # @return [String, nil]
293
- #
294
- def domain
295
- case data_type
296
- when "domain"
297
- data
298
- when "url"
299
- Addressable::URI.parse(data).host
300
- end
301
- end
302
226
  end
303
227
  end
304
228
  end
@@ -6,6 +6,27 @@ module Mihari
6
6
  # Rule model
7
7
  #
8
8
  class Rule < ActiveRecord::Base
9
+ # @!attribute [rw] id
10
+ # @return [String]
11
+
12
+ # @!attribute [rw] title
13
+ # @return [String]
14
+
15
+ # @!attribute [rw] description
16
+ # @return [String]
17
+
18
+ # @!attribute [rw] data
19
+ # @return [Hash]
20
+
21
+ # @!attribute [rw] created_at
22
+ # @return [DateTime]
23
+
24
+ # @!attribute [rw] updated_at
25
+ # @return [DateTime]
26
+
27
+ # @!attribute [r] alerts
28
+ # @return [Array<Mihari::Models::Alert>]
29
+
9
30
  has_many :alerts, dependent: :destroy
10
31
  has_many :taggings, dependent: :destroy
11
32
  has_many :tags, through: :taggings
data/lib/mihari/rule.rb CHANGED
@@ -174,8 +174,10 @@ module Mihari
174
174
  # @return [Array<Mihari::Models::Artifact>]
175
175
  #
176
176
  def enriched_artifacts
177
- @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
178
- enrichers.each { |enricher| artifact.enrich_by_enricher enricher }
177
+ @enriched_artifacts ||= unique_artifacts.map do |artifact|
178
+ serial_enrichers.each { |enricher| enricher.result(artifact) }
179
+ Parallel.each(parallel_enrichers) { |enricher| enricher.result(artifact) }
180
+
179
181
  artifact
180
182
  end
181
183
  end
@@ -188,7 +190,10 @@ module Mihari
188
190
  def bulk_emit
189
191
  return [] if enriched_artifacts.empty?
190
192
 
191
- Parallel.map(emitters) { |emitter| emitter.result(enriched_artifacts).value_or nil }.compact
193
+ [].tap do |out|
194
+ out << serial_emitters.map { |emitter| emitter.result(enriched_artifacts).value_or(nil) }
195
+ out << Parallel.map(parallel_emitters) { |emitter| emitter.result(enriched_artifacts).value_or(nil) }
196
+ end.flatten.compact
192
197
  end
193
198
 
194
199
  #
@@ -289,11 +294,11 @@ module Mihari
289
294
  #
290
295
  # @return [Boolean]
291
296
  #
292
- def falsepositive?(value)
293
- return true if falsepositives.include?(value)
297
+ def falsepositive?(artifact)
298
+ return true if falsepositives.include?(artifact)
294
299
 
295
300
  regexps = falsepositives.select { |fp| fp.is_a?(Regexp) }
296
- regexps.any? { |fp| fp.match?(value) }
301
+ regexps.any? { |fp| fp.match?(artifact) }
297
302
  end
298
303
 
299
304
  #
@@ -365,6 +370,14 @@ module Mihari
365
370
  end
366
371
  end
367
372
 
373
+ def parallel_emitters
374
+ emitters.select(&:parallel?)
375
+ end
376
+
377
+ def serial_emitters
378
+ emitters.reject(&:parallel?)
379
+ end
380
+
368
381
  #
369
382
  # Get enricher class
370
383
  #
@@ -391,6 +404,14 @@ module Mihari
391
404
  end
392
405
  end
393
406
 
407
+ def parallel_enrichers
408
+ enrichers.select(&:parallel?)
409
+ end
410
+
411
+ def serial_enrichers
412
+ enrichers.reject(&:parallel?)
413
+ end
414
+
394
415
  #
395
416
  # Validate the data format
396
417
  #
@@ -3,9 +3,9 @@
3
3
  module Mihari
4
4
  module Schemas
5
5
  Alert = Dry::Schema.Params do
6
- required(:rule_id).value(:string)
7
- required(:artifacts).value(array[:string])
8
- optional(:source).value(:string)
6
+ required(:rule_id).filled(:string)
7
+ required(:artifacts).array { filled(:string) }
8
+ optional(:source).filled(:string)
9
9
  end
10
10
 
11
11
  #
@@ -20,8 +20,8 @@ module Mihari
20
20
  key = keys.first
21
21
  const_set(key.upcase, Dry::Schema.Params do
22
22
  required(:analyzer).value(Types::String.enum(*keys))
23
- required(:query).value(:string)
24
- optional(:api_key).value(:string)
23
+ required(:query).filled(:string)
24
+ optional(:api_key).filled(:string)
25
25
  optional(:options).hash(AnalyzerPaginationOptions)
26
26
  end)
27
27
  end
@@ -36,60 +36,60 @@ module Mihari
36
36
  key = keys.first
37
37
  const_set(key.upcase, Dry::Schema.Params do
38
38
  required(:analyzer).value(Types::String.enum(*keys))
39
- required(:query).value(:string)
40
- optional(:api_key).value(:string)
39
+ required(:query).filled(:string)
40
+ optional(:api_key).filled(:string)
41
41
  optional(:options).hash(AnalyzerOptions)
42
42
  end)
43
43
  end
44
44
 
45
45
  DNSTwister = Dry::Schema.Params do
46
46
  required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::DNSTwister.keys))
47
- required(:query).value(:string)
47
+ required(:query).filled(:string)
48
48
  optional(:options).hash(AnalyzerOptions)
49
49
  end
50
50
 
51
51
  Censys = Dry::Schema.Params do
52
52
  required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Censys.keys))
53
- required(:query).value(:string)
54
- optional(:id).value(:string)
55
- optional(:secret).value(:string)
53
+ required(:query).filled(:string)
54
+ optional(:id).filled(:string)
55
+ optional(:secret).filled(:string)
56
56
  optional(:options).hash(AnalyzerPaginationOptions)
57
57
  end
58
58
 
59
59
  CIRCL = Dry::Schema.Params do
60
60
  required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::CIRCL.keys))
61
- required(:query).value(:string)
62
- optional(:username).value(:string)
63
- optional(:password).value(:string)
61
+ required(:query).filled(:string)
62
+ optional(:username).filled(:string)
63
+ optional(:password).filled(:string)
64
64
  optional(:options).hash(AnalyzerOptions)
65
65
  end
66
66
 
67
67
  Fofa = Dry::Schema.Params do
68
68
  required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Fofa.keys))
69
- required(:query).value(:string)
70
- optional(:api_key).value(:string)
71
- optional(:email).value(:string)
69
+ required(:query).filled(:string)
70
+ optional(:api_key).filled(:string)
71
+ optional(:email).filled(:string)
72
72
  optional(:options).hash(AnalyzerPaginationOptions)
73
73
  end
74
74
 
75
75
  PassiveTotal = Dry::Schema.Params do
76
76
  required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::PassiveTotal.keys))
77
- required(:query).value(:string)
78
- optional(:username).value(:string)
79
- optional(:api_key).value(:string)
77
+ required(:query).filled(:string)
78
+ optional(:username).filled(:string)
79
+ optional(:api_key).filled(:string)
80
80
  optional(:options).hash(AnalyzerOptions)
81
81
  end
82
82
 
83
83
  ZoomEye = Dry::Schema.Params do
84
84
  required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::ZoomEye.keys))
85
- required(:query).value(:string)
85
+ required(:query).filled(:string)
86
86
  required(:type).value(Types::String.enum("host", "web"))
87
87
  optional(:options).hash(AnalyzerPaginationOptions)
88
88
  end
89
89
 
90
90
  Crtsh = Dry::Schema.Params do
91
91
  required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Crtsh.keys))
92
- required(:query).value(:string)
92
+ required(:query).filled(:string)
93
93
  optional(:exclude_expired).value(:bool).default(true)
94
94
  optional(:match).value(Types::String.enum("=", "ILIKE", "LIKE", "single", "any", "FTS")).default(nil)
95
95
  optional(:options).hash(AnalyzerOptions)
@@ -97,22 +97,22 @@ module Mihari
97
97
 
98
98
  HunterHow = Dry::Schema.Params do
99
99
  required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::HunterHow.keys))
100
- required(:query).value(:string)
100
+ required(:query).filled(:string)
101
101
  required(:start_time).value(:date)
102
102
  required(:end_time).value(:date)
103
- optional(:api_key).value(:string)
103
+ optional(:api_key).filled(:string)
104
104
  optional(:options).hash(AnalyzerPaginationOptions)
105
105
  end
106
106
 
107
107
  Feed = Dry::Schema.Params do
108
108
  required(:analyzer).value(Types::String.enum(*Mihari::Analyzers::Feed.keys))
109
- required(:query).value(:string)
110
- required(:selector).value(:string)
109
+ required(:query).filled(:string)
110
+ required(:selector).filled(:string)
111
111
  optional(:method).value(Types::HTTPRequestMethods).default("GET")
112
- optional(:headers).value(:hash).default({})
113
- optional(:params).value(:hash)
114
- optional(:form).value(:hash)
115
- optional(:json).value(:hash)
112
+ optional(:headers).filled(:hash)
113
+ optional(:params).filled(:hash)
114
+ optional(:form).filled(:hash)
115
+ optional(:json).filled(:hash)
116
116
  optional(:options).hash(AnalyzerOptions)
117
117
  end
118
118
  end
@@ -15,31 +15,31 @@ module Mihari
15
15
 
16
16
  MISP = Dry::Schema.Params do
17
17
  required(:emitter).value(Types::String.enum(*Mihari::Emitters::MISP.keys))
18
- optional(:url).value(:string)
19
- optional(:api_key).value(:string)
18
+ optional(:url).filled(:string)
19
+ optional(:api_key).filled(:string)
20
20
  optional(:options).hash(Options)
21
21
  end
22
22
 
23
23
  TheHive = Dry::Schema.Params do
24
24
  required(:emitter).value(Types::String.enum(*Mihari::Emitters::TheHive.keys))
25
- optional(:url).value(:string)
26
- optional(:api_key).value(:string)
25
+ optional(:url).filled(:string)
26
+ optional(:api_key).filled(:string)
27
27
  optional(:options).hash(Options)
28
28
  end
29
29
 
30
30
  Slack = Dry::Schema.Params do
31
31
  required(:emitter).value(Types::String.enum(*Mihari::Emitters::Slack.keys))
32
- optional(:webhook_url).value(:string)
33
- optional(:channel).value(:string)
32
+ optional(:webhook_url).filled(:string)
33
+ optional(:channel).filled(:string)
34
34
  optional(:options).hash(Options)
35
35
  end
36
36
 
37
37
  Webhook = Dry::Schema.Params do
38
38
  required(:emitter).value(Types::String.enum(*Mihari::Emitters::Webhook.keys))
39
- required(:url).value(:string)
39
+ required(:url).filled(:string)
40
40
  optional(:method).value(Types::HTTPRequestMethods).default("POST")
41
- optional(:headers).value(:hash).default({})
42
- optional(:template).value(:string)
41
+ optional(:headers).filled(:hash)
42
+ optional(:template).filled(:string)
43
43
  optional(:options).hash(Options)
44
44
  end
45
45
  end
@@ -8,9 +8,9 @@ module Dry
8
8
  # (see https://github.com/dry-rb/dry-schema/issues/70)
9
9
  #
10
10
  class DSL
11
- def default(value)
11
+ def default(artifact)
12
12
  schema_dsl.before(:rule_applier) do |result|
13
- result.update(name => value) if result.output && !result[name]
13
+ result.update(name => artifact) if result.output && !result[name]
14
14
  end
15
15
  end
16
16
  end
@@ -7,17 +7,14 @@ module Mihari
7
7
  optional(:retry_interval).value(:integer).default(Mihari.config.retry_interval)
8
8
  optional(:retry_exponential_backoff).value(:bool).default(Mihari.config.retry_exponential_backoff)
9
9
  optional(:timeout).value(:integer)
10
+ optional(:parallel).value(:bool).default(Mihari.config.parallel)
10
11
  end
11
12
 
12
13
  IgnoreErrorOptions = Dry::Schema.Params do
13
14
  optional(:ignore_error).value(:bool).default(Mihari.config.ignore_error)
14
15
  end
15
16
 
16
- ParallelOptions = Dry::Schema.Params do
17
- optional(:parallel).value(:bool).default(Mihari.config.parallel)
18
- end
19
-
20
- AnalyzerOptions = Options | IgnoreErrorOptions | ParallelOptions
17
+ AnalyzerOptions = Options | IgnoreErrorOptions
21
18
 
22
19
  PaginationOptions = Dry::Schema.Params do
23
20
  optional(:pagination_interval).value(:integer).default(Mihari.config.pagination_interval)
@@ -7,27 +7,27 @@ require "mihari/schemas/enricher"
7
7
  module Mihari
8
8
  module Schemas
9
9
  Rule = Dry::Schema.Params do
10
- required(:id).value(:string)
11
- required(:title).value(:string)
12
- required(:description).value(:string)
10
+ required(:id).filled(:string)
11
+ required(:title).filled(:string)
12
+ required(:description).filled(:string)
13
13
 
14
- optional(:tags).value(array[:string]).default([])
14
+ optional(:author).filled(:string)
15
+ optional(:status).filled(:string)
15
16
 
16
- optional(:author).value(:string)
17
- optional(:references).value(array[:string])
18
- optional(:related).value(array[:string])
19
- optional(:status).value(:string)
17
+ optional(:tags).array { filled(:string) }.default([])
18
+ optional(:references).array { filled(:string) }
19
+ optional(:related).array { filled(:string) }
20
20
 
21
21
  optional(:created_on).value(:date)
22
22
  optional(:updated_on).value(:date)
23
23
 
24
24
  required(:queries).value(:array).each { Analyzer } # rubocop:disable Lint/Void
25
-
26
25
  optional(:emitters).value(:array).each { Emitter }.default(DEFAULT_EMITTERS) # rubocop:disable Lint/Void
27
26
  optional(:enrichers).value(:array).each { Enricher }.default(DEFAULT_ENRICHERS) # rubocop:disable Lint/Void
28
27
 
29
- optional(:data_types).value(array[Types::DataTypes]).default(Mihari::Types::DataTypes.values)
30
- optional(:falsepositives).value(array[:string]).default([])
28
+ optional(:data_types).filled(array[Types::DataTypes]).default(Mihari::Types::DataTypes.values)
29
+
30
+ optional(:falsepositives).array { filled(:string) }.default([])
31
31
 
32
32
  optional(:artifact_ttl).value(:integer)
33
33
  end
@@ -42,7 +42,7 @@ module Mihari
42
42
 
43
43
  rule(:falsepositives) do
44
44
  value.each do |v|
45
- key.failure("#{v} is not a valid format.") unless valid_falsepositive?(v)
45
+ key.failure("#{v} is not a valid format") unless valid_falsepositive?(v)
46
46
  end
47
47
  end
48
48