mihari 4.6.1 → 4.7.2

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mihari/analyzers/clients/otx.rb +36 -0
  3. data/lib/mihari/analyzers/otx.rb +19 -11
  4. data/lib/mihari/analyzers/rule.rb +17 -1
  5. data/lib/mihari/commands/init.rb +25 -2
  6. data/lib/mihari/commands/search.rb +2 -7
  7. data/lib/mihari/commands/validator.rb +10 -5
  8. data/lib/mihari/constants.rb +2 -0
  9. data/lib/mihari/enrichers/google_public_dns.rb +36 -0
  10. data/lib/mihari/enrichers/whois.rb +126 -0
  11. data/lib/mihari/errors.rb +2 -0
  12. data/lib/mihari/http.rb +2 -2
  13. data/lib/mihari/models/alert.rb +6 -1
  14. data/lib/mihari/models/artifact.rb +30 -0
  15. data/lib/mihari/models/dns.rb +5 -21
  16. data/lib/mihari/models/geolocation.rb +2 -4
  17. data/lib/mihari/models/port.rb +1 -1
  18. data/lib/mihari/models/rule.rb +7 -2
  19. data/lib/mihari/models/whois.rb +1 -96
  20. data/lib/mihari/schemas/enricher.rb +9 -0
  21. data/lib/mihari/schemas/rule.rb +6 -0
  22. data/lib/mihari/structs/filters.rb +71 -0
  23. data/lib/mihari/structs/google_public_dns.rb +42 -0
  24. data/lib/mihari/structs/ipinfo.rb +4 -4
  25. data/lib/mihari/structs/rule.rb +187 -137
  26. data/lib/mihari/types.rb +7 -0
  27. data/lib/mihari/version.rb +1 -1
  28. data/lib/mihari/web/endpoints/alerts.rb +1 -1
  29. data/lib/mihari/web/endpoints/rules.rb +13 -5
  30. data/lib/mihari/web/public/index.html +1 -1
  31. data/lib/mihari/web/public/redoc-static.html +796 -763
  32. data/lib/mihari/web/public/static/css/chunk-vendors.5013d549.css +7 -0
  33. data/lib/mihari/web/public/static/js/app.3ac3bd7a.js +2 -0
  34. data/lib/mihari/web/public/static/js/app.3ac3bd7a.js.map +1 -0
  35. data/lib/mihari/web/public/static/js/{chunk-vendors.dde2116c.js → chunk-vendors.37b7208e.js} +6 -6
  36. data/lib/mihari/web/public/static/js/chunk-vendors.37b7208e.js.map +1 -0
  37. data/lib/mihari.rb +4 -2
  38. data/mihari.gemspec +8 -9
  39. data/sig/lib/mihari/cli/base.rbs +0 -2
  40. data/sig/lib/mihari/enrichers/google_public_dns.rbs +18 -0
  41. data/sig/lib/mihari/models/alert.rbs +3 -3
  42. data/sig/lib/mihari/models/rule.rbs +2 -2
  43. data/sig/lib/mihari/structs/filters.rbs +40 -0
  44. data/sig/lib/mihari/structs/google_public_dns.rbs +21 -0
  45. data/sig/lib/mihari/structs/ipinfo.rbs +2 -2
  46. data/sig/lib/mihari/structs/rule.rbs +36 -43
  47. metadata +32 -45
  48. data/lib/mihari/mixins/rule.rb +0 -84
  49. data/lib/mihari/structs/alert.rb +0 -44
  50. data/lib/mihari/web/public/static/css/chunk-vendors.06251949.css +0 -7
  51. data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js +0 -2
  52. data/lib/mihari/web/public/static/js/app-legacy.9d5c9c3d.js.map +0 -1
  53. data/lib/mihari/web/public/static/js/app.823b5af7.js +0 -2
  54. data/lib/mihari/web/public/static/js/app.823b5af7.js.map +0 -1
  55. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js +0 -25
  56. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.b110c129.js.map +0 -1
  57. data/lib/mihari/web/public/static/js/chunk-vendors.dde2116c.js.map +0 -1
  58. data/sig/lib/mihari/mixins/rule.rbs +0 -36
  59. data/sig/lib/mihari/structs/alert.rbs +0 -27
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "whois-parser"
4
-
5
3
  module Mihari
6
4
  class WhoisRecord < ActiveRecord::Base
7
5
  belongs_to :artifact
@@ -17,100 +15,7 @@ module Mihari
17
15
  # @return [WhoisRecord, nil]
18
16
  #
19
17
  def build_by_domain(domain)
20
- domain = PublicSuffix.domain(domain)
21
-
22
- # check memo
23
- if @memo.key?(domain)
24
- whois_record = @memo[domain]
25
- # return clone of the record
26
- return whois_record.dup
27
- end
28
-
29
- record = Whois.whois(domain)
30
- parser = record.parser
31
-
32
- return nil if parser.available?
33
-
34
- whois_record = new(
35
- domain: domain,
36
- created_on: get_created_on(parser),
37
- updated_on: get_updated_on(parser),
38
- expires_on: get_expires_on(parser),
39
- registrar: get_registrar(parser),
40
- contacts: get_contacts(parser)
41
- )
42
- # set memo
43
- @memo[domain] = whois_record
44
- whois_record
45
- rescue Whois::Error, Whois::ParserError, Timeout::Error
46
- nil
47
- end
48
-
49
- private
50
-
51
- #
52
- # Get created_on
53
- #
54
- # @param [::Whois::Parser:] parser
55
- #
56
- # @return [Date, nil]
57
- #
58
- def get_created_on(parser)
59
- parser.created_on
60
- rescue ::Whois::AttributeNotImplemented
61
- nil
62
- end
63
-
64
- #
65
- # Get updated_on
66
- #
67
- # @param [::Whois::Parser:] parser
68
- #
69
- # @return [Date, nil]
70
- #
71
- def get_updated_on(parser)
72
- parser.updated_on
73
- rescue ::Whois::AttributeNotImplemented
74
- nil
75
- end
76
-
77
- #
78
- # Get expires_on
79
- #
80
- # @param [::Whois::Parser:] parser
81
- #
82
- # @return [Date, nil]
83
- #
84
- def get_expires_on(parser)
85
- parser.expires_on
86
- rescue ::Whois::AttributeNotImplemented
87
- nil
88
- end
89
-
90
- #
91
- # Get registrar
92
- #
93
- # @param [::Whois::Parser:] parser
94
- #
95
- # @return [Hash, nil]
96
- #
97
- def get_registrar(parser)
98
- parser.registrar&.to_h
99
- rescue ::Whois::AttributeNotImplemented
100
- nil
101
- end
102
-
103
- #
104
- # Get contacts
105
- #
106
- # @param [::Whois::Parser:] parser
107
- #
108
- # @return [Array[Hash], nil]
109
- #
110
- def get_contacts(parser)
111
- parser.contacts.map(&:to_h)
112
- rescue ::Whois::AttributeNotImplemented
113
- nil
18
+ Enrichers::Whois.query domain
114
19
  end
115
20
  end
116
21
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Schemas
5
+ Enricher = Dry::Schema.Params do
6
+ required(:enricher).value(Types::EnricherTypes)
7
+ end
8
+ end
9
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "mihari/schemas/analyzer"
4
4
  require "mihari/schemas/emitter"
5
+ require "mihari/schemas/enricher"
5
6
 
6
7
  module Mihari
7
8
  module Schemas
@@ -20,6 +21,8 @@ module Mihari
20
21
 
21
22
  optional(:emitters).value(:array).each { Emitter | MISP | TheHive | Slack | HTTP }
22
23
 
24
+ optional(:enrichers).value(:array).each(Enricher)
25
+
23
26
  optional(:allowed_data_types).value(array[Types::DataTypes]).default(ALLOWED_DATA_TYPES)
24
27
  optional(:disallowed_data_values).value(array[:string]).default([])
25
28
 
@@ -35,6 +38,9 @@ module Mihari
35
38
  emitters = h[:emitters]
36
39
  h[:emitters] = emitters || DEFAULT_EMITTERS
37
40
 
41
+ enrichers = h[:enrichers]
42
+ h[:enrichers] = enrichers || DEFAULT_ENRICHERS
43
+
38
44
  h
39
45
  end
40
46
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Structs
5
+ module Filters
6
+ module Alert
7
+ class SearchFilter < Dry::Struct
8
+ attribute? :artifact_data, Types::String.optional
9
+ attribute? :description, Types::String.optional
10
+ attribute? :source, Types::String.optional
11
+ attribute? :tag_name, Types::String.optional
12
+ attribute? :title, Types::String.optional
13
+ attribute? :from_at, Types::DateTime.optional
14
+ attribute? :to_at, Types::DateTime.optional
15
+ attribute? :asn, Types::Int.optional
16
+ attribute? :dns_record, Types::String.optional
17
+ attribute? :reverse_dns_name, Types::String.optional
18
+
19
+ def valid_artifact_filters?
20
+ !(artifact_data || asn || dns_record || reverse_dns_name).nil?
21
+ end
22
+ end
23
+
24
+ class SearchFilterWithPagination < SearchFilter
25
+ attribute? :page, Types::Int.default(1)
26
+ attribute? :limit, Types::Int.default(10)
27
+
28
+ def without_pagination
29
+ SearchFilter.new(
30
+ artifact_data: artifact_data,
31
+ description: description,
32
+ from_at: from_at,
33
+ source: source,
34
+ tag_name: tag_name,
35
+ title: title,
36
+ to_at: to_at,
37
+ asn: asn,
38
+ dns_record: dns_record,
39
+ reverse_dns_name: reverse_dns_name
40
+ )
41
+ end
42
+ end
43
+ end
44
+
45
+ module Rule
46
+ class SearchFilter < Dry::Struct
47
+ attribute? :description, Types::String.optional
48
+ attribute? :tag_name, Types::String.optional
49
+ attribute? :title, Types::String.optional
50
+ attribute? :from_at, Types::DateTime.optional
51
+ attribute? :to_at, Types::DateTime.optional
52
+ end
53
+
54
+ class SearchFilterWithPagination < SearchFilter
55
+ attribute? :page, Types::Int.default(1)
56
+ attribute? :limit, Types::Int.default(10)
57
+
58
+ def without_pagination
59
+ SearchFilter.new(
60
+ description: description,
61
+ from_at: from_at,
62
+ tag_name: tag_name,
63
+ title: title,
64
+ to_at: to_at
65
+ )
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Structs
5
+ module GooglePublicDNS
6
+ INT_TYPE_TO_TYPE = {
7
+ 1 => "A",
8
+ 2 => "NS",
9
+ 5 => "CNAME",
10
+ 16 => "TXT",
11
+ 28 => "AAAA"
12
+ }
13
+
14
+ class Answer < Dry::Struct
15
+ attribute :name, Types::String
16
+ attribute :data, Types::String
17
+ attribute :resource_type, Types::String
18
+
19
+ def self.from_dynamic!(d)
20
+ d = Types::Hash[d]
21
+ resource_type = INT_TYPE_TO_TYPE[d.fetch("type")]
22
+ new(
23
+ name: d.fetch("name"),
24
+ data: d.fetch("data"),
25
+ resource_type: resource_type
26
+ )
27
+ end
28
+ end
29
+
30
+ class Response < Dry::Struct
31
+ attribute :answers, Types.Array(Answer)
32
+
33
+ def self.from_dynamic!(d)
34
+ d = Types::Hash[d]
35
+ new(
36
+ answers: d.fetch("Answer", []).map { |x| Answer.from_dynamic!(x) }
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -6,8 +6,8 @@ module Mihari
6
6
  class Response < Dry::Struct
7
7
  attribute :ip, Types::String
8
8
  attribute :hostname, Types::String.optional
9
- attribute :loc, Types::String
10
- attribute :country_code, Types::String
9
+ attribute :loc, Types::String.optional
10
+ attribute :country_code, Types::String.optional
11
11
  attribute :asn, Types::Integer.optional
12
12
 
13
13
  class << self
@@ -23,9 +23,9 @@ module Mihari
23
23
 
24
24
  new(
25
25
  ip: d.fetch("ip"),
26
- loc: d.fetch("loc"),
26
+ loc: d["loc"],
27
27
  hostname: d["hostname"],
28
- country_code: d.fetch("country"),
28
+ country_code: d["country"],
29
29
  asn: asn
30
30
  )
31
31
  end
@@ -1,186 +1,236 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
4
+ require "erb"
5
+ require "pathname"
6
+ require "yaml"
7
+
3
8
  module Mihari
4
9
  module Structs
5
- module Rule
6
- class SearchFilter < Dry::Struct
7
- attribute? :description, Types::String.optional
8
- attribute? :tag_name, Types::String.optional
9
- attribute? :title, Types::String.optional
10
- attribute? :from_at, Types::DateTime.optional
11
- attribute? :to_at, Types::DateTime.optional
10
+ class Rule
11
+ # @return [Hash]
12
+ attr_reader :data
13
+
14
+ # @return [String]
15
+ attr_reader :yaml
16
+
17
+ # @return [Array, nil]
18
+ attr_reader :errors
19
+
20
+ # @return [String]
21
+ attr_writer :id
22
+
23
+ def initialize(data, yaml)
24
+ @data = data.deep_symbolize_keys
25
+ @yaml = yaml
26
+
27
+ @errors = nil
28
+ @no_method_error = nil
29
+
30
+ validate
12
31
  end
13
32
 
14
- class SearchFilterWithPagination < SearchFilter
15
- attribute? :page, Types::Int.default(1)
16
- attribute? :limit, Types::Int.default(10)
17
-
18
- def without_pagination
19
- SearchFilter.new(
20
- description: description,
21
- from_at: from_at,
22
- tag_name: tag_name,
23
- title: title,
24
- to_at: to_at
25
- )
33
+ #
34
+ # @return [Boolean]
35
+ #
36
+ def errors?
37
+ return false if @errors.nil?
38
+
39
+ !@errors.empty?
40
+ end
41
+
42
+ #
43
+ # @return [Array<String>]
44
+ #
45
+ def error_messages
46
+ return [] if @errors.nil?
47
+
48
+ @errors.messages.filter_map do |message|
49
+ path = message.path.map(&:to_s).join
50
+ "#{path} #{message.text}"
51
+ rescue NoMethodError
52
+ nil
26
53
  end
27
54
  end
28
55
 
29
- class Rule
30
- # @return [Hash]
31
- attr_reader :data
56
+ def validate
57
+ begin
58
+ contract = Schemas::RuleContract.new
59
+ result = contract.call(data)
60
+ rescue NoMethodError => e
61
+ @no_method_error = e
62
+ return
63
+ end
64
+
65
+ @data = result.to_h
66
+ @errors = result.errors
67
+ end
32
68
 
33
- # @return [String]
34
- attr_reader :yaml
69
+ def validate!
70
+ raise RuleValidationError, "Data should be a hash" unless data.is_a?(Hash)
71
+ raise RuleValidationError, error_messages.join("\n") if errors?
72
+ raise RuleValidationError, "Something wrong with queries, emitters or enrichers." unless @no_method_error.nil?
73
+ rescue RuleValidationError => e
74
+ message = "Failed to parse the input as a rule"
75
+ message += ": #{e.message}" unless e.message.empty?
76
+ Mihari.logger.error message
35
77
 
36
- # @return [Array, nil]
37
- attr_reader :errors
78
+ raise e
79
+ end
38
80
 
39
- # @return [String]
40
- attr_writer :id
81
+ def [](key)
82
+ data[key.to_sym]
83
+ end
41
84
 
42
- def initialize(data, yaml)
43
- @data = data.deep_symbolize_keys
44
- @yaml = yaml
85
+ #
86
+ # @return [String]
87
+ #
88
+ def id
89
+ @id ||= data[:id] || UUIDTools::UUID.md5_create(UUIDTools::UUID_URL_NAMESPACE, data.to_yaml).to_s
90
+ end
45
91
 
46
- @errors = nil
47
- @no_method_error = nil
92
+ #
93
+ # @return [String]
94
+ #
95
+ def title
96
+ @title ||= data[:title]
97
+ end
48
98
 
49
- validate
50
- end
99
+ #
100
+ # @return [String]
101
+ #
102
+ def description
103
+ @description ||= data[:description]
104
+ end
51
105
 
52
- #
53
- # @return [Boolean]
54
- #
55
- def errors?
56
- return false if @errors.nil?
106
+ #
107
+ # @return [Mihari::Rule]
108
+ #
109
+ def to_model
110
+ rule = Mihari::Rule.find(id)
111
+
112
+ rule.title = title
113
+ rule.description = description
114
+ rule.data = data
115
+ rule.yaml = yaml
116
+
117
+ rule
118
+ rescue ActiveRecord::RecordNotFound
119
+ Mihari::Rule.new(
120
+ id: id,
121
+ title: title,
122
+ description: description,
123
+ data: data,
124
+ yaml: yaml
125
+ )
126
+ end
57
127
 
58
- !@errors.empty?
59
- end
128
+ #
129
+ # @return [Mihari::Analyzers::Rule]
130
+ #
131
+ def to_analyzer
132
+ analyzer = Mihari::Analyzers::Rule.new(
133
+ title: self[:title],
134
+ description: self[:description],
135
+ tags: self[:tags],
136
+ queries: self[:queries],
137
+ allowed_data_types: self[:allowed_data_types],
138
+ disallowed_data_values: self[:disallowed_data_values],
139
+ emitters: self[:emitters],
140
+ enrichers: self[:enrichers],
141
+ id: id
142
+ )
143
+ analyzer.ignore_old_artifacts = self[:ignore_old_artifacts]
144
+ analyzer.ignore_threshold = self[:ignore_threshold]
145
+
146
+ analyzer
147
+ end
148
+
149
+ class << self
150
+ include Mixins::Database
60
151
 
61
152
  #
62
- # @return [Array<String>]
153
+ # @param [Mihari::Rule] model
63
154
  #
64
- def error_messages
65
- return [] if @errors.nil?
155
+ # @return [Mihari::Structs::Rule]
156
+ #
157
+ def from_model(model)
158
+ data = model.data.deep_symbolize_keys
159
+ # set ID if YAML data do not have ID
160
+ data[:id] = model.id unless data.key?(:id)
66
161
 
67
- @errors.messages.map do |message|
68
- path = message.path.map(&:to_s).join
69
- "#{path} #{message.text}"
70
- end
162
+ Structs::Rule.new(data, model.yaml)
71
163
  end
72
164
 
73
- def validate
74
- begin
75
- contract = Schemas::RuleContract.new
76
- result = contract.call(data)
77
- rescue NoMethodError => e
78
- @no_method_error = e
79
- return
80
- end
165
+ #
166
+ # @param [String] yaml
167
+ #
168
+ # @return [Mihari::Structs::Rule]
169
+ # @param [String, nil] id
170
+ #
171
+ def from_yaml(yaml, id: nil)
172
+ data = load_erb_yaml(yaml)
173
+ # set ID if id is given & YAML data do not have ID
174
+ data[:id] = id if !id.nil? && !data.key?(:id)
81
175
 
82
- @data = result.to_h
83
- @errors = result.errors
176
+ Structs::Rule.new(data, yaml)
84
177
  end
85
178
 
86
- def validate!
87
- raise RuleValidationError, "Data should be a hash" unless data.is_a?(Hash)
179
+ #
180
+ # @param [String] path_or_id Path to YAML file or YAML string or ID of a rule in the database
181
+ #
182
+ # @return [Mihari::Structs::Rule]
183
+ #
184
+ def from_path_or_id(path_or_id)
185
+ yaml = nil
88
186
 
89
- raise RuleValidationError, error_messages.join("\n") if errors?
187
+ yaml = load_yaml_from_file(path_or_id) if File.exist?(path_or_id)
188
+ yaml = load_yaml_from_db(path_or_id) if yaml.nil?
90
189
 
91
- raise RuleValidationError, "Something wrong with queries or emitters." unless @no_method_error.nil?
190
+ Structs::Rule.from_yaml yaml
92
191
  end
93
192
 
94
- def [](key)
95
- data[key.to_sym]
96
- end
193
+ private
97
194
 
98
195
  #
99
- # @return [String]
196
+ # Load ERR templated YAML
100
197
  #
101
- def id
102
- @id ||= data[:id] || UUIDTools::UUID.md5_create(UUIDTools::UUID_URL_NAMESPACE, data.to_yaml).to_s
103
- end
104
-
198
+ # @param [String] yaml
105
199
  #
106
- # @return [String]
200
+ # @return [Hash]
107
201
  #
108
- def title
109
- @title ||= data[:title]
202
+ def load_erb_yaml(yaml)
203
+ YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date, Symbol], symbolize_names: true)
204
+ rescue Psych::SyntaxError => e
205
+ raise YAMLSyntaxError, e.message
110
206
  end
111
207
 
112
208
  #
113
- # @return [String]
209
+ # Load YAML string from path
114
210
  #
115
- def description
116
- @description ||= data[:description]
117
- end
118
-
211
+ # @param [String] path
119
212
  #
120
- # @return [Mihari::Rule]
213
+ # @return [String, nil]
121
214
  #
122
- def to_model
123
- rule = Mihari::Rule.find(id)
215
+ def load_yaml_from_file(path)
216
+ return nil unless Pathname(path).exist?
124
217
 
125
- rule.title = title
126
- rule.description = description
127
- rule.data = data
128
- rule.yaml = yaml
129
-
130
- rule
131
- rescue ActiveRecord::RecordNotFound
132
- Mihari::Rule.new(
133
- id: id,
134
- title: title,
135
- description: description,
136
- data: data,
137
- yaml: yaml
138
- )
218
+ File.read path
139
219
  end
140
220
 
141
221
  #
142
- # @return [Mihari::Analyzers::Rule]
222
+ # Load YAML string from the database
143
223
  #
144
- def to_analyzer
145
- analyzer = Mihari::Analyzers::Rule.new(
146
- title: self[:title],
147
- description: self[:description],
148
- tags: self[:tags],
149
- queries: self[:queries],
150
- allowed_data_types: self[:allowed_data_types],
151
- disallowed_data_values: self[:disallowed_data_values],
152
- emitters: self[:emitters],
153
- id: id
154
- )
155
- analyzer.ignore_old_artifacts = self[:ignore_old_artifacts]
156
- analyzer.ignore_threshold = self[:ignore_threshold]
157
-
158
- analyzer
159
- end
160
-
161
- class << self
162
- include Mixins::Rule
163
-
164
- #
165
- # @param [Mihari::Rule] model
166
- #
167
- # @return [Mihari::Structs::Rule::Rule]
168
- #
169
- def from_model(model)
170
- data = model.data.deep_symbolize_keys
171
- data[:id] = model.id unless data.key?(:id)
172
-
173
- Structs::Rule::Rule.new(data, model.yaml)
174
- end
175
-
176
- #
177
- # @param [String] yaml
178
- #
179
- # @return [Mihari::Structs::Rule::Rule]
180
- #
181
- def from_yaml(yaml)
182
- data = load_erb_yaml(yaml)
183
- Structs::Rule::Rule.new(data, yaml)
224
+ # @param [String] id <description>
225
+ #
226
+ # @return [Hash]
227
+ #
228
+ def load_yaml_from_db(id)
229
+ with_db_connection do
230
+ rule = Mihari::Rule.find(id)
231
+ rule.yaml || rule.symbolized_data.to_yaml
232
+ rescue ActiveRecord::RecordNotFound
233
+ raise ArgumentError, "ID:#{id} is not found in the database"
184
234
  end
185
235
  end
186
236
  end
data/lib/mihari/types.rb CHANGED
@@ -21,5 +21,12 @@ module Mihari
21
21
  "database",
22
22
  "webhook"
23
23
  )
24
+
25
+ EnricherTypes = Types::String.enum(
26
+ "whois",
27
+ "ipinfo",
28
+ "shodan",
29
+ "google_public_dns"
30
+ )
24
31
  end
25
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "4.6.1"
4
+ VERSION = "4.7.2"
5
5
  end
@@ -43,7 +43,7 @@ module Mihari
43
43
  # symbolize hash keys
44
44
  filter = filter.to_h.symbolize_keys
45
45
 
46
- search_filter_with_pagenation = Structs::Alert::SearchFilterWithPagination.new(**filter)
46
+ search_filter_with_pagenation = Structs::Filters::Alert::SearchFilterWithPagination.new(**filter)
47
47
  alerts = Mihari::Alert.search(search_filter_with_pagenation)
48
48
  total = Mihari::Alert.count(search_filter_with_pagenation.without_pagination)
49
49