mihari 5.1.1 → 5.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -3
  3. data/.rubocop.yml +6 -0
  4. data/README.md +0 -1
  5. data/lib/mihari/analyzers/base.rb +32 -27
  6. data/lib/mihari/analyzers/binaryedge.rb +8 -2
  7. data/lib/mihari/analyzers/censys.rb +7 -49
  8. data/lib/mihari/analyzers/circl.rb +5 -2
  9. data/lib/mihari/analyzers/crtsh.rb +6 -0
  10. data/lib/mihari/analyzers/dnstwister.rb +4 -2
  11. data/lib/mihari/analyzers/feed.rb +21 -0
  12. data/lib/mihari/analyzers/greynoise.rb +5 -28
  13. data/lib/mihari/analyzers/onyphe.rb +8 -33
  14. data/lib/mihari/analyzers/otx.rb +3 -0
  15. data/lib/mihari/analyzers/passivetotal.rb +3 -0
  16. data/lib/mihari/analyzers/pulsedive.rb +3 -0
  17. data/lib/mihari/analyzers/rule.rb +0 -1
  18. data/lib/mihari/analyzers/securitytrails.rb +8 -10
  19. data/lib/mihari/analyzers/shodan.rb +13 -81
  20. data/lib/mihari/analyzers/urlscan.rb +9 -0
  21. data/lib/mihari/analyzers/virustotal.rb +4 -0
  22. data/lib/mihari/analyzers/virustotal_intelligence.rb +8 -2
  23. data/lib/mihari/analyzers/zoomeye.rb +9 -0
  24. data/lib/mihari/clients/binaryedge.rb +5 -0
  25. data/lib/mihari/clients/censys.rb +4 -4
  26. data/lib/mihari/clients/circl.rb +3 -3
  27. data/lib/mihari/clients/greynoise.rb +6 -1
  28. data/lib/mihari/clients/misp.rb +6 -1
  29. data/lib/mihari/clients/onyphe.rb +13 -1
  30. data/lib/mihari/clients/otx.rb +20 -0
  31. data/lib/mihari/clients/passivetotal.rb +6 -2
  32. data/lib/mihari/clients/publsedive.rb +18 -1
  33. data/lib/mihari/clients/securitytrails.rb +94 -0
  34. data/lib/mihari/clients/shodan.rb +14 -3
  35. data/lib/mihari/clients/the_hive.rb +6 -1
  36. data/lib/mihari/clients/urlscan.rb +3 -1
  37. data/lib/mihari/clients/virustotal.rb +9 -3
  38. data/lib/mihari/clients/zoomeye.rb +7 -1
  39. data/lib/mihari/commands/database.rb +1 -6
  40. data/lib/mihari/commands/searcher.rb +1 -2
  41. data/lib/mihari/database.rb +9 -0
  42. data/lib/mihari/structs/censys.rb +62 -0
  43. data/lib/mihari/structs/greynoise.rb +43 -0
  44. data/lib/mihari/structs/onyphe.rb +45 -0
  45. data/lib/mihari/structs/shodan.rb +83 -0
  46. data/lib/mihari/version.rb +1 -1
  47. data/lib/mihari/web/middleware/connection_adapter.rb +1 -3
  48. data/lib/mihari/web/public/assets/{index-63900d73.js → index-7d0fb8c4.js} +2 -2
  49. data/lib/mihari/web/public/index.html +1 -1
  50. data/lib/mihari/web/public/redoc-static.html +2 -2
  51. data/lib/mihari.rb +1 -3
  52. data/mihari.gemspec +2 -3
  53. metadata +9 -25
  54. data/lib/mihari/analyzers/dnpedia.rb +0 -33
  55. data/lib/mihari/clients/dnpedia.rb +0 -64
  56. data/lib/mihari/mixins/database.rb +0 -16
@@ -10,6 +10,12 @@ module Mihari
10
10
  # @return [String, nil]
11
11
  attr_reader :api_key
12
12
 
13
+ # @return [Integer]
14
+ attr_reader :interval
15
+
16
+ # @return [String]
17
+ attr_reader :query
18
+
13
19
  def initialize(*args, **kwargs)
14
20
  super(*args, **kwargs)
15
21
 
@@ -18,13 +24,9 @@ module Mihari
18
24
 
19
25
  def artifacts
20
26
  results = search
21
- return [] unless results || results.empty?
22
-
23
- results = results.map { |result| Structs::Shodan::Result.from_dynamic!(result) }
24
- matches = results.map { |result| result.matches || [] }.flatten
27
+ return [] if results.empty?
25
28
 
26
- uniq_matches = matches.uniq(&:ip_str)
27
- uniq_matches.map { |match| build_artifact(match, matches) }
29
+ results.map { |result| result.to_artifacts(source) }.flatten.uniq(&:data)
28
30
  end
29
31
 
30
32
  private
@@ -42,29 +44,25 @@ module Mihari
42
44
  #
43
45
  # Search with pagination
44
46
  #
45
- # @param [String] query
46
47
  # @param [Integer] page
47
48
  #
48
- # @return [Hash]
49
+ # @return [Structs::Shodan::Result]
49
50
  #
50
- def search_with_page(query, page: 1)
51
+ def search_with_page(page: 1)
51
52
  client.search(query, page: page)
52
53
  end
53
54
 
54
55
  #
55
56
  # Search
56
57
  #
57
- # @return [Array<Hash>]
58
+ # @return [Array<Structs::Shodan::Result>]
58
59
  #
59
60
  def search
60
61
  responses = []
61
62
  (1..Float::INFINITY).each do |page|
62
- res = search_with_page(query, page: page)
63
-
64
- break unless res
65
-
63
+ res = search_with_page(page: page)
66
64
  responses << res
67
- break if res["total"].to_i <= page * PAGE_SIZE
65
+ break if res.total <= page * PAGE_SIZE
68
66
 
69
67
  # sleep #{interval} seconds to avoid the rate limitation (if it is set)
70
68
  sleep interval
@@ -76,42 +74,6 @@ module Mihari
76
74
  responses
77
75
  end
78
76
 
79
- #
80
- # Collect metadata from matches
81
- #
82
- # @param [Array<Structs::Shodan::Match>] matches
83
- # @param [String] ip
84
- #
85
- # @return [Array<Hash>]
86
- #
87
- def collect_metadata_by_ip(matches, ip)
88
- matches.select { |match| match.ip_str == ip }.map(&:metadata)
89
- end
90
-
91
- #
92
- # Collect ports from matches
93
- #
94
- # @param [Array<Structs::Shodan::Match>] matches
95
- # @param [String] ip
96
- #
97
- # @return [Array<String>]
98
- #
99
- def collect_ports_by_ip(matches, ip)
100
- matches.select { |match| match.ip_str == ip }.map(&:port)
101
- end
102
-
103
- #
104
- # Collect hostnames from matches
105
- #
106
- # @param [Array<Structs::Shodan::Match>] matches
107
- # @param [String] ip
108
- #
109
- # @return [Array<String>]
110
- #
111
- def collect_hostnames_by_ip(matches, ip)
112
- matches.select { |match| match.ip_str == ip }.map(&:hostnames).flatten.uniq
113
- end
114
-
115
77
  #
116
78
  # Build an artifact from a Shodan search API response
117
79
  #
@@ -121,36 +83,6 @@ module Mihari
121
83
  # @return [Artifact]
122
84
  #
123
85
  def build_artifact(match, matches)
124
- as = nil
125
- as = AutonomousSystem.new(asn: normalize_asn(match.asn)) unless match.asn.nil?
126
-
127
- geolocation = nil
128
- if !match.location.country_name.nil? && !match.location.country_code.nil?
129
- geolocation = Geolocation.new(
130
- country: match.location.country_name,
131
- country_code: match.location.country_code
132
- )
133
- end
134
-
135
- metadata = collect_metadata_by_ip(matches, match.ip_str)
136
-
137
- ports = collect_ports_by_ip(matches, match.ip_str).map do |port|
138
- Port.new(port: port)
139
- end
140
-
141
- reverse_dns_names = collect_hostnames_by_ip(matches, match.ip_str).map do |name|
142
- ReverseDnsName.new(name: name)
143
- end
144
-
145
- Artifact.new(
146
- data: match.ip_str,
147
- source: source,
148
- metadata: metadata,
149
- autonomous_system: as,
150
- geolocation: geolocation,
151
- ports: ports,
152
- reverse_dns_names: reverse_dns_names
153
- )
154
86
  end
155
87
  end
156
88
  end
@@ -15,6 +15,15 @@ module Mihari
15
15
  # @return [String, nil]
16
16
  attr_reader :api_key
17
17
 
18
+ # @return [String]
19
+ attr_reader :query
20
+
21
+ # @return [Integer]
22
+ attr_reader :interval
23
+
24
+ # @return [String]
25
+ attr_reader :allowed_data_types
26
+
18
27
  def initialize(*args, **kwargs)
19
28
  super
20
29
 
@@ -7,11 +7,15 @@ module Mihari
7
7
 
8
8
  param :query
9
9
 
10
+ # @return [String]
10
11
  attr_reader :type
11
12
 
12
13
  # @return [String, nil]
13
14
  attr_reader :api_key
14
15
 
16
+ # @return [String]
17
+ attr_reader :query
18
+
15
19
  def initialize(*args, **kwargs)
16
20
  super(*args, **kwargs)
17
21
 
@@ -10,6 +10,12 @@ module Mihari
10
10
  # @return [String, nil]
11
11
  attr_reader :api_key
12
12
 
13
+ # @return [String]
14
+ attr_reader :query
15
+
16
+ # @return [Integer]
17
+ attr_reader :interval
18
+
13
19
  def initialize(*args, **kwargs)
14
20
  super
15
21
 
@@ -19,7 +25,7 @@ module Mihari
19
25
  end
20
26
 
21
27
  def artifacts
22
- responses = search_witgh_cursor
28
+ responses = search_with_cursor
23
29
  responses.map do |response|
24
30
  response.data.map do |datum|
25
31
  Artifact.new(data: datum.value, source: source, metadata: datum.metadata)
@@ -47,7 +53,7 @@ module Mihari
47
53
  #
48
54
  # @return [Array<Structs::VirusTotalIntelligence::Response>]
49
55
  #
50
- def search_witgh_cursor
56
+ def search_with_cursor
51
57
  cursor = nil
52
58
  responses = []
53
59
 
@@ -12,6 +12,15 @@ module Mihari
12
12
  # @return [String, nil]
13
13
  attr_reader :api_key
14
14
 
15
+ # @return [String]
16
+ attr_reader :query
17
+
18
+ # @return [String]
19
+ attr_reader :type
20
+
21
+ # @return [Integer]
22
+ attr_reader :interval
23
+
15
24
  def initialize(*args, **kwargs)
16
25
  super(*args, **kwargs)
17
26
 
@@ -3,6 +3,11 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class BinaryEdge < Base
6
+ #
7
+ # @param [String] base_url
8
+ # @param [String, nil] api_key
9
+ # @param [Hash] headers
10
+ #
6
11
  def initialize(base_url = "https://api.binaryedge.io/v2", api_key:, headers: {})
7
12
  raise(ArgumentError, "'api_key' argument is required") unless api_key
8
13
 
@@ -7,8 +7,8 @@ module Mihari
7
7
  class Censys < Base
8
8
  #
9
9
  # @param [String] base_url
10
- # @param [String] id
11
- # @param [String] secret
10
+ # @param [String, nil] id
11
+ # @param [String, nil] secret
12
12
  # @param [Hash] headers
13
13
  #
14
14
  def initialize(base_url = "https://search.censys.io", id:, secret:, headers: {})
@@ -30,12 +30,12 @@ module Mihari
30
30
  # @params [Integer, nil] per_page the number of results to be returned for each page.
31
31
  # @params [Integer, nil] cursor the cursor of the desired result set.
32
32
  #
33
- # @return [Hash]
33
+ # @return [Structs::Censys::Response]
34
34
  #
35
35
  def search(query, per_page: nil, cursor: nil)
36
36
  params = { q: query, per_page: per_page, cursor: cursor }.compact
37
37
  res = get("/api/v2/hosts/search", params: params)
38
- JSON.parse(res.body.to_s)
38
+ Structs::Censys::Response.from_dynamic! JSON.parse(res.body.to_s)
39
39
  end
40
40
  end
41
41
  end
@@ -7,8 +7,8 @@ module Mihari
7
7
  class CIRCL < Base
8
8
  #
9
9
  # @param [String] base_url
10
- # @param [String] username
11
- # @param [String] password
10
+ # @param [String, nil] username
11
+ # @param [String, nil] password
12
12
  # @param [Hash] headers
13
13
  #
14
14
  def initialize(base_url = "https://www.circl.lu", username:, password:, headers: {})
@@ -43,7 +43,7 @@ module Mihari
43
43
  #
44
44
  #
45
45
  # @param [String] path
46
- # @param [Array<Hash>] params
46
+ # @param [Hash] params
47
47
  #
48
48
  def _get(path, params: {})
49
49
  res = get(path, params: params)
@@ -3,6 +3,11 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class GreyNoise < Base
6
+ #
7
+ # @param [String] base_url
8
+ # @param [String, nil] api_key
9
+ # @param [Hash] headers
10
+ #
6
11
  def initialize(base_url = "https://api.greynoise.io", api_key:, headers: {})
7
12
  raise(ArgumentError, "'api_key' argument is required") unless api_key
8
13
 
@@ -22,7 +27,7 @@ module Mihari
22
27
  def gnql_search(query, size: nil, scroll: nil)
23
28
  params = { query: query, size: size, scroll: scroll }.compact
24
29
  res = get("/v2/experimental/gnql", params: params)
25
- JSON.parse res.body.to_s
30
+ Structs::GreyNoise::Response.from_dynamic! JSON.parse(res.body.to_s)
26
31
  end
27
32
  end
28
33
  end
@@ -5,7 +5,7 @@ module Mihari
5
5
  class MISP < Base
6
6
  #
7
7
  # @param [String] base_url
8
- # @param [String] api_key
8
+ # @param [String, nil] api_key
9
9
  # @param [Hash] headers
10
10
  #
11
11
  def initialize(base_url, api_key:, headers: {})
@@ -15,6 +15,11 @@ module Mihari
15
15
  super(base_url, headers: headers)
16
16
  end
17
17
 
18
+ #
19
+ # @param [Hash] payload
20
+ #
21
+ # @return [Hash]
22
+ #
18
23
  def create_event(payload)
19
24
  res = post("/events/add", json: payload)
20
25
  JSON.parse(res.body.to_s)
@@ -3,8 +3,14 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class Onyphe < Base
6
+ # @return [String]
6
7
  attr_reader :api_key
7
8
 
9
+ #
10
+ # @param [String] base_url
11
+ # @param [String, nil] api_key
12
+ # @param [Hash] headers
13
+ #
8
14
  def initialize(base_url = "https://www.onyphe.io", api_key:, headers: {})
9
15
  raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
10
16
 
@@ -13,10 +19,16 @@ module Mihari
13
19
  @api_key = api_key
14
20
  end
15
21
 
22
+ #
23
+ # @param [String] query
24
+ # @param [Integer] page
25
+ #
26
+ # @return [Hash]
27
+ #
16
28
  def datascan(query, page: 1)
17
29
  params = { page: page, apikey: api_key }
18
30
  res = get("/api/v2/simple/datascan/#{query}", params: params)
19
- JSON.parse(res.body.to_s)
31
+ Structs::Onyphe::Response.from_dynamic! JSON.parse(res.body.to_s)
20
32
  end
21
33
  end
22
34
  end
@@ -3,6 +3,11 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class OTX < Base
6
+ #
7
+ # @param [String] base_url
8
+ # @param [String, nil] api_key
9
+ # @param [Hash] headers
10
+ #
6
11
  def initialize(base_url = "https://otx.alienvault.com", api_key:, headers: {})
7
12
  raise(ArgumentError, "'api_key' argument is required") unless api_key
8
13
 
@@ -10,16 +15,31 @@ module Mihari
10
15
  super(base_url, headers: headers)
11
16
  end
12
17
 
18
+ #
19
+ # @param [String] ip
20
+ #
21
+ # @return [Hash]
22
+ #
13
23
  def query_by_ip(ip)
14
24
  _get "/api/v1/indicators/IPv4/#{ip}/passive_dns"
15
25
  end
16
26
 
27
+ #
28
+ # @param [String] domain
29
+ #
30
+ # @return [Hash]
31
+ #
17
32
  def query_by_domain(domain)
18
33
  _get "/api/v1/indicators/domain/#{domain}/passive_dns"
19
34
  end
20
35
 
21
36
  private
22
37
 
38
+ #
39
+ # @param [String] path
40
+ #
41
+ # @return [Hash]
42
+ #
23
43
  def _get(path)
24
44
  res = get(path)
25
45
  JSON.parse(res.body.to_s)
@@ -7,8 +7,8 @@ module Mihari
7
7
  class PassiveTotal < Base
8
8
  #
9
9
  # @param [String] base_url
10
- # @param [String] username
11
- # @param [String] api_key
10
+ # @param [String, nil] username
11
+ # @param [String, nil] api_key
12
12
  # @param [Hash] headers
13
13
  #
14
14
  def initialize(base_url = "https://api.passivetotal.org", username:, api_key:, headers: {})
@@ -31,6 +31,8 @@ module Mihari
31
31
  #
32
32
  # @param [String] query
33
33
  #
34
+ # @return [Hash]
35
+ #
34
36
  def passive_dns_search(query)
35
37
  params = { query: query }
36
38
  _get("/v2/dns/passive/unique", params: params)
@@ -56,6 +58,8 @@ module Mihari
56
58
  # @param [String] path
57
59
  # @param [Hash] params
58
60
  #
61
+ # @return [Hash]
62
+ #
59
63
  def _get(path, params: {})
60
64
  res = get(path, params: params)
61
65
  JSON.parse(res.body.to_s)
@@ -3,8 +3,14 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class PulseDive < Base
6
+ # @return [String]
6
7
  attr_reader :api_key
7
8
 
9
+ #
10
+ # @param [String] base_url
11
+ # @param [String, nil] api_key
12
+ # @param [Hash] headers
13
+ #
8
14
  def initialize(base_url = "https://pulsedive.com", api_key:, headers: {})
9
15
  super(base_url, headers: headers)
10
16
 
@@ -13,21 +19,32 @@ module Mihari
13
19
  raise(ArgumentError, "'api_key' argument is required") unless api_key
14
20
  end
15
21
 
22
+ #
23
+ # @param [String] indicator_id
24
+ #
25
+ # @return [Hash]
26
+ #
16
27
  def get_indicator(ip_or_domain)
17
28
  _get "/api/info.php", params: { indicator: ip_or_domain }
18
29
  end
19
30
 
31
+ #
32
+ # @param [String] indicator_id
33
+ #
34
+ # @return [Hash]
35
+ #
20
36
  def get_properties(indicator_id)
21
37
  _get "/api/info.php", params: { iid: indicator_id, get: "properties" }
22
38
  end
23
39
 
24
40
  private
25
41
 
26
- #
27
42
  #
28
43
  # @param [String] path
29
44
  # @param [Hash] params
30
45
  #
46
+ # @return [Hash]
47
+ #
31
48
  def _get(path, params: {})
32
49
  params["key"] = api_key
33
50
 
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Clients
5
+ class SecurityTrails < Base
6
+ #
7
+ # @param [String] base_url
8
+ # @param [String, nil] api_key
9
+ # @param [Hash] headers
10
+ #
11
+ def initialize(base_url = "https://api.securitytrails.com", api_key:, headers: {})
12
+ raise(ArgumentError, "'api_key' argument is required") unless api_key
13
+
14
+ headers["apikey"] = api_key
15
+ super(base_url, headers: headers)
16
+ end
17
+
18
+ #
19
+ # @param [String] mail
20
+ #
21
+ # @return [Array<Hash>]
22
+ #
23
+ def search_by_mail(mail)
24
+ res = _post "/v1/domains/list", json: { filter: { whois_email: mail } }
25
+ res["records"] || []
26
+ end
27
+
28
+ #
29
+ # @param [String] ip
30
+ #
31
+ # @return [Array<Hash>]
32
+ #
33
+ def search_by_ip(ip)
34
+ res = _post "/v1/domains/list", json: { filter: { ipv4: ip } }
35
+ res["records"] || []
36
+ end
37
+
38
+ #
39
+ # @param [String] domain
40
+ # @param [String] type
41
+ #
42
+ # @return [Array<Hash>]
43
+ #
44
+ def get_all_dns_history(domain, type:)
45
+ first_page = get_dns_history(domain, type: type, page: 1)
46
+
47
+ pages = first_page["pages"].to_i
48
+ records = first_page["records"] || []
49
+
50
+ (2..pages).each do |page_idx|
51
+ next_page = get_dns_history(domain, type: type, page: page_idx)
52
+ records << next_page["records"]
53
+ end
54
+
55
+ records.flatten
56
+ end
57
+
58
+ private
59
+
60
+ #
61
+ # @param [String] domain
62
+ # @param [String] type
63
+ # @param [Integer] page
64
+ #
65
+ # @return [Array<Hash>]
66
+ #
67
+ def get_dns_history(domain, type:, page:)
68
+ _get "/v1/history/#{domain}/dns/#{type}", params: { page: page }
69
+ end
70
+
71
+ #
72
+ # @param [String] path
73
+ # @param [Hash, nil] params
74
+ #
75
+ # @return [Hash]
76
+ #
77
+ def _get(path, params:)
78
+ res = get(path, params: params)
79
+ JSON.parse(res.body.to_s)
80
+ end
81
+
82
+ #
83
+ # @param [String] path
84
+ # @param [Hash, nil] json
85
+ #
86
+ # @return [Hash]
87
+ #
88
+ def _post(path, json:)
89
+ res = post(path, json: json)
90
+ JSON.parse(res.body.to_s)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -3,8 +3,14 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class Shodan < Base
6
+ # @return [String]
6
7
  attr_reader :api_key
7
8
 
9
+ #
10
+ # @param [String] base_url
11
+ # @param [String, nil] api_key
12
+ # @param [Hash] headers
13
+ #
8
14
  def initialize(base_url = "https://api.shodan.io", api_key:, headers: {})
9
15
  raise(ArgumentError, "'api_key' argument is required") unless api_key
10
16
 
@@ -13,8 +19,13 @@ module Mihari
13
19
  @api_key = api_key
14
20
  end
15
21
 
16
- # Search Shodan using the same query syntax as the website and use facets
17
- # to get summary information for different properties.
22
+ #
23
+ # @param [String] query
24
+ # @param [Integer] page
25
+ # @param [Boolean] minify
26
+ #
27
+ # @return [Structs::Shodan::Result]
28
+ #
18
29
  def search(query, page: 1, minify: true)
19
30
  params = {
20
31
  query: query,
@@ -23,7 +34,7 @@ module Mihari
23
34
  key: api_key
24
35
  }
25
36
  res = get("/shodan/host/search", params: params)
26
- JSON.parse(res.body.to_s)
37
+ Structs::Shodan::Result.from_dynamic! JSON.parse(res.body.to_s)
27
38
  end
28
39
  end
29
40
  end
@@ -5,7 +5,7 @@ module Mihari
5
5
  class TheHive < Base
6
6
  #
7
7
  # @param [String] base_url
8
- # @param [String] api_key
8
+ # @param [String, nil] api_key
9
9
  # @param [String, nil] api_version
10
10
  # @param [Hash] headers
11
11
  #
@@ -18,6 +18,11 @@ module Mihari
18
18
  super(base_url, headers: headers)
19
19
  end
20
20
 
21
+ #
22
+ # @param [Hash] json
23
+ #
24
+ # @return [Hash]
25
+ #
21
26
  def alert(json)
22
27
  json = json.to_camelback_keys.compact
23
28
  res = post("/alert", json: json)
@@ -5,7 +5,7 @@ module Mihari
5
5
  class UrlScan < Base
6
6
  #
7
7
  # @param [String] base_url
8
- # @param [String] api_key
8
+ # @param [String, nil] api_key
9
9
  # @param [Hash] headers
10
10
  #
11
11
  def initialize(base_url = "https://urlscan.io", api_key:, headers: {})
@@ -21,6 +21,8 @@ module Mihari
21
21
  # @param [Integer] size
22
22
  # @param [String, nil] search_after
23
23
  #
24
+ # @return [Hash]
25
+ #
24
26
  def search(q, size: 100, search_after: nil)
25
27
  params = { q: q, size: size, search_after: search_after }.compact
26
28
  res = get("/api/v1/search/", params: params)