mihari 4.2.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +25 -3
  3. data/lib/mihari/analyzers/rule.rb +49 -6
  4. data/lib/mihari/constants.rb +2 -0
  5. data/lib/mihari/emitters/base.rb +3 -0
  6. data/lib/mihari/emitters/http.rb +127 -0
  7. data/lib/mihari/emitters/webhook.rb +7 -16
  8. data/lib/mihari/entities/rule.rb +5 -0
  9. data/lib/mihari/feed/reader.rb +6 -4
  10. data/lib/mihari/http.rb +19 -14
  11. data/lib/mihari/schemas/rule.rb +30 -4
  12. data/lib/mihari/structs/rule.rb +1 -0
  13. data/lib/mihari/types.rb +11 -3
  14. data/lib/mihari/version.rb +1 -1
  15. data/lib/mihari/web/public/index.html +1 -1
  16. data/lib/mihari/web/public/redoc-static.html +57 -51
  17. data/lib/mihari/web/public/static/css/app.0de4b715.css +1 -0
  18. data/lib/mihari/web/public/static/css/chunk-vendors.c57bb3fd.css +7 -0
  19. data/lib/mihari/web/public/static/fonts/fa-brands-400.edf40f86.woff2 +0 -0
  20. data/lib/mihari/web/public/static/fonts/fa-brands-400.f7223235.ttf +0 -0
  21. data/lib/mihari/web/public/static/fonts/fa-regular-400.3665ebc7.woff2 +0 -0
  22. data/lib/mihari/web/public/static/fonts/fa-regular-400.a7fde52b.ttf +0 -0
  23. data/lib/mihari/web/public/static/fonts/fa-solid-900.0d2abd43.woff2 +0 -0
  24. data/lib/mihari/web/public/static/fonts/fa-solid-900.5b03221c.ttf +0 -0
  25. data/lib/mihari/web/public/static/fonts/fa-v4compatibility.42932bea.ttf +0 -0
  26. data/lib/mihari/web/public/static/js/app-legacy.e451304b.js +2 -0
  27. data/lib/mihari/web/public/static/js/app-legacy.e451304b.js.map +1 -0
  28. data/lib/mihari/web/public/static/js/app.e74e91d7.js +2 -0
  29. data/lib/mihari/web/public/static/js/app.e74e91d7.js.map +1 -0
  30. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.41357cdf.js +25 -0
  31. data/lib/mihari/web/public/static/js/chunk-vendors-legacy.41357cdf.js.map +1 -0
  32. data/lib/mihari/web/public/static/js/chunk-vendors.c5525f1e.js +31 -0
  33. data/lib/mihari/web/public/static/js/chunk-vendors.c5525f1e.js.map +1 -0
  34. data/lib/mihari.rb +11 -9
  35. data/mihari.gemspec +10 -5
  36. data/sig/lib/mihari/constants.rbs +2 -0
  37. data/sig/lib/mihari/emitters/http.rbs +35 -0
  38. data/sig/lib/mihari/http.rbs +2 -3
  39. data/sig/lib/mihari/structs/rule.rbs +1 -3
  40. data/sig/lib/mihari/types.rbs +2 -0
  41. metadata +76 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 853724da55a4225403595284e6437da9816d016d99e380d239507e1eecdf35cd
4
- data.tar.gz: 35e5ef84d38c67eaa1670a5e6261aed5c629966921ca3b14fb2509c9e3993c45
3
+ metadata.gz: dc5b6ab7daa3030a0ef0778df2753247bde10b978be709102ecf1be60b672a9c
4
+ data.tar.gz: 1c1c283cf1e2989e9e94e0aa582567dd71b7900f5c5faa40a0ce3f1b327982a3
5
5
  SHA512:
6
- metadata.gz: 50855245bf70579da57c26859d2ab491e5238f7200c1de50cc7f46cafcd94c57e411e0fdbffdd60ddb00df2c8783ed051c87be24963fe10c5459784f337bd8d3
7
- data.tar.gz: 436e6c0f3ceacb5b72f4212e83832759320cce109310736d87f6d1bfb49ac61f32a25a4d44cf133d2d5794466dea72bd69244997ccbbc6525418bc9aef403d0f
6
+ metadata.gz: 918ee19022035b5f5e3db9a00318253803b1cc430b45676225af94861fe6dc6fe51343545d224e6094f09ad37dc003713fbbcb1777904b01205903e22d05bf23
7
+ data.tar.gz: c5ce99eb3d8e01b1b2a8ac51916afa172c1fecb55611a3b8aa76e686c72801beb11135ed1c8aed31e11b693b7467dbe513902f55e229c834bbfcb09460ba4202
@@ -1,9 +1,13 @@
1
1
  name: Ruby CI
2
2
 
3
- on: [pull_request]
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
4
8
 
5
9
  jobs:
6
- build:
10
+ test:
7
11
  runs-on: ubuntu-latest
8
12
 
9
13
  services:
@@ -39,7 +43,7 @@ jobs:
39
43
  strategy:
40
44
  fail-fast: false
41
45
  matrix:
42
- ruby: [2.7, "3.0"]
46
+ ruby: [2.7, "3.0", 3.1]
43
47
 
44
48
  steps:
45
49
  - uses: actions/checkout@v3
@@ -65,3 +69,21 @@ jobs:
65
69
  DATABASE: mysql2://mysql:mysql@127.0.0.1:3306/test
66
70
  run: |
67
71
  bundle exec rake
72
+
73
+ - name: Coveralls Parallel
74
+ uses: coverallsapp/github-action@master
75
+ with:
76
+ github-token: ${{ secrets.github_token }}
77
+ flag-name: run-${{ matrix.ruby-version }}
78
+ parallel: true
79
+
80
+ coverage:
81
+ name: Coverage
82
+ needs: test
83
+ runs-on: ubuntu-latest
84
+ steps:
85
+ - name: Coveralls Finished
86
+ uses: coverallsapp/github-action@master
87
+ with:
88
+ github-token: ${{ secrets.github_token }}
89
+ parallel-finished: true
@@ -28,6 +28,15 @@ module Mihari
28
28
  "zoomeye" => ZoomEye
29
29
  }.freeze
30
30
 
31
+ EMITTER_TO_CLASS = {
32
+ "database" => Emitters::Database,
33
+ "http" => Emitters::HTTP,
34
+ "misp" => Emitters::MISP,
35
+ "slack" => Emitters::Slack,
36
+ "the_hive" => Emitters::TheHive,
37
+ "webhook" => Emitters::Webhook
38
+ }.freeze
39
+
31
40
  class Rule < Base
32
41
  include Mixins::DisallowedDataValue
33
42
  include Mixins::Rule
@@ -41,6 +50,8 @@ module Mihari
41
50
  option :allowed_data_types, default: proc { ALLOWED_DATA_TYPES }
42
51
  option :disallowed_data_values, default: proc { [] }
43
52
 
53
+ option :emitters, optional: true
54
+
44
55
  attr_reader :source
45
56
 
46
57
  def initialize(**kwargs)
@@ -48,6 +59,8 @@ module Mihari
48
59
 
49
60
  @source = id
50
61
 
62
+ @emitters = emitters || DEFAULT_EMITTERS
63
+
51
64
  validate_analyzer_configurations
52
65
  end
53
66
 
@@ -59,18 +72,20 @@ module Mihari
59
72
  def artifacts
60
73
  artifacts = []
61
74
 
62
- queries.each do |params|
63
- analyzer_name = params[:analyzer]
75
+ queries.each do |original_params|
76
+ parmas = original_params.deep_dup
77
+
78
+ analyzer_name = parmas[:analyzer]
64
79
  klass = get_analyzer_class(analyzer_name)
65
80
 
66
- query = params[:query]
81
+ query = parmas[:query]
67
82
 
68
83
  # set interval in the top level
69
- options = params[:options] || {}
84
+ options = parmas[:options] || {}
70
85
  interval = options[:interval]
71
- params[:interval] = interval if interval
86
+ parmas[:interval] = interval if interval
72
87
 
73
- analyzer = klass.new(query, **params)
88
+ analyzer = klass.new(query, **parmas)
74
89
 
75
90
  # Use #normalized_artifacts method to get atrifacts as Array<Mihari::Artifact>
76
91
  # So Mihari::Artifact object has "source" attribute (e.g. "Shodan")
@@ -120,6 +135,34 @@ module Mihari
120
135
 
121
136
  private
122
137
 
138
+ #
139
+ # Get emitter class
140
+ #
141
+ # @param [String] emitter_name
142
+ #
143
+ # @return [Class<Mihari::Emitters::Base>] emitter class
144
+ #
145
+ def get_emitter_class(emitter_name)
146
+ emitter = EMITTER_TO_CLASS[emitter_name]
147
+ return emitter if emitter
148
+
149
+ raise ArgumentError, "#{emitter_name} is not supported"
150
+ end
151
+
152
+ def valid_emitters
153
+ @valid_emitters ||= emitters.filter_map do |original_params|
154
+ params = original_params.deep_dup
155
+
156
+ name = params[:emitter]
157
+ params.delete(:emitter)
158
+
159
+ klass = get_emitter_class(name)
160
+ emitter = klass.new(**params)
161
+
162
+ emitter.valid? ? emitter : nil
163
+ end
164
+ end
165
+
123
166
  #
124
167
  # Get analyzer class
125
168
  #
@@ -2,4 +2,6 @@
2
2
 
3
3
  module Mihari
4
4
  ALLOWED_DATA_TYPES = ["hash", "ip", "domain", "url", "mail"].freeze
5
+
6
+ DEFAULT_EMITTERS = ["database", "misp", "slack", "the_hive", "webhook"].map { |name| { emitter: name } }.freeze
5
7
  end
@@ -6,6 +6,9 @@ module Mihari
6
6
  include Mixins::Configurable
7
7
  include Mixins::Retriable
8
8
 
9
+ def initialize(*)
10
+ end
11
+
9
12
  class << self
10
13
  def inherited(child)
11
14
  super
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module Mihari
6
+ module Emitters
7
+ class PayloadTemplate < ERB
8
+ def self.template
9
+ %{
10
+ {
11
+ "title": "<%= @title %>",
12
+ "description": "<%= @description %>",
13
+ "source": "<%= @source %>",
14
+ "artifacts": [
15
+ <% @artifacts.each_with_index do |artifact, idx| %>
16
+ "<%= artifact.data %>"
17
+ <%= ',' if idx < (@artifacts.length - 1) %>
18
+ <% end %>
19
+ ],
20
+ "tags": [
21
+ <% @tags.each_with_index do |tag, idx| %>
22
+ "<%= tag %>"
23
+ <%= ',' if idx < (@tags.length - 1) %>
24
+ <% end %>
25
+ ]
26
+ }
27
+ }
28
+ end
29
+
30
+ def initialize(title:, description:, artifacts:, source:, tags:, options: {})
31
+ @title = title
32
+ @description = description
33
+ @artifacts = artifacts
34
+ @source = source
35
+ @tags = tags
36
+
37
+ @template = options.fetch(:template, self.class.template)
38
+ super(@template)
39
+ end
40
+
41
+ def result
42
+ super(binding)
43
+ end
44
+ end
45
+
46
+ class HTTP < Base
47
+ # @return [Addressable::URI, nil]
48
+ attr_reader :uri
49
+
50
+ # @return [Hash]
51
+ attr_reader :http_request_headers
52
+
53
+ # @return [String]
54
+ attr_reader :http_request_method
55
+
56
+ # @return [String, nil]
57
+ attr_reader :template
58
+
59
+ def initialize(*args, **kwargs)
60
+ super(*args, **kwargs)
61
+
62
+ uri = kwargs[:url] || kwargs[:uri]
63
+ http_request_headers = kwargs[:http_request_headers] || {}
64
+ http_request_method = kwargs[:http_request_method] || "POST"
65
+ template = kwargs[:template]
66
+
67
+ @uri = Addressable::URI.parse(uri) if uri
68
+ @http_request_headers = http_request_headers
69
+ @http_request_method = http_request_method
70
+ @template = template
71
+ end
72
+
73
+ def emit(title:, description:, artifacts:, source:, tags:)
74
+ return if artifacts.empty?
75
+
76
+ res = nil
77
+
78
+ payload_ = payload_as_string(
79
+ title: title,
80
+ description: description,
81
+ artifacts: artifacts,
82
+ source: source,
83
+ tags: tags
84
+ )
85
+ payload = JSON.parse(payload_)
86
+
87
+ client = Mihari::HTTP.new(uri, headers: http_request_headers, payload: payload)
88
+
89
+ case http_request_method
90
+ when "GET"
91
+ res = client.get
92
+ when "POST"
93
+ res = client.post
94
+ end
95
+
96
+ res
97
+ end
98
+
99
+ def valid?
100
+ return false if uri.nil?
101
+
102
+ ["http", "https"].include? uri.scheme.downcase
103
+ end
104
+
105
+ private
106
+
107
+ def payload_as_string(title:, description:, artifacts:, source:, tags:)
108
+ @payload_as_string ||= [].tap do |out|
109
+ options = {}
110
+ unless template.nil?
111
+ options[:template] = File.read(template)
112
+ end
113
+
114
+ payload_template = PayloadTemplate.new(
115
+ title: title,
116
+ description: description,
117
+ artifacts: artifacts,
118
+ source: source,
119
+ tags: tags,
120
+ options: options
121
+ )
122
+ out << payload_template.result
123
+ end.first
124
+ end
125
+ end
126
+ end
127
+ end
@@ -11,20 +11,11 @@ module Mihari
11
11
  def emit(title:, description:, artifacts:, source:, tags:)
12
12
  return if artifacts.empty?
13
13
 
14
- uri = Addressable::URI.parse(Mihari.config.webhook_url)
15
- data = {
16
- title: title,
17
- description: description,
18
- artifacts: artifacts.map(&:data),
19
- source: source,
20
- tags: tags
21
- }
14
+ headers = { 'content-type': "application/x-www-form-urlencoded" }
15
+ headers["content-type"] = "application/json" if use_json_body?
22
16
 
23
- if use_json_body?
24
- Net::HTTP.post(uri, data.to_json, "Content-Type" => "application/json")
25
- else
26
- Net::HTTP.post_form(uri, data)
27
- end
17
+ emitter = Emitters::HTTP.new(uri: Mihari.config.webhook_url)
18
+ emitter.emit(title: title, description: description, artifacts: artifacts, source: source, tags: tags)
28
19
  end
29
20
 
30
21
  private
@@ -45,16 +36,16 @@ module Mihari
45
36
  #
46
37
  # Check whether a webhook URL is set or not
47
38
  #
48
- # @return [<Type>] <description>
39
+ # @return [Boolean]
49
40
  #
50
41
  def webhook_url?
51
42
  !webhook_url.nil?
52
43
  end
53
44
 
54
45
  #
55
- # Check whether to use JSON body or NOT
46
+ # Check whether to use JSON body or not
56
47
  #
57
- # @return [<Type>] <description>
48
+ # @return [Boolean]
58
49
  #
59
50
  def use_json_body?
60
51
  @use_json_body ||= Mihari.config.webhook_use_json_body
@@ -8,6 +8,10 @@ module Mihari
8
8
  expose :interval, documentation: { type: Integer, required: false }
9
9
  end
10
10
 
11
+ class Emitter < Grape::Entity
12
+ expose :emitter, documentation: { type: String, required: true }
13
+ end
14
+
11
15
  class Rule < Grape::Entity
12
16
  expose :id, documentation: { type: String, required: true }
13
17
 
@@ -16,6 +20,7 @@ module Mihari
16
20
  expose :title, documentation: { type: String, required: true }
17
21
  expose :description, documentation: { type: String, required: true }
18
22
  expose :queries, using: Entities::Query, documentation: { type: Entities::Query, is_array: true, required: true }
23
+ expose :emitters, using: Entities::Emitter, documentation: { type: Entities::Emitter, is_array: true, required: false }
19
24
  expose :tags, documentation: { type: String, is_array: true }
20
25
  expose :allowed_data_types, documentation: { type: String, is_array: true }, as: :allowedDtaTypes
21
26
  expose :disallowed_data_values, documentation: { type: String, is_array: true }, as: :disallowedDataValues
@@ -1,25 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "csv"
4
+ require "insensitive_hash"
4
5
 
5
6
  module Mihari
6
7
  module Feed
7
8
  class Reader
8
- attr_reader :uri, :http_request_headers, :http_request_method, :http_request_payload_type, :http_request_payload
9
+ attr_reader :uri, :http_request_headers, :http_request_method, :http_request_payload
9
10
 
10
11
  def initialize(uri, http_request_headers: {}, http_request_method: "GET", http_request_payload_type: nil, http_request_payload: {})
11
12
  @uri = Addressable::URI.parse(uri)
12
- @http_request_headers = http_request_headers
13
+ @http_request_headers = http_request_headers.insensitive
13
14
  @http_request_method = http_request_method
14
- @http_request_payload_type = http_request_payload_type
15
15
  @http_request_payload = http_request_payload
16
+
17
+ http_request_headers["content-type"] = http_request_payload_type if http_request_payload_type
16
18
  end
17
19
 
18
20
  def read
19
21
  return read_file(uri.path) if uri.scheme == "file"
20
22
 
21
23
  res = nil
22
- client = HTTP.new(uri, headers: http_request_headers, payload: http_request_payload, payload_type: http_request_payload_type)
24
+ client = HTTP.new(uri, headers: http_request_headers, payload: http_request_payload)
23
25
 
24
26
  res = client.get if http_request_method == "GET"
25
27
  res = client.post if http_request_method == "POST"
data/lib/mihari/http.rb CHANGED
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "insensitive_hash"
4
+
3
5
  module Mihari
4
6
  class HTTP
5
- attr_reader :uri, :headers, :payload_type, :payload
7
+ attr_reader :uri, :headers, :payload
6
8
 
7
- def initialize(uri, headers: {}, payload_type: nil, payload: {})
8
- @uri = Addressable::URI.parse(uri)
9
- @headers = headers
10
- @payload_type = payload_type
9
+ def initialize(uri, headers: {}, payload: {})
10
+ @uri = uri.is_a?(Addressable::URI) ? uri : Addressable::URI.parse(uri)
11
+ @headers = headers.insensitive
11
12
  @payload = payload
12
13
  end
13
14
 
@@ -31,12 +32,10 @@ module Mihari
31
32
  def post
32
33
  post = Net::HTTP::Post.new(uri)
33
34
 
34
- case payload_type
35
+ case content_type
35
36
  when "application/json"
36
- headers["content-type"] = "application/json" unless headers.key?("content-type")
37
37
  post.body = JSON.generate(payload)
38
38
  when "application/x-www-form-urlencoded"
39
- headers["content-type"] = "application/x-www-form-urlencoded" unless headers.key?("content-type")
40
39
  post.set_form_data(payload)
41
40
  end
42
41
 
@@ -44,19 +43,23 @@ module Mihari
44
43
  end
45
44
 
46
45
  class << self
47
- def get(uri, headers: {}, payload_type: nil, payload: {})
48
- client = new(uri, headers: headers, payload_type: payload_type, payload: payload)
46
+ def get(uri, headers: {}, payload: {})
47
+ client = new(uri, headers: headers, payload: payload)
49
48
  client.get
50
49
  end
51
50
 
52
- def post(uri, headers: {}, payload_type: nil, payload: {})
53
- client = new(uri, headers: headers, payload_type: payload_type, payload: payload)
51
+ def post(uri, headers: {}, payload: {})
52
+ client = new(uri, headers: headers, payload: payload)
54
53
  client.post
55
54
  end
56
55
  end
57
56
 
58
57
  private
59
58
 
59
+ def content_type
60
+ headers["content-type"] || "application/json"
61
+ end
62
+
60
63
  #
61
64
  # Get options for HTTP request
62
65
  #
@@ -84,8 +87,10 @@ module Mihari
84
87
 
85
88
  res = http.request(req)
86
89
 
87
- code = res.code.to_i
88
- raise HttpError, "Unsupported response code returned: #{code}" if code != 200
90
+ unless res.is_a?(Net::HTTPSuccess)
91
+ code = res.code.to_i
92
+ raise HttpError, "Unsuccessful response code returned: #{code}"
93
+ end
89
94
 
90
95
  res
91
96
  end
@@ -42,14 +42,26 @@ module Mihari
42
42
  Feed = Dry::Schema.Params do
43
43
  required(:analyzer).value(Types::String.enum("feed"))
44
44
  required(:query).value(:string)
45
- required(:http_request_method).value(Types::FeedHttpRequestMethods).default("GET")
46
- required(:http_request_headers).value(:hash).default({})
47
- required(:http_request_payload).value(:hash).default({})
48
45
  required(:selector).value(:string)
49
- optional(:http_request_payload_type).value(Types::FeedHttpRequestPayloadTypes)
46
+ optional(:http_request_method).value(Types::HttpRequestMethods).default("GET")
47
+ optional(:http_request_headers).value(:hash).default({})
48
+ optional(:http_request_payload).value(:hash).default({})
49
+ optional(:http_request_payload_type).value(Types::HttpRequestPayloadTypes)
50
50
  optional(:options).hash(AnalyzerOptions)
51
51
  end
52
52
 
53
+ Emitter = Dry::Schema.Params do
54
+ required(:emitter).value(Types::EmitterTypes)
55
+ end
56
+
57
+ HTTP = Dry::Schema.Params do
58
+ required(:emitter).value(Types::String.enum("http"))
59
+ required(:url).value(:string)
60
+ optional(:http_request_method).value(Types::HttpRequestMethods).default("POST")
61
+ optional(:http_request_headers).value(:hash).default({})
62
+ optional(:template).value(:string)
63
+ end
64
+
53
65
  Rule = Dry::Schema.Params do
54
66
  required(:title).value(:string)
55
67
  required(:description).value(:string)
@@ -63,11 +75,25 @@ module Mihari
63
75
 
64
76
  required(:queries).value(:array).each { Analyzer | Spyse | ZoomEye | Urlscan | Crtsh | Feed }
65
77
 
78
+ optional(:emitters).value(:array).each { Emitter | HTTP }
79
+
66
80
  optional(:allowed_data_types).value(array[Types::DataTypes]).default(ALLOWED_DATA_TYPES)
67
81
  optional(:disallowed_data_values).value(array[:string]).default([])
68
82
 
69
83
  optional(:ignore_old_artifacts).value(:bool).default(false)
70
84
  optional(:ignore_threshold).value(:integer).default(0)
85
+
86
+ before(:key_coercer) do |result|
87
+ # it looks like that dry-schema v1.9.1 has an issue with setting an array of schemas as a default value
88
+ # e.g. optional(:emitters).value(:array).each { Emitter | HTTP }.default(DEFAULT_EMITTERS) does not work well
89
+ # so let's do a dirty hack...
90
+ h = result.to_h
91
+
92
+ emitters = h[:emitters]
93
+ h[:emitters] = emitters || DEFAULT_EMITTERS
94
+
95
+ h
96
+ end
71
97
  end
72
98
 
73
99
  class RuleContract < Dry::Validation::Contract
@@ -133,6 +133,7 @@ module Mihari
133
133
  queries: self[:queries],
134
134
  allowed_data_types: self[:allowed_data_types],
135
135
  disallowed_data_values: self[:disallowed_data_values],
136
+ emitters: self[:emitters],
136
137
  id: id
137
138
  )
138
139
  analyzer.ignore_old_artifacts = self[:ignore_old_artifacts]
data/lib/mihari/types.rb CHANGED
@@ -22,7 +22,6 @@ module Mihari
22
22
  "circl",
23
23
  "dnpedia",
24
24
  "dnstwister",
25
- "feed",
26
25
  "greynoise",
27
26
  "onyphe",
28
27
  "otx",
@@ -38,7 +37,16 @@ module Mihari
38
37
  "vt"
39
38
  )
40
39
 
41
- FeedHttpRequestMethods = Types::String.enum("GET", "POST")
42
- FeedHttpRequestPayloadTypes = Types::String.enum("application/json", "application/x-www-form-urlencoded")
40
+ HttpRequestMethods = Types::String.enum("GET", "POST")
41
+ HttpRequestPayloadTypes = Types::String.enum("application/json", "application/x-www-form-urlencoded")
42
+
43
+ EmitterTypes = Types::String.enum(
44
+ "database",
45
+ "http",
46
+ "misp",
47
+ "slack",
48
+ "the_hive",
49
+ "webhook"
50
+ )
43
51
  end
44
52
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "4.2.0"
4
+ VERSION = "4.3.0"
5
5
  end
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="/static/favicon.ico"/><title>Mihari</title><script defer="defer" type="module" src="/static/js/chunk-vendors.15e84e22.js"></script><script defer="defer" type="module" src="/static/js/app.4818aedd.js"></script><link href="/static/css/chunk-vendors.3ed9b08e.css" rel="stylesheet"><link href="/static/css/app.43138058.css" rel="stylesheet"><script defer="defer" src="/static/js/chunk-vendors-legacy.c99e452e.js" nomodule></script><script defer="defer" src="/static/js/app-legacy.46b666f0.js" nomodule></script></head><body><noscript><strong>We're sorry but Mihari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="/static/favicon.ico"/><title>Mihari</title><script defer="defer" type="module" src="/static/js/chunk-vendors.c5525f1e.js"></script><script defer="defer" type="module" src="/static/js/app.e74e91d7.js"></script><link href="/static/css/chunk-vendors.c57bb3fd.css" rel="stylesheet"><link href="/static/css/app.0de4b715.css" rel="stylesheet"><script defer="defer" src="/static/js/chunk-vendors-legacy.41357cdf.js" nomodule></script><script defer="defer" src="/static/js/app-legacy.e451304b.js" nomodule></script></head><body><noscript><strong>We're sorry but Mihari doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>