mihari 7.2.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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