mihari 7.1.3 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/Rakefile +8 -1
  4. data/lefthook.yml +4 -1
  5. data/lib/mihari/actor.rb +9 -0
  6. data/lib/mihari/analyzers/base.rb +7 -18
  7. data/lib/mihari/analyzers/binaryedge.rb +0 -6
  8. data/lib/mihari/analyzers/censys.rb +0 -9
  9. data/lib/mihari/analyzers/circl.rb +0 -6
  10. data/lib/mihari/analyzers/fofa.rb +0 -6
  11. data/lib/mihari/analyzers/greynoise.rb +0 -6
  12. data/lib/mihari/analyzers/hunterhow.rb +0 -6
  13. data/lib/mihari/analyzers/onyphe.rb +0 -6
  14. data/lib/mihari/analyzers/otx.rb +0 -6
  15. data/lib/mihari/analyzers/passivetotal.rb +0 -4
  16. data/lib/mihari/analyzers/pulsedive.rb +0 -6
  17. data/lib/mihari/analyzers/securitytrails.rb +0 -4
  18. data/lib/mihari/analyzers/shodan.rb +0 -6
  19. data/lib/mihari/analyzers/urlscan.rb +0 -6
  20. data/lib/mihari/analyzers/virustotal.rb +0 -4
  21. data/lib/mihari/analyzers/virustotal_intelligence.rb +7 -6
  22. data/lib/mihari/analyzers/zoomeye.rb +0 -6
  23. data/lib/mihari/commands/web.rb +1 -1
  24. data/lib/mihari/concerns/falsepositive_normalizable.rb +30 -0
  25. data/lib/mihari/concerns/falsepositive_validatable.rb +1 -17
  26. data/lib/mihari/config.rb +1 -1
  27. data/lib/mihari/database.rb +18 -1
  28. data/lib/mihari/emitters/database.rb +0 -6
  29. data/lib/mihari/emitters/misp.rb +0 -6
  30. data/lib/mihari/emitters/slack.rb +5 -21
  31. data/lib/mihari/emitters/the_hive.rb +0 -6
  32. data/lib/mihari/enrichers/whois.rb +5 -7
  33. data/lib/mihari/entities/artifact.rb +6 -2
  34. data/lib/mihari/entities/autonomous_system.rb +1 -1
  35. data/lib/mihari/entities/cpe.rb +1 -1
  36. data/lib/mihari/entities/port.rb +1 -1
  37. data/lib/mihari/entities/vulnerability.rb +10 -0
  38. data/lib/mihari/errors.rb +2 -0
  39. data/lib/mihari/models/artifact.rb +65 -30
  40. data/lib/mihari/models/vulnerability.rb +12 -0
  41. data/lib/mihari/rule.rb +18 -24
  42. data/lib/mihari/schemas/rule.rb +7 -0
  43. data/lib/mihari/services/builders.rb +22 -3
  44. data/lib/mihari/services/enrichers.rb +2 -0
  45. data/lib/mihari/services/feed.rb +2 -5
  46. data/lib/mihari/services/proxies.rb +3 -3
  47. data/lib/mihari/structs/censys.rb +2 -2
  48. data/lib/mihari/structs/greynoise.rb +1 -1
  49. data/lib/mihari/structs/onyphe.rb +1 -1
  50. data/lib/mihari/structs/shodan.rb +59 -21
  51. data/lib/mihari/version.rb +1 -1
  52. data/lib/mihari/web/endpoints/artifacts.rb +4 -2
  53. data/lib/mihari/web/endpoints/rules.rb +1 -1
  54. data/lib/mihari/web/public/assets/{index-TOeU8PE2.js → index-GWurHG1o.js} +46 -46
  55. data/lib/mihari/web/public/assets/{index-dVaNxqTC.css → index-ReF8ffd-.css} +1 -1
  56. data/lib/mihari/web/public/index.html +2 -2
  57. data/lib/mihari/web/public/redoc-static.html +17 -17
  58. data/lib/mihari.rb +3 -0
  59. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7463506c12a2476cfea5dda939e88e01150fb6539c232e102c15fca4c57ab99
4
- data.tar.gz: fdb700f461140badc1d90b3199999a97e483e57ae9c9bfed2d0157a94f35529d
3
+ metadata.gz: 6b91d99562526b653b793f71e8ef5575f113d26859ca86b91f1980d1c44e898c
4
+ data.tar.gz: 07c302ce446b0f0986bfc82dd575ebde203f4b25b73a1aee72a2ce37a08b9b87
5
5
  SHA512:
6
- metadata.gz: 18d9dd1f63056cd2731caa823904fd9df80ca21e63a540eab89d06e7951fa10cde498344049f3ba7de8740da2e8cfb9da9fe660f77c3a1abca5e308e36a6973c
7
- data.tar.gz: 5a8ce97455cb0e68538af1ca2c8af5b89ea96f817d4f11cf6c00851035a20288f9bc2d2de16b58b52f3e72e988c77a2e4d8744a3fbebfd27c2cb6895d4c4dccb
6
+ metadata.gz: 67d607a09ab2992b6721358b9e96e0c049a355221b2e97358440d70f96ef4933ac86b337bcc12bdc4b2aafccf4e5d6cf8cd68f4b2bbe567523bfba22b7fbd88c
7
+ data.tar.gz: 6f73de19824d31e21bae4ee7ce456c1b15fa0a875d1f07025c33ac0356ef257ac2eb819ffd8b685bf8914a472da04f691e5ae02a361397da9a83253d5345b108
data/Dockerfile CHANGED
@@ -2,7 +2,7 @@ FROM ruby:3.2.2-alpine3.19
2
2
 
3
3
  ARG MIHARI_VERSION=0.0.0
4
4
 
5
- RUN apk --no-cache add build-base ruby-dev libpq-dev && \
5
+ RUN apk --no-cache add build-base ruby-dev libpq-dev whois && \
6
6
  echo 'gem: --no-document' >> /usr/local/etc/gemrc && \
7
7
  gem install pg && \
8
8
  gem install mihari -v ${MIHARI_VERSION} && \
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "time"
4
+
3
5
  require "rspec/core/rake_task"
4
6
  require "standard/rake"
5
7
 
@@ -46,13 +48,18 @@ namespace :build do
46
48
  desc "Build Swagger doc"
47
49
  task :swagger, [:path] do |_t, args|
48
50
  args.with_defaults(path: "./frontend/swagger.yaml")
51
+
52
+ started_at = Time.now
49
53
  build_swagger_doc args.path
54
+ elapsed = (Time.now - started_at).floor(2)
55
+
56
+ puts "Swagger doc is built in #{elapsed}s"
50
57
  end
51
58
  end
52
59
 
53
60
  task :build do
54
- # Build Swagger dos
55
61
  Rake::Task["build:swagger"].invoke
62
+
56
63
  # Build ReDocs docs & frontend assets
57
64
  sh "cd frontend && npm install && npm run docs && npm run build-only"
58
65
  # Copy built assets into ./lib/web/public/
data/lefthook.yml CHANGED
@@ -1,5 +1,4 @@
1
1
  pre-commit:
2
- parallel: true
3
2
  commands:
4
3
  standard:
5
4
  glob: "*.rb"
@@ -15,6 +14,10 @@ pre-commit:
15
14
  glob: "*.{js,ts,vue}"
16
15
  run: npx prettier --write {staged_files}
17
16
  stage_fixed: true
17
+ type-check:
18
+ root: "frontend/"
19
+ glob: "*.{js,ts,vue}"
20
+ run: npm run type-check
18
21
  actionlint:
19
22
  glob: ".github/workflows/*.yaml"
20
23
  run: actionlint
data/lib/mihari/actor.rb CHANGED
@@ -93,6 +93,15 @@ module Mihari
93
93
  ([key] + [key_aliases]).flatten.compact.map(&:downcase)
94
94
  end
95
95
 
96
+ def configuration_keys
97
+ # Automatically generate configuration keys based on key
98
+ # For example,
99
+ # - Shodan analyzer's key is "shodan"
100
+ # - Mihari.config has "shodan_api_key"
101
+ # - Select "shodan_api_key" by using "#{key}_" prefix
102
+ Mihari.config.keys.select { |config_key| config_key.start_with?("#{key}_") }
103
+ end
104
+
96
105
  def type
97
106
  return "analyzer" if ancestors.include?(Mihari::Analyzers::Base)
98
107
  return "emitter" if ancestors.include?(Mihari::Emitters::Base)
@@ -63,12 +63,10 @@ module Mihari
63
63
  artifacts.compact.sort.map do |artifact|
64
64
  # No need to set data_type manually
65
65
  # It is set automatically in #initialize
66
- artifact = artifact.is_a?(Models::Artifact) ? artifact : Models::Artifact.new(data: artifact)
67
-
68
- artifact.source = self.class.key
69
- artifact.query = query
70
-
71
- artifact
66
+ (artifact.is_a?(Models::Artifact) ? artifact : Models::Artifact.new(data: artifact)).tap do |normalized|
67
+ normalized.source = self.class.key
68
+ normalized.query = query
69
+ end
72
70
  end.select(&:valid?).uniq(&:data)
73
71
  end
74
72
 
@@ -118,18 +116,9 @@ module Mihari
118
116
  #
119
117
  # @return [Mihari::Analyzers::Base]
120
118
  #
121
- def from_query(params)
122
- copied = params.deep_dup
123
-
124
- # convert params into arguments for initialization
125
- query = copied[:query]
126
-
127
- # delete analyzer and query
128
- %i[analyzer query].each { |key| copied.delete key }
129
-
130
- copied[:options] = copied[:options] || nil
131
-
132
- new(query, **copied)
119
+ def from_params(params)
120
+ query = params.delete(:query)
121
+ new(query, **params)
133
122
  end
134
123
 
135
124
  def inherited(child)
@@ -24,12 +24,6 @@ module Mihari
24
24
  client.search_with_pagination(query, pagination_limit: pagination_limit).map(&:artifacts).flatten
25
25
  end
26
26
 
27
- class << self
28
- def configuration_keys
29
- %w[binaryedge_api_key]
30
- end
31
- end
32
-
33
27
  private
34
28
 
35
29
  #
@@ -41,15 +41,6 @@ module Mihari
41
41
  configuration_keys? || (id? && secret?)
42
42
  end
43
43
 
44
- class << self
45
- #
46
- # @return [Array<String>]
47
- #
48
- def configuration_keys
49
- %w[censys_id censys_secret]
50
- end
51
- end
52
-
53
44
  private
54
45
 
55
46
  #
@@ -47,12 +47,6 @@ module Mihari
47
47
  configuration_keys? || (username? && password?)
48
48
  end
49
49
 
50
- class << self
51
- def configuration_keys
52
- %w[circl_passive_password circl_passive_username]
53
- end
54
- end
55
-
56
50
  private
57
51
 
58
52
  def client
@@ -35,12 +35,6 @@ module Mihari
35
35
  api_key? && email?
36
36
  end
37
37
 
38
- class << self
39
- def configuration_keys
40
- %w[fofa_api_key fofa_email]
41
- end
42
- end
43
-
44
38
  private
45
39
 
46
40
  def email?
@@ -27,12 +27,6 @@ module Mihari
27
27
  ).map(&:artifacts).flatten
28
28
  end
29
29
 
30
- class << self
31
- def configuration_keys
32
- %w[greynoise_api_key]
33
- end
34
- end
35
-
36
30
  private
37
31
 
38
32
  def client
@@ -44,12 +44,6 @@ module Mihari
44
44
  end.flatten
45
45
  end
46
46
 
47
- class << self
48
- def configuration_keys
49
- %w[hunterhow_api_key]
50
- end
51
- end
52
-
53
47
  private
54
48
 
55
49
  def client
@@ -29,12 +29,6 @@ module Mihari
29
29
  ).map(&:artifacts).flatten
30
30
  end
31
31
 
32
- class << self
33
- def configuration_keys
34
- %w[onyphe_api_key]
35
- end
36
- end
37
-
38
32
  private
39
33
 
40
34
  def client
@@ -38,12 +38,6 @@ module Mihari
38
38
  end
39
39
  end
40
40
 
41
- class << self
42
- def configuration_keys
43
- %w[otx_api_key]
44
- end
45
- end
46
-
47
41
  private
48
42
 
49
43
  def client
@@ -50,10 +50,6 @@ module Mihari
50
50
  end
51
51
 
52
52
  class << self
53
- def configuration_keys
54
- %w[passivetotal_username passivetotal_api_key]
55
- end
56
-
57
53
  #
58
54
  # @return [Array<String>, nil]
59
55
  #
@@ -43,12 +43,6 @@ module Mihari
43
43
  end
44
44
  end
45
45
 
46
- class << self
47
- def configuration_keys
48
- %w[pulsedive_api_key]
49
- end
50
- end
51
-
52
46
  private
53
47
 
54
48
  def client
@@ -44,10 +44,6 @@ module Mihari
44
44
  end
45
45
 
46
46
  class << self
47
- def configuration_keys
48
- %w[securitytrails_api_key]
49
- end
50
-
51
47
  #
52
48
  # @return [Array<String>, nil]
53
49
  #
@@ -27,12 +27,6 @@ module Mihari
27
27
  ).map(&:artifacts).flatten.uniq(&:data)
28
28
  end
29
29
 
30
- class << self
31
- def configuration_keys
32
- %w[shodan_api_key]
33
- end
34
- end
35
-
36
30
  private
37
31
 
38
32
  #
@@ -37,12 +37,6 @@ module Mihari
37
37
  artifacts.select { |artifact| allowed_data_types.include? artifact.data_type }
38
38
  end
39
39
 
40
- class << self
41
- def configuration_keys
42
- %w[urlscan_api_key]
43
- end
44
- end
45
-
46
40
  private
47
41
 
48
42
  def client
@@ -39,10 +39,6 @@ module Mihari
39
39
  end
40
40
 
41
41
  class << self
42
- def configuration_keys
43
- %w[virustotal_api_key]
44
- end
45
-
46
42
  #
47
43
  # @return [Array<String>, nil]
48
44
  #
@@ -24,12 +24,6 @@ module Mihari
24
24
  client.intel_search_with_pagination(query, pagination_limit: pagination_limit).map(&:artifacts).flatten
25
25
  end
26
26
 
27
- class << self
28
- def configuration_keys
29
- %w[virustotal_api_key]
30
- end
31
- end
32
-
33
27
  class << self
34
28
  #
35
29
  # @return [String]
@@ -44,6 +38,13 @@ module Mihari
44
38
  def key_aliases
45
39
  ["vt_intel"]
46
40
  end
41
+
42
+ #
43
+ # @return [Array<String>]
44
+ #
45
+ def configuration_keys
46
+ %w[virustotal_api_key]
47
+ end
47
48
  end
48
49
 
49
50
  private
@@ -40,12 +40,6 @@ module Mihari
40
40
  end
41
41
  end
42
42
 
43
- class << self
44
- def configuration_keys
45
- %w[zoomeye_api_key]
46
- end
47
- end
48
-
49
43
  private
50
44
 
51
45
  #
@@ -12,7 +12,7 @@ module Mihari
12
12
  desc "web", "Start the web app"
13
13
  method_option :port, type: :numeric, default: 9292, desc: "Port to listen on"
14
14
  method_option :host, type: :string, default: "localhost", desc: "Hostname to listen on"
15
- method_option :threads, type: :string, default: "0:5", desc: "min:max threads to use"
15
+ method_option :threads, type: :string, default: "0:3", desc: "min:max threads to use"
16
16
  method_option :verbose, type: :boolean, default: false, desc: "Don't report each request"
17
17
  method_option :worker_timeout, type: :numeric, default: 60, desc: "Worker timeout value (in seconds)"
18
18
  method_option :open, type: :boolean, default: true, desc: "Whether to open the app in browser or not"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Concerns
5
+ #
6
+ # False positive normalizable concern
7
+ #
8
+ module FalsePositiveNormalizable
9
+ extend ActiveSupport::Concern
10
+
11
+ prepend MemoWise
12
+
13
+ #
14
+ # Normalize a falsepositive value
15
+ #
16
+ # @param [String] value
17
+ #
18
+ # @return [String, Regexp]
19
+ #
20
+ def normalize_falsepositive(value)
21
+ return value if !value.start_with?("/") || !value.end_with?("/")
22
+
23
+ # if a value is surrounded by slashes, take it as a regexp
24
+ value_without_slashes = value[1..-2]
25
+ Regexp.compile value_without_slashes.to_s
26
+ end
27
+ memo_wise :normalize_falsepositive
28
+ end
29
+ end
30
+ end
@@ -8,23 +8,7 @@ module Mihari
8
8
  module FalsePositiveValidatable
9
9
  extend ActiveSupport::Concern
10
10
 
11
- prepend MemoWise
12
-
13
- #
14
- # Normalize a falsepositive value
15
- #
16
- # @param [String] value
17
- #
18
- # @return [String, Regexp]
19
- #
20
- def normalize_falsepositive(value)
21
- return value if !value.start_with?("/") || !value.end_with?("/")
22
-
23
- # if a value is surrounded by slashes, take it as a regexp
24
- value_without_slashes = value[1..-2]
25
- Regexp.compile value_without_slashes.to_s
26
- end
27
- memo_wise :normalize_falsepositive
11
+ include FalsePositiveNormalizable
28
12
 
29
13
  #
30
14
  # Check whether a value is valid format as a disallowed data value
data/lib/mihari/config.rb CHANGED
@@ -170,7 +170,7 @@ module Mihari
170
170
  # @return [Array<String>]
171
171
  #
172
172
  def keys
173
- to_h.keys.map(&:to_s).map(&:upcase)
173
+ @keys ||= to_h.keys.map(&:to_s).map(&:downcase)
174
174
  end
175
175
  end
176
176
  end
@@ -104,11 +104,28 @@ class V7Schema < ActiveRecord::Migration[7.1]
104
104
  end
105
105
  end
106
106
 
107
+ class V72Schema < ActiveRecord::Migration[7.1]
108
+ def change
109
+ create_table :vulnerabilities, if_not_exists: true do |t|
110
+ t.string :name, null: false
111
+ t.datetime :created_at
112
+
113
+ t.belongs_to :artifact, foreign_key: true, null: false
114
+ end
115
+
116
+ rename_column :cpes, :cpe, :name if ActiveRecord::Base.connection.column_exists?(:cpes, :cpe)
117
+ rename_column :autonomous_systems, :asn, :number if ActiveRecord::Base.connection.column_exists?(
118
+ :autonomous_systems, :asn
119
+ )
120
+ rename_column :ports, :port, :number if ActiveRecord::Base.connection.column_exists?(:ports, :port)
121
+ end
122
+ end
123
+
107
124
  #
108
125
  # @return [Array<ActiveRecord::Migration>] schemas
109
126
  #
110
127
  def schemas
111
- [V7Schema]
128
+ [V7Schema, V72Schema]
112
129
  end
113
130
 
114
131
  module Mihari
@@ -24,12 +24,6 @@ module Mihari
24
24
  def target
25
25
  Mihari.config.database_url.host || Mihari.config.database_url.to_s
26
26
  end
27
-
28
- class << self
29
- def configuration_keys
30
- %w[database_url]
31
- end
32
- end
33
27
  end
34
28
  end
35
29
  end
@@ -63,12 +63,6 @@ module Mihari
63
63
  URI(url).host || "N/A"
64
64
  end
65
65
 
66
- class << self
67
- def configuration_keys
68
- %w[misp_url misp_api_key]
69
- end
70
- end
71
-
72
66
  private
73
67
 
74
68
  def client
@@ -176,21 +176,11 @@ module Mihari
176
176
  # @return [::Slack::Notifier]
177
177
  #
178
178
  def notifier
179
- @notifier ||= [].tap do |out|
180
- out << if timeout.nil?
181
- ::Slack::Notifier.new(
182
- webhook_url,
183
- channel: channel, username: username
184
- )
185
- else
186
- ::Slack::Notifier.new(
187
- webhook_url,
188
- channel: channel,
189
- username: username,
190
- http_options: { timeout: timeout }
191
- )
192
- end
193
- end.first
179
+ @notifier ||= lambda do
180
+ return ::Slack::Notifier.new(webhook_url, channel: channel, username: username) if timeout.nil?
181
+
182
+ ::Slack::Notifier.new(webhook_url, channel: channel, username: username, http_options: { timeout: timeout })
183
+ end.call
194
184
  end
195
185
 
196
186
  #
@@ -227,12 +217,6 @@ module Mihari
227
217
 
228
218
  notifier.post(text: text, attachments: attachments, mrkdwn: true)
229
219
  end
230
-
231
- class << self
232
- def configuration_keys
233
- %w[slack_webhook_url slack_channel]
234
- end
235
- end
236
220
  end
237
221
  end
238
222
  end
@@ -53,12 +53,6 @@ module Mihari
53
53
  client.alert payload
54
54
  end
55
55
 
56
- class << self
57
- def configuration_keys
58
- %w[thehive_url thehive_api_key]
59
- end
60
- end
61
-
62
56
  private
63
57
 
64
58
  def client
@@ -53,13 +53,11 @@ module Mihari
53
53
  # @return [::Whois::Client]
54
54
  #
55
55
  def whois
56
- @whois ||= [].tap do |out|
57
- out << if timeout.nil?
58
- ::Whois::Client.new
59
- else
60
- ::Whois::Client.new(timeout: timeout)
61
- end
62
- end.last
56
+ @whois ||= lambda do
57
+ return ::Whois::Client.new if timeout.nil?
58
+
59
+ ::Whois::Client.new(timeout: timeout)
60
+ end.call
63
61
  end
64
62
 
65
63
  #
@@ -8,12 +8,12 @@ module Mihari
8
8
  expose :data_type, documentation: { type: String, required: true }, as: :dataType
9
9
  expose :source, documentation: { type: String, required: true }
10
10
  expose :query, documentation: { type: String, required: false }
11
- expose :metadata, documentation: { type: Hash }
12
11
  expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
12
+ expose :tags, using: Entities::Tag, documentation: { type: Entities::Tag, is_array: true, required: true }
13
13
  end
14
14
 
15
15
  class Artifact < BaseArtifact
16
- expose :tags, using: Entities::Tag, documentation: { type: Entities::Tag, is_array: true, required: true }
16
+ expose :metadata, documentation: { type: Hash }
17
17
  expose :autonomous_system, using: Entities::AutonomousSystem,
18
18
  documentation: { type: Entities::AutonomousSystem, required: false }, as: :autonomousSystem
19
19
  expose :geolocation, using: Entities::Geolocation, documentation: { type: Entities::Geolocation, required: false }
@@ -36,6 +36,10 @@ module Mihari
36
36
  as: :ports do |status, _options|
37
37
  status.ports.empty? ? nil : status.ports
38
38
  end
39
+ expose :vulnerabilities, using: Vulnerability, documentation: { type: Vulnerability, is_array: true, required: false },
40
+ as: :vulnerabilities do |status, _options|
41
+ status.vulnerabilities.empty? ? nil : status.vulnerabilities
42
+ end
39
43
  end
40
44
 
41
45
  class ArtifactsWithPagination < Pagination
@@ -3,7 +3,7 @@
3
3
  module Mihari
4
4
  module Entities
5
5
  class AutonomousSystem < Grape::Entity
6
- expose :asn, documentation: { type: Integer, required: true }
6
+ expose :number, documentation: { type: Integer, required: true }
7
7
  end
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@
3
3
  module Mihari
4
4
  module Entities
5
5
  class CPE < Grape::Entity
6
- expose :cpe, documentation: { type: String, required: true }
6
+ expose :name, documentation: { type: String, required: true }
7
7
  expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
8
8
  end
9
9
  end
@@ -3,7 +3,7 @@
3
3
  module Mihari
4
4
  module Entities
5
5
  class Port < Grape::Entity
6
- expose :port, documentation: { type: Integer, required: true }
6
+ expose :number, documentation: { type: Integer, required: true }
7
7
  expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
8
8
  end
9
9
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Entities
5
+ class Vulnerability < Grape::Entity
6
+ expose :name, documentation: { type: String, required: true }
7
+ expose :created_at, documentation: { type: DateTime, required: true }, as: :createdAt
8
+ end
9
+ end
10
+ end
data/lib/mihari/errors.rb CHANGED
@@ -7,6 +7,8 @@ module Mihari
7
7
 
8
8
  class ValueError < Error; end
9
9
 
10
+ class UnenrichableError < Error; end
11
+
10
12
  class ConfigurationError < Error
11
13
  # @return [Array<String>, nil]
12
14
  attr_reader :detail