mihari 5.1.1 → 5.1.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 (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)