mihari 7.1.0 → 7.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/README.md +2 -1
- data/Rakefile +39 -1
- data/lib/mihari/actor.rb +5 -5
- data/lib/mihari/analyzers/base.rb +18 -11
- data/lib/mihari/analyzers/passivetotal.rb +1 -1
- data/lib/mihari/analyzers/securitytrails.rb +1 -1
- data/lib/mihari/analyzers/virustotal.rb +1 -1
- data/lib/mihari/analyzers/virustotal_intelligence.rb +2 -2
- data/lib/mihari/cli/application.rb +16 -4
- data/lib/mihari/commands/alert.rb +5 -5
- data/lib/mihari/commands/artifact.rb +5 -5
- data/lib/mihari/commands/rule.rb +6 -6
- data/lib/mihari/commands/search.rb +1 -1
- data/lib/mihari/commands/tag.rb +3 -3
- data/lib/mihari/concerns/retriable.rb +1 -1
- data/lib/mihari/constants.rb +1 -1
- data/lib/mihari/database.rb +1 -1
- data/lib/mihari/emitters/base.rb +15 -1
- data/lib/mihari/emitters/database.rb +4 -0
- data/lib/mihari/emitters/misp.rb +7 -0
- data/lib/mihari/emitters/slack.rb +7 -0
- data/lib/mihari/emitters/the_hive.rb +7 -0
- data/lib/mihari/emitters/webhook.rb +7 -0
- data/lib/mihari/enrichers/base.rb +9 -1
- data/lib/mihari/enrichers/google_public_dns.rb +1 -1
- data/lib/mihari/rule.rb +2 -14
- data/lib/mihari/schemas/analyzer.rb +19 -19
- data/lib/mihari/schemas/emitter.rb +5 -5
- data/lib/mihari/schemas/enricher.rb +4 -4
- data/lib/mihari/structs/config.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/{index-U5u7qHZZ.js → index-Guw2aMpk.js} +53 -53
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +28 -28
- data/lib/mihari.rb +14 -3
- data/mihari.gemspec +4 -4
- data/mkdocs.yml +1 -1
- metadata +57 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52d1c8320fdb5233c9738f3b4599868260fef892599cdfb42da6c3af17583b75
|
4
|
+
data.tar.gz: 625cd92558eff5a4d5613e588cc5ee85b9b714a9af211788da1a294d8d54ac45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96811d3ebfffcb27a7577b814be280916dd46bd3477233374ffafd2bf784d9e5133f89b1d7650c722c25e2f678f82380f5ced2eff8f9b7e1f3c96583f54a1114
|
7
|
+
data.tar.gz: 5c13581e670c7aff158e93335fe9c626294cad622f153e61a338f31bb5a1e459302f34a3a9ddf18e0bb322e1359c43a6b782387cdc800f9b047c04e5521f4c70
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# mihari
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/mihari.svg)](https://badge.fury.io/rb/mihari)
|
4
|
-
[![Ruby CI](https://github.com/ninoseki/mihari/actions/workflows/
|
4
|
+
[![Ruby CI](https://github.com/ninoseki/mihari/actions/workflows/ruby.yml/badge.svg)](https://github.com/ninoseki/mihari/actions/workflows/ruby.yml)
|
5
|
+
[![Node.js CI](https://github.com/ninoseki/mihari/actions/workflows/node.yml/badge.svg)](https://github.com/ninoseki/mihari/actions/workflows/node.yml)
|
5
6
|
[![Coverage Status](https://coveralls.io/repos/github/ninoseki/mihari/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/mihari?branch=master)
|
6
7
|
[![CodeFactor](https://www.codefactor.io/repository/github/ninoseki/mihari/badge)](https://www.codefactor.io/repository/github/ninoseki/mihari)
|
7
8
|
|
data/Rakefile
CHANGED
@@ -7,11 +7,49 @@ RSpec::Core::RakeTask.new(:spec)
|
|
7
7
|
|
8
8
|
task default: :spec
|
9
9
|
|
10
|
-
desc "
|
10
|
+
desc "Run rackup (with rerun)"
|
11
11
|
task :rackup do
|
12
12
|
sh "rerun --pattern '{Gemfile.lock,lib/**/*.rb,lib/*.rb}' -- rackup config.ru"
|
13
13
|
end
|
14
14
|
|
15
|
+
def recursive_delete(hash, to_remove)
|
16
|
+
hash.delete(to_remove)
|
17
|
+
hash.each_value do |value|
|
18
|
+
recursive_delete(value, to_remove) if value.is_a? Hash
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_swagger_doc(path)
|
23
|
+
require_relative "lib/mihari"
|
24
|
+
require_relative "lib/mihari/web/application"
|
25
|
+
|
26
|
+
require "rack/test"
|
27
|
+
|
28
|
+
app = Mihari::Web::App.new
|
29
|
+
session = Rack::Test::Session.new(app)
|
30
|
+
|
31
|
+
res = session.request("/api/swagger_doc")
|
32
|
+
|
33
|
+
json = JSON.parse(res.body.to_s)
|
34
|
+
# remove host because it can be varied
|
35
|
+
keys_to_remove = %w[host]
|
36
|
+
keys_to_remove.each do |key|
|
37
|
+
recursive_delete json, key
|
38
|
+
end
|
39
|
+
|
40
|
+
f = File.open(path, "w")
|
41
|
+
f.write json.to_yaml
|
42
|
+
f.close
|
43
|
+
end
|
44
|
+
|
45
|
+
namespace :build do
|
46
|
+
desc "Build Swagger doc"
|
47
|
+
task :swagger, [:path] do |_t, args|
|
48
|
+
args.with_defaults(path: "./frontend/swagger.yaml")
|
49
|
+
build_swagger_doc args.path
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
15
53
|
def ci?
|
16
54
|
ENV.fetch("CI", false)
|
17
55
|
end
|
data/lib/mihari/actor.rb
CHANGED
@@ -55,7 +55,7 @@ module Mihari
|
|
55
55
|
|
56
56
|
joined = self.class.configuration_keys.join(", ")
|
57
57
|
be = (self.class.configuration_keys.length > 1) ? "are" : "is"
|
58
|
-
message = "#{self.class.
|
58
|
+
message = "#{self.class.key} is not configured correctly. #{joined} #{be} missing."
|
59
59
|
raise ConfigurationError, message
|
60
60
|
end
|
61
61
|
|
@@ -75,22 +75,22 @@ module Mihari
|
|
75
75
|
#
|
76
76
|
# @return [String]
|
77
77
|
#
|
78
|
-
def
|
78
|
+
def key
|
79
79
|
to_s.split("::").last.downcase
|
80
80
|
end
|
81
81
|
|
82
82
|
#
|
83
83
|
# @return [Array<String>, nil]
|
84
84
|
#
|
85
|
-
def
|
85
|
+
def key_aliases
|
86
86
|
nil
|
87
87
|
end
|
88
88
|
|
89
89
|
#
|
90
90
|
# @return [Array<String>]
|
91
91
|
#
|
92
|
-
def
|
93
|
-
([
|
92
|
+
def keys
|
93
|
+
([key] + [key_aliases]).flatten.compact.map(&:downcase)
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|
@@ -65,7 +65,7 @@ module Mihari
|
|
65
65
|
# It is set automatically in #initialize
|
66
66
|
artifact = artifact.is_a?(Models::Artifact) ? artifact : Models::Artifact.new(data: artifact)
|
67
67
|
|
68
|
-
artifact.source = self.class.
|
68
|
+
artifact.source = self.class.key
|
69
69
|
artifact.query = query
|
70
70
|
|
71
71
|
artifact
|
@@ -80,7 +80,7 @@ module Mihari
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def result(...)
|
83
|
-
|
83
|
+
result = Try[StandardError] do
|
84
84
|
retry_on_error(
|
85
85
|
times: retry_times,
|
86
86
|
interval: retry_interval,
|
@@ -88,19 +88,26 @@ module Mihari
|
|
88
88
|
) do
|
89
89
|
call(...)
|
90
90
|
end
|
91
|
-
end
|
92
|
-
|
93
|
-
return res.recover { [] } if ignore_error?
|
91
|
+
end.to_result
|
94
92
|
|
95
|
-
result = res.to_result
|
96
93
|
return result if result.success?
|
97
94
|
|
98
95
|
# Wrap failure with AnalyzerError to explicitly name a failed analyzer
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
)
|
96
|
+
error = AnalyzerError.new(result.failure.message, self.class.key, cause: result.failure)
|
97
|
+
return Failure(error) unless ignore_error?
|
98
|
+
|
99
|
+
# Return Success if ignore_error? is true with logging
|
100
|
+
Mihari.logger.warn("Analyzer:#{self.class.key} with #{truncated_query} failed - #{result.failure}")
|
101
|
+
Success([])
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Truncate query for logging
|
106
|
+
#
|
107
|
+
# @return [String]
|
108
|
+
#
|
109
|
+
def truncated_query
|
110
|
+
query.truncate(32)
|
104
111
|
end
|
105
112
|
|
106
113
|
class << self
|
@@ -38,6 +38,21 @@ module Mihari
|
|
38
38
|
include Concerns::ErrorUnwrappable
|
39
39
|
|
40
40
|
no_commands do
|
41
|
+
#
|
42
|
+
# @param [StandardError] error
|
43
|
+
#
|
44
|
+
# @return [String, Hash, nil]
|
45
|
+
#
|
46
|
+
def error_to_detail(error)
|
47
|
+
# Dirty hack to show the DB error message
|
48
|
+
# (NOTE: #safe_execute block suppress #with_db_connection's error message)
|
49
|
+
if error.is_a?(ActiveRecord::StatementInvalid)
|
50
|
+
return "DB migration is not yet complete. Please run 'mihari db migrate'."
|
51
|
+
end
|
52
|
+
|
53
|
+
error.respond_to?(:detail) ? error.detail : nil
|
54
|
+
end
|
55
|
+
|
41
56
|
def safe_execute
|
42
57
|
yield
|
43
58
|
rescue StandardError => e
|
@@ -48,10 +63,7 @@ module Mihari
|
|
48
63
|
# Raise error if debug is set as true
|
49
64
|
raise error if options["debug"]
|
50
65
|
|
51
|
-
data = Entities::ErrorMessage.represent(
|
52
|
-
message: error.message,
|
53
|
-
detail: error.respond_to?(:detail) ? error.detail : nil
|
54
|
-
)
|
66
|
+
data = Entities::ErrorMessage.represent(message: error.message, detail: error_to_detail(error))
|
55
67
|
warn JSON.pretty_generate(data.as_json)
|
56
68
|
|
57
69
|
Sentry.capture_exception(error) if Sentry.initialized? && !error.is_a?(ValidationError)
|
@@ -26,7 +26,7 @@ module Mihari
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
desc "create
|
29
|
+
desc "create PATH", "Create an alert"
|
30
30
|
around :with_db_connection
|
31
31
|
#
|
32
32
|
# @param [String] path
|
@@ -46,7 +46,7 @@ module Mihari
|
|
46
46
|
puts JSON.pretty_generate(data.as_json)
|
47
47
|
end
|
48
48
|
|
49
|
-
desc "list
|
49
|
+
desc "list QUERY", "List/search alerts"
|
50
50
|
around :with_db_connection
|
51
51
|
method_option :page, type: :numeric, default: 1
|
52
52
|
method_option :limit, type: :numeric, default: 10
|
@@ -67,7 +67,7 @@ module Mihari
|
|
67
67
|
desc "list-transform QUERY", "List/search alerts with transformation"
|
68
68
|
around :with_db_connection
|
69
69
|
method_option :template, type: :string, required: true, aliases: "-t",
|
70
|
-
|
70
|
+
desc: "Jbuilder template stringor a path to a template"
|
71
71
|
method_option :page, type: :numeric, default: 1
|
72
72
|
method_option :limit, type: :numeric, default: 10
|
73
73
|
#
|
@@ -86,7 +86,7 @@ module Mihari
|
|
86
86
|
)
|
87
87
|
end
|
88
88
|
|
89
|
-
desc "get
|
89
|
+
desc "get ID", "Get an alert"
|
90
90
|
around :with_db_connection
|
91
91
|
#
|
92
92
|
# @param [Integer] id
|
@@ -97,7 +97,7 @@ module Mihari
|
|
97
97
|
puts JSON.pretty_generate(data.as_json)
|
98
98
|
end
|
99
99
|
|
100
|
-
desc "delete
|
100
|
+
desc "delete ID", "Delete an alert"
|
101
101
|
around :with_db_connection
|
102
102
|
#
|
103
103
|
# @param [Integer] id
|
@@ -25,7 +25,7 @@ module Mihari
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
desc "list
|
28
|
+
desc "list QUERY", "List/search artifacts"
|
29
29
|
around :with_db_connection
|
30
30
|
method_option :page, type: :numeric, default: 1
|
31
31
|
method_option :limit, type: :numeric, default: 10
|
@@ -46,7 +46,7 @@ module Mihari
|
|
46
46
|
desc "list-transform QUERY", "List/search artifacts with transformation"
|
47
47
|
around :with_db_connection
|
48
48
|
method_option :template, type: :string, required: true, aliases: "-t",
|
49
|
-
|
49
|
+
desc: "Jbuilder template stringor a path to a template"
|
50
50
|
method_option :page, type: :numeric, default: 1
|
51
51
|
method_option :limit, type: :numeric, default: 10
|
52
52
|
#
|
@@ -65,7 +65,7 @@ module Mihari
|
|
65
65
|
)
|
66
66
|
end
|
67
67
|
|
68
|
-
desc "get
|
68
|
+
desc "get ID", "Get an artifact"
|
69
69
|
around :with_db_connection
|
70
70
|
#
|
71
71
|
# @param [Integer] id
|
@@ -76,7 +76,7 @@ module Mihari
|
|
76
76
|
puts JSON.pretty_generate(data.as_json)
|
77
77
|
end
|
78
78
|
|
79
|
-
desc "enrich
|
79
|
+
desc "enrich ID", "Enrich an artifact"
|
80
80
|
around :with_db_connection
|
81
81
|
#
|
82
82
|
# @param [Integer] id
|
@@ -85,7 +85,7 @@ module Mihari
|
|
85
85
|
Services::ArtifactEnricher.result(id).value!
|
86
86
|
end
|
87
87
|
|
88
|
-
desc "delete
|
88
|
+
desc "delete ID", "Delete an artifact"
|
89
89
|
around :with_db_connection
|
90
90
|
#
|
91
91
|
# @param [Integer] id
|
data/lib/mihari/commands/rule.rb
CHANGED
@@ -26,7 +26,7 @@ module Mihari
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
desc "validate
|
29
|
+
desc "validate PATH", "Validate a rule"
|
30
30
|
#
|
31
31
|
# Validate format of a rule
|
32
32
|
#
|
@@ -37,7 +37,7 @@ module Mihari
|
|
37
37
|
puts rule.data.to_yaml
|
38
38
|
end
|
39
39
|
|
40
|
-
desc "init
|
40
|
+
desc "init PATH", "Initialize a new rule"
|
41
41
|
#
|
42
42
|
# Initialize a new rule file
|
43
43
|
#
|
@@ -50,7 +50,7 @@ module Mihari
|
|
50
50
|
Services::RuleInitializer.call(path)
|
51
51
|
end
|
52
52
|
|
53
|
-
desc "list
|
53
|
+
desc "list QUERY", "List/search rules"
|
54
54
|
around :with_db_connection
|
55
55
|
method_option :page, type: :numeric, default: 1
|
56
56
|
method_option :limit, type: :numeric, default: 10
|
@@ -71,7 +71,7 @@ module Mihari
|
|
71
71
|
desc "list-transform QUERY", "List/search rules with transformation"
|
72
72
|
around :with_db_connection
|
73
73
|
method_option :template, type: :string, required: true, aliases: "-t",
|
74
|
-
|
74
|
+
desc: "Jbuilder template stringor a path to a template"
|
75
75
|
method_option :page, type: :numeric, default: 1
|
76
76
|
method_option :limit, type: :numeric, default: 10
|
77
77
|
#
|
@@ -90,7 +90,7 @@ module Mihari
|
|
90
90
|
)
|
91
91
|
end
|
92
92
|
|
93
|
-
desc "get
|
93
|
+
desc "get ID", "Get a rule"
|
94
94
|
around :with_db_connection
|
95
95
|
def get(id)
|
96
96
|
value = Services::RuleGetter.result(id).value!
|
@@ -98,7 +98,7 @@ module Mihari
|
|
98
98
|
puts JSON.pretty_generate(data.as_json)
|
99
99
|
end
|
100
100
|
|
101
|
-
desc "delete
|
101
|
+
desc "delete ID", "Delete a rule"
|
102
102
|
around :with_db_connection
|
103
103
|
#
|
104
104
|
# @param [String] id
|
@@ -11,7 +11,7 @@ module Mihari
|
|
11
11
|
thor.class_eval do
|
12
12
|
include Concerns::DatabaseConnectable
|
13
13
|
|
14
|
-
desc "search
|
14
|
+
desc "search PATH_OR_ID", "Search by a rule"
|
15
15
|
around :with_db_connection
|
16
16
|
method_option :force_overwrite, type: :boolean, default: false, aliases: "-f",
|
17
17
|
desc: "Force overwriting a rule"
|
data/lib/mihari/commands/tag.rb
CHANGED
@@ -25,7 +25,7 @@ module Mihari
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
desc "list", "List/search tags"
|
28
|
+
desc "list QUERY", "List/search tags"
|
29
29
|
around :with_db_connection
|
30
30
|
method_option :page, type: :numeric, default: 1
|
31
31
|
method_option :limit, type: :numeric, default: 10
|
@@ -46,7 +46,7 @@ module Mihari
|
|
46
46
|
desc "list-transform QUERY", "List/search tags with transformation"
|
47
47
|
around :with_db_connection
|
48
48
|
method_option :template, type: :string, required: true, aliases: "-t",
|
49
|
-
|
49
|
+
desc: "Jbuilder template stringor a path to a template"
|
50
50
|
method_option :page, type: :numeric, default: 1
|
51
51
|
method_option :limit, type: :numeric, default: 10
|
52
52
|
#
|
@@ -65,7 +65,7 @@ module Mihari
|
|
65
65
|
)
|
66
66
|
end
|
67
67
|
|
68
|
-
desc "delete
|
68
|
+
desc "delete ID", "Delete a tag"
|
69
69
|
around :with_db_connection
|
70
70
|
#
|
71
71
|
# @param [Integer] id
|
data/lib/mihari/constants.rb
CHANGED
@@ -5,7 +5,7 @@ module Mihari
|
|
5
5
|
DEFAULT_DATA_TYPES = Types::DataTypes.values.freeze
|
6
6
|
|
7
7
|
# @return [Array<Hash>]
|
8
|
-
DEFAULT_EMITTERS = Emitters::Database.
|
8
|
+
DEFAULT_EMITTERS = Emitters::Database.keys.map { |name| { emitter: name.downcase } }.freeze
|
9
9
|
|
10
10
|
# @return [Array<Hash>]
|
11
11
|
DEFAULT_ENRICHERS = Mihari.enricher_to_class.keys.map { |name| { enricher: name.downcase } }.freeze
|
data/lib/mihari/database.rb
CHANGED
@@ -156,7 +156,7 @@ module Mihari
|
|
156
156
|
Mihari::Database.connect unless connected?
|
157
157
|
yield
|
158
158
|
rescue ActiveRecord::StatementInvalid
|
159
|
-
Mihari.logger.error("
|
159
|
+
Mihari.logger.error("DB migration is not yet complete. Please run 'mihari db migrate'.")
|
160
160
|
ensure
|
161
161
|
Mihari::Database.close
|
162
162
|
end
|
data/lib/mihari/emitters/base.rb
CHANGED
@@ -19,6 +19,14 @@ module Mihari
|
|
19
19
|
@rule = rule
|
20
20
|
end
|
21
21
|
|
22
|
+
# A target to emit the data
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
#
|
26
|
+
def target
|
27
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
28
|
+
end
|
29
|
+
|
22
30
|
#
|
23
31
|
# @param [Array<Mihari::Models::Artifact>] artifacts
|
24
32
|
#
|
@@ -30,13 +38,19 @@ module Mihari
|
|
30
38
|
# @return [Dry::Monads::Result::Success<Object>, Dry::Monads::Result::Failure]
|
31
39
|
#
|
32
40
|
def result(artifacts)
|
33
|
-
Try[StandardError] do
|
41
|
+
result = Try[StandardError] do
|
34
42
|
retry_on_error(
|
35
43
|
times: retry_times,
|
36
44
|
interval: retry_interval,
|
37
45
|
exponential_backoff: retry_exponential_backoff
|
38
46
|
) { call(artifacts) }
|
39
47
|
end.to_result
|
48
|
+
|
49
|
+
if result.failure?
|
50
|
+
Mihari.logger.warn("Emitter:#{self.class.key} for #{target.truncate(32)} failed - #{result.failure}")
|
51
|
+
end
|
52
|
+
|
53
|
+
result
|
40
54
|
end
|
41
55
|
|
42
56
|
class << self
|
data/lib/mihari/emitters/misp.rb
CHANGED
@@ -19,17 +19,25 @@ module Mihari
|
|
19
19
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
20
20
|
end
|
21
21
|
|
22
|
+
#
|
23
|
+
# @param [Mihari::Models::Artifact] value
|
22
24
|
#
|
23
25
|
# @return [Dry::Monads::Result::Success<Object>, Dry::Monads::Result::Failure]
|
24
26
|
#
|
25
27
|
def result(value)
|
26
|
-
Try[StandardError] do
|
28
|
+
result = Try[StandardError] do
|
27
29
|
retry_on_error(
|
28
30
|
times: retry_times,
|
29
31
|
interval: retry_interval,
|
30
32
|
exponential_backoff: retry_exponential_backoff
|
31
33
|
) { call value }
|
32
34
|
end.to_result
|
35
|
+
|
36
|
+
if result.failure?
|
37
|
+
Mihari.logger.warn("Enricher:#{self.class.key} for #{value.truncate(32)} failed: #{result.failure}")
|
38
|
+
end
|
39
|
+
|
40
|
+
result
|
33
41
|
end
|
34
42
|
|
35
43
|
class << self
|
data/lib/mihari/rule.rb
CHANGED
@@ -188,20 +188,8 @@ module Mihari
|
|
188
188
|
def bulk_emit
|
189
189
|
return [] if enriched_artifacts.empty?
|
190
190
|
|
191
|
-
|
192
|
-
|
193
|
-
results = Parallel.map(emitters) { |emitter| emitter.result enriched_artifacts }
|
194
|
-
results.zip(emitters).map do |result_and_emitter|
|
195
|
-
result, emitter = result_and_emitter
|
196
|
-
|
197
|
-
case result
|
198
|
-
when Success
|
199
|
-
Mihari.logger.info "Emission by #{emitter.class} succeed"
|
200
|
-
else
|
201
|
-
Mihari.logger.info "Emission by #{emitter.class} failed: #{result.failure}"
|
202
|
-
end
|
203
|
-
|
204
|
-
result.value_or nil
|
191
|
+
Parallel.map(emitters) do |emitter|
|
192
|
+
emitter.result(enriched_artifacts).value_or nil
|
205
193
|
end.compact
|
206
194
|
end
|
207
195
|
|