aikido-zen 0.2.0-x86_64-mingw-64 → 1.0.1.beta.2-x86_64-mingw-64

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.aikido +6 -0
  3. data/.simplecov +6 -0
  4. data/README.md +67 -83
  5. data/benchmarks/README.md +8 -12
  6. data/docs/rails.md +1 -1
  7. data/lib/aikido/zen/agent.rb +10 -8
  8. data/lib/aikido/zen/api_client.rb +14 -4
  9. data/lib/aikido/zen/background_worker.rb +52 -0
  10. data/lib/aikido/zen/collector.rb +12 -1
  11. data/lib/aikido/zen/config.rb +20 -0
  12. data/lib/aikido/zen/context.rb +4 -0
  13. data/lib/aikido/zen/detached_agent/agent.rb +78 -0
  14. data/lib/aikido/zen/detached_agent/front_object.rb +37 -0
  15. data/lib/aikido/zen/detached_agent/server.rb +41 -0
  16. data/lib/aikido/zen/detached_agent.rb +2 -0
  17. data/lib/aikido/zen/errors.rb +8 -0
  18. data/lib/aikido/zen/internals.rb +41 -7
  19. data/lib/aikido/zen/libzen-v0.1.39-x86_64-mingw-64.dll +0 -0
  20. data/lib/aikido/zen/middleware/rack_throttler.rb +9 -3
  21. data/lib/aikido/zen/middleware/request_tracker.rb +6 -4
  22. data/lib/aikido/zen/outbound_connection_monitor.rb +4 -0
  23. data/lib/aikido/zen/rails_engine.rb +8 -8
  24. data/lib/aikido/zen/rate_limiter/breaker.rb +3 -3
  25. data/lib/aikido/zen/rate_limiter.rb +6 -11
  26. data/lib/aikido/zen/request/heuristic_router.rb +6 -0
  27. data/lib/aikido/zen/request/rails_router.rb +6 -18
  28. data/lib/aikido/zen/request/schema/auth_schemas.rb +14 -0
  29. data/lib/aikido/zen/request/schema.rb +18 -0
  30. data/lib/aikido/zen/runtime_settings.rb +2 -2
  31. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +4 -2
  32. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +4 -2
  33. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +4 -2
  34. data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +33 -21
  35. data/lib/aikido/zen/scanners/ssrf_scanner.rb +6 -1
  36. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +6 -0
  37. data/lib/aikido/zen/sink.rb +11 -1
  38. data/lib/aikido/zen/sinks/action_controller.rb +9 -4
  39. data/lib/aikido/zen/sinks/async_http.rb +35 -16
  40. data/lib/aikido/zen/sinks/curb.rb +52 -26
  41. data/lib/aikido/zen/sinks/em_http.rb +39 -25
  42. data/lib/aikido/zen/sinks/excon.rb +63 -45
  43. data/lib/aikido/zen/sinks/file.rb +67 -71
  44. data/lib/aikido/zen/sinks/http.rb +38 -19
  45. data/lib/aikido/zen/sinks/httpclient.rb +51 -22
  46. data/lib/aikido/zen/sinks/httpx.rb +37 -18
  47. data/lib/aikido/zen/sinks/kernel.rb +18 -57
  48. data/lib/aikido/zen/sinks/mysql2.rb +19 -7
  49. data/lib/aikido/zen/sinks/net_http.rb +37 -19
  50. data/lib/aikido/zen/sinks/patron.rb +41 -24
  51. data/lib/aikido/zen/sinks/pg.rb +50 -27
  52. data/lib/aikido/zen/sinks/resolv.rb +37 -16
  53. data/lib/aikido/zen/sinks/socket.rb +46 -17
  54. data/lib/aikido/zen/sinks/sqlite3.rb +31 -12
  55. data/lib/aikido/zen/sinks/trilogy.rb +19 -7
  56. data/lib/aikido/zen/sinks.rb +29 -20
  57. data/lib/aikido/zen/sinks_dsl.rb +226 -0
  58. data/lib/aikido/zen/version.rb +2 -2
  59. data/lib/aikido/zen/worker.rb +5 -0
  60. data/lib/aikido/zen.rb +59 -9
  61. data/placeholder/.gitignore +4 -0
  62. data/placeholder/README.md +11 -0
  63. data/placeholder/Rakefile +75 -0
  64. data/placeholder/lib/placeholder.rb.template +3 -0
  65. data/placeholder/placeholder.gemspec.template +20 -0
  66. data/tasklib/bench.rake +29 -6
  67. data/tasklib/libzen.rake +70 -66
  68. data/tasklib/wrk.rb +88 -0
  69. metadata +23 -13
  70. data/CHANGELOG.md +0 -25
  71. data/lib/aikido/zen/libzen-v0.1.37.x86_64.dll +0 -0
  72. data/lib/aikido.rb +0 -3
@@ -1,25 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../sink"
3
+ require_relative "../scanners/ssrf_scanner"
4
4
  require_relative "../outbound_connection_monitor"
5
5
 
6
6
  module Aikido::Zen
7
7
  module Sinks
8
8
  module Net
9
9
  module HTTP
10
+ def self.load_sinks!
11
+ # In stdlib but not always required
12
+ require "net/http"
13
+
14
+ ::Net::HTTP.prepend(Net::HTTP::HTTPExtensions)
15
+ end
16
+
10
17
  SINK = Sinks.add("net-http", scanners: [
11
- Aikido::Zen::Scanners::SSRFScanner,
12
- Aikido::Zen::OutboundConnectionMonitor
18
+ Scanners::SSRFScanner,
19
+ OutboundConnectionMonitor
13
20
  ])
14
21
 
15
- module Extensions
22
+ module Helpers
16
23
  # Maps a Net::HTTP connection to an Aikido OutboundConnection,
17
24
  # which our tooling expects.
18
25
  #
19
26
  # @param http [Net::HTTP]
20
27
  # @return [Aikido::Zen::OutboundConnection]
21
28
  def self.build_outbound(http)
22
- Aikido::Zen::OutboundConnection.new(
29
+ OutboundConnection.new(
23
30
  host: http.address,
24
31
  port: http.port
25
32
  )
@@ -34,7 +41,7 @@ module Aikido::Zen
34
41
  path: req.path
35
42
  }))
36
43
 
37
- Aikido::Zen::Scanners::SSRFScanner::Request.new(
44
+ Scanners::SSRFScanner::Request.new(
38
45
  verb: req.method,
39
46
  uri: uri,
40
47
  headers: req.to_hash,
@@ -43,33 +50,44 @@ module Aikido::Zen
43
50
  end
44
51
 
45
52
  def self.wrap_response(response)
46
- Aikido::Zen::Scanners::SSRFScanner::Response.new(
53
+ Scanners::SSRFScanner::Response.new(
47
54
  status: response.code.to_i,
48
55
  headers: response.to_hash,
49
56
  header_normalizer: ->(val) { Array(val).join(", ") }
50
57
  )
51
58
  end
52
59
 
53
- def request(req, *)
54
- wrapped_request = Extensions.wrap_request(req, self)
60
+ def self.scan(request, connection, operation)
61
+ SINK.scan(
62
+ request: request,
63
+ connection: connection,
64
+ operation: operation
65
+ )
66
+ end
67
+ end
68
+
69
+ module HTTPExtensions
70
+ extend Sinks::DSL
71
+
72
+ sink_around :request do |super_call, req|
73
+ wrapped_request = Helpers.wrap_request(req, self)
55
74
 
56
75
  # Store the request information so the DNS sinks can pick it up.
57
- if (context = Aikido::Zen.current_context)
76
+ context = Aikido::Zen.current_context
77
+ if context
58
78
  prev_request = context["ssrf.request"]
59
79
  context["ssrf.request"] = wrapped_request
60
80
  end
61
81
 
62
- SINK.scan(
63
- connection: Extensions.build_outbound(self),
64
- request: wrapped_request,
65
- operation: "request"
66
- )
82
+ connection = Helpers.build_outbound(self)
83
+
84
+ Helpers.scan(wrapped_request, connection, "request")
67
85
 
68
- response = super
86
+ response = super_call.call
69
87
 
70
- Aikido::Zen::Scanners::SSRFScanner.track_redirects(
88
+ Scanners::SSRFScanner.track_redirects(
71
89
  request: wrapped_request,
72
- response: Extensions.wrap_response(response)
90
+ response: Helpers.wrap_response(response)
73
91
  )
74
92
 
75
93
  response
@@ -82,4 +100,4 @@ module Aikido::Zen
82
100
  end
83
101
  end
84
102
 
85
- ::Net::HTTP.prepend(Aikido::Zen::Sinks::Net::HTTP::Extensions)
103
+ Aikido::Zen::Sinks::Net::HTTP.load_sinks!
@@ -1,56 +1,75 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../sink"
3
+ require_relative "../scanners/ssrf_scanner"
4
4
  require_relative "../outbound_connection_monitor"
5
5
 
6
6
  module Aikido::Zen
7
7
  module Sinks
8
8
  module Patron
9
+ def self.load_sinks!
10
+ if Gem.loaded_specs["patron"]
11
+ require "patron"
12
+
13
+ ::Patron::Session.prepend(SessionExtensions)
14
+ end
15
+ end
16
+
9
17
  SINK = Sinks.add("patron", scanners: [
10
- Aikido::Zen::Scanners::SSRFScanner,
11
- Aikido::Zen::OutboundConnectionMonitor
18
+ Scanners::SSRFScanner,
19
+ OutboundConnectionMonitor
12
20
  ])
13
21
 
14
- module Extensions
22
+ module Helpers
15
23
  def self.wrap_response(request, response)
16
24
  # In this case, automatic redirection happened inside libcurl.
17
25
  if response.url != request.url && !response.url.to_s.empty?
18
- Aikido::Zen::Scanners::SSRFScanner::Response.new(
26
+ Scanners::SSRFScanner::Response.new(
19
27
  status: 302, # We can't know what the actual status was, but we just need a 3XX
20
28
  headers: response.headers.merge("Location" => response.url)
21
29
  )
22
30
  else
23
- Aikido::Zen::Scanners::SSRFScanner::Response.new(
31
+ Scanners::SSRFScanner::Response.new(
24
32
  status: response.status,
25
33
  headers: response.headers
26
34
  )
27
35
  end
28
36
  end
29
37
 
30
- def handle_request(request)
31
- wrapped_request = Aikido::Zen::Scanners::SSRFScanner::Request.new(
38
+ def self.scan(request, connection, operation)
39
+ SINK.scan(
40
+ request: request,
41
+ connection: connection,
42
+ operation: operation
43
+ )
44
+ end
45
+ end
46
+
47
+ module SessionExtensions
48
+ extend Sinks::DSL
49
+
50
+ sink_around :handle_request do |super_call, request|
51
+ wrapped_request = Scanners::SSRFScanner::Request.new(
32
52
  verb: request.action,
33
53
  uri: URI(request.url),
34
54
  headers: request.headers
35
55
  )
36
56
 
37
57
  # Store the request information so the DNS sinks can pick it up.
38
- if (context = Aikido::Zen.current_context)
58
+ context = Aikido::Zen.current_context
59
+ if context
39
60
  prev_request = context["ssrf.request"]
40
61
  context["ssrf.request"] = wrapped_request
41
62
  end
42
63
 
43
- SINK.scan(
44
- connection: Aikido::Zen::OutboundConnection.from_uri(URI(request.url)),
45
- request: wrapped_request,
46
- operation: "request"
47
- )
64
+ connection = OutboundConnection.from_uri(URI(request.url))
65
+
66
+ Helpers.scan(wrapped_request, connection, "request")
48
67
 
49
- response = super
68
+ response = super_call.call
50
69
 
51
- Aikido::Zen::Scanners::SSRFScanner.track_redirects(
70
+ Scanners::SSRFScanner.track_redirects(
52
71
  request: wrapped_request,
53
- response: Extensions.wrap_response(request, response)
72
+ response: Helpers.wrap_response(request, response)
54
73
  )
55
74
 
56
75
  # When libcurl has follow_location set, it will handle redirections
@@ -62,18 +81,16 @@ module Aikido::Zen
62
81
  # stop the response from being exposed to the user. This downgrades
63
82
  # the SSRF into a blind SSRF, which is better than doing nothing.
64
83
  if request.url != response.url && !response.url.to_s.empty?
65
- last_effective_request = Aikido::Zen::Scanners::SSRFScanner::Request.new(
84
+ last_effective_request = Scanners::SSRFScanner::Request.new(
66
85
  verb: request.action,
67
86
  uri: URI(response.url),
68
87
  headers: request.headers
69
88
  )
70
89
  context["ssrf.request"] = last_effective_request if context
71
90
 
72
- SINK.scan(
73
- connection: Aikido::Zen::OutboundConnection.from_uri(URI(response.url)),
74
- request: last_effective_request,
75
- operation: "request"
76
- )
91
+ connection = OutboundConnection.from_uri(URI(response.url))
92
+
93
+ Helpers.scan(last_effective_request, connection, "request")
77
94
  end
78
95
 
79
96
  response
@@ -85,4 +102,4 @@ module Aikido::Zen
85
102
  end
86
103
  end
87
104
 
88
- ::Patron::Session.prepend(Aikido::Zen::Sinks::Patron::Extensions)
105
+ Aikido::Zen::Sinks::Patron.load_sinks!
@@ -1,51 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../sink"
4
-
5
3
  module Aikido::Zen
6
4
  module Sinks
7
5
  module PG
6
+ def self.load_sinks!
7
+ if Gem.loaded_specs["pg"]
8
+ require "pg"
9
+
10
+ ::PG::Connection.prepend(PG::ConnectionExtensions)
11
+ end
12
+ end
13
+
8
14
  SINK = Sinks.add("pg", scanners: [Scanners::SQLInjectionScanner])
9
15
 
10
- # For some reason, the ActiveRecord pg adapter does not wrap exceptions in
11
- # StatementInvalid, which leads to inconsistent handling. This guarantees
12
- # that all Zen errors are wrapped in a StatementInvalid, so documentation
13
- # can be consistent.
14
- WRAP_EXCEPTIONS = if defined?(ActiveRecord::StatementInvalid)
15
- <<~RUBY
16
- rescue Aikido::Zen::SQLInjectionError
17
- raise ActiveRecord::StatementInvalid
18
- RUBY
16
+ module Helpers
17
+ # For some reason, the ActiveRecord pg adaptor does not wrap exceptions
18
+ # in ActiveRecord::StatementInvalid, leading to inconsistent handling.
19
+ # This guarantees that Aikido::Zen::SQLInjectionErrors are wrapped in
20
+ # an ActiveRecord::StatementInvalid.
21
+ def self.safe(&block)
22
+ # Code coverage is disabled here because this ActiveRecord behavior is
23
+ # exercised in end-to-end tests, which are not covered by SimpleCov.
24
+ # :nocov:
25
+ if !defined?(ActiveRecord::StatementInvalid)
26
+ Sinks::DSL.safe(&block)
27
+ else
28
+ begin
29
+ Sinks::DSL.safe(&block)
30
+ rescue Aikido::Zen::SQLInjectionError => err
31
+ raise ActiveRecord::StatementInvalid, cause: err
32
+ end
33
+ end
34
+ # :nocov:
35
+ end
36
+
37
+ def self.scan(query, operation)
38
+ SINK.scan(
39
+ query: query,
40
+ dialect: :postgresql,
41
+ operation: operation
42
+ )
43
+ end
19
44
  end
20
45
 
21
- module Extensions
46
+ module ConnectionExtensions
47
+ extend Sinks::DSL
48
+
22
49
  %i[
23
50
  send_query exec sync_exec async_exec
24
51
  send_query_params exec_params sync_exec_params async_exec_params
25
- ].each do |method|
26
- module_eval <<~RUBY, __FILE__, __LINE__ + 1
27
- def #{method}(query, *)
28
- SINK.scan(query: query, dialect: :postgresql, operation: :#{method})
29
- super
30
- #{WRAP_EXCEPTIONS}
52
+ ].each do |method_name|
53
+ presafe_sink_before method_name do |query|
54
+ Helpers.safe do
55
+ Helpers.scan(query, method_name)
31
56
  end
32
- RUBY
57
+ end
33
58
  end
34
59
 
35
60
  %i[
36
61
  send_prepare prepare async_prepare sync_prepare
37
- ].each do |method|
38
- module_eval <<~RUBY, __FILE__, __LINE__ + 1
39
- def #{method}(_, query, *)
40
- SINK.scan(query: query, dialect: :postgresql, operation: :#{method})
41
- super
42
- #{WRAP_EXCEPTIONS}
62
+ ].each do |method_name|
63
+ presafe_sink_before method_name do |_, query|
64
+ Helpers.safe do
65
+ Helpers.scan(query, method_name)
43
66
  end
44
- RUBY
67
+ end
45
68
  end
46
69
  end
47
70
  end
48
71
  end
49
72
  end
50
73
 
51
- ::PG::Connection.prepend(Aikido::Zen::Sinks::PG::Extensions)
74
+ Aikido::Zen::Sinks::PG.load_sinks!
@@ -1,28 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../sink"
4
3
  require_relative "../scanners/stored_ssrf_scanner"
5
4
  require_relative "../scanners/ssrf_scanner"
6
5
 
7
6
  module Aikido::Zen
8
7
  module Sinks
9
8
  module Resolv
9
+ def self.load_sinks!
10
+ # In stdlib but not always required
11
+ require "resolv"
12
+
13
+ ::Resolv.prepend(ResolvExtensions)
14
+ end
15
+
10
16
  SINK = Sinks.add("resolv", scanners: [
11
- Aikido::Zen::Scanners::StoredSSRFScanner,
12
- Aikido::Zen::Scanners::SSRFScanner
17
+ Scanners::StoredSSRFScanner,
18
+ Scanners::SSRFScanner
13
19
  ])
14
20
 
15
- module Extensions
16
- def each_address(name, &block)
17
- addresses = []
18
-
19
- super do |address|
20
- addresses << address
21
- yield address
22
- end
23
- ensure
24
- if (context = Aikido::Zen.current_context)
25
- context["dns.lookups"] ||= Aikido::Zen::Scanners::SSRF::DNSLookups.new
21
+ module Helpers
22
+ def self.scan(name, addresses, operation)
23
+ context = Aikido::Zen.current_context
24
+ if context
25
+ context["dns.lookups"] ||= Scanners::SSRF::DNSLookups.new
26
26
  context["dns.lookups"].add(name, addresses)
27
27
  end
28
28
 
@@ -30,12 +30,33 @@ module Aikido::Zen
30
30
  hostname: name,
31
31
  addresses: addresses,
32
32
  request: context && context["ssrf.request"],
33
- operation: "lookup"
33
+ operation: operation
34
34
  )
35
35
  end
36
36
  end
37
+
38
+ module ResolvExtensions
39
+ def each_address(*args, **kwargs, &blk)
40
+ # each_address is defined "manually" because no sink method pattern
41
+ # is applicable.
42
+
43
+ name, = args
44
+
45
+ addresses = []
46
+ super(*args, **kwargs) do |address| # rubocop:disable Style/SuperArguments
47
+ addresses << address
48
+ blk.call(address)
49
+ end
50
+ ensure
51
+ # Ensure partial results are scanned.
52
+
53
+ Sinks::DSL.safe do
54
+ Helpers.scan(name, addresses, "lookup")
55
+ end
56
+ end
57
+ end
37
58
  end
38
59
  end
39
60
  end
40
61
 
41
- ::Resolv.prepend(Aikido::Zen::Sinks::Resolv::Extensions)
62
+ Aikido::Zen::Sinks::Resolv.load_sinks!
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "socket"
4
+ require_relative "../scanners/stored_ssrf_scanner"
5
+ require_relative "../scanners/ssrf_scanner"
4
6
 
5
7
  module Aikido::Zen
6
8
  module Sinks
@@ -8,44 +10,71 @@ module Aikido::Zen
8
10
  # there's no way to access the internal DNS resolution that happens in C
9
11
  # when using the socket primitives.
10
12
  module Socket
13
+ def self.load_sinks!
14
+ ::IPSocket.singleton_class.prepend(Socket::IPSocketExtensions)
15
+ end
16
+
11
17
  SINK = Sinks.add("socket", scanners: [
12
- Aikido::Zen::Scanners::StoredSSRFScanner,
13
- Aikido::Zen::Scanners::SSRFScanner
18
+ Scanners::StoredSSRFScanner,
19
+ Scanners::SSRFScanner
14
20
  ])
15
21
 
16
- module IPSocketExtensions
17
- def self.scan_socket(hostname, socket)
22
+ module Helpers
23
+ def self.scan(hostname, socket, operation)
24
+ # We're patching IPSocket.open(..) method.
25
+ # The IPSocket class hierarchy is:
26
+ # IPSocket
27
+ # / \
28
+ # TCPSocket UDPSocket
29
+ # / \
30
+ # TCPServer SOCKSSocket
31
+ #
32
+ # Because we want to scan only HTTP requests, we skip in case the
33
+ # socket is not *exactly* an instance of TCPSocket — it's any
34
+ # of it subclasses
35
+ return unless socket.instance_of?(TCPSocket)
36
+
18
37
  # ["AF_INET", 80, "10.0.0.1", "10.0.0.1"]
19
- addr_family, *, remote_address = socket.peeraddr
38
+ address_family, _port, _hostname, numeric_address = socket.peeraddr(:numeric)
20
39
 
21
40
  # We only care about IPv4 (AF_INET) or IPv6 (AF_INET6) sockets
22
41
  # This might be overcautious, since this is _IP_Socket, so you
23
42
  # would expect it's only used for IP connections?
24
- return unless addr_family.start_with?("AF_INET")
25
43
 
26
- if (context = Aikido::Zen.current_context)
27
- context["dns.lookups"] ||= Aikido::Zen::Scanners::SSRF::DNSLookups.new
28
- context["dns.lookups"].add(hostname, remote_address)
44
+ # Code coverage is disabled here because the then clause is a no-op,
45
+ # so there is nothing to cover.
46
+ # :nocov:
47
+ return unless address_family.start_with?("AF_INET")
48
+ # :nocov:
49
+
50
+ context = Aikido::Zen.current_context
51
+ if context
52
+ context["dns.lookups"] ||= Scanners::SSRF::DNSLookups.new
53
+ context["dns.lookups"].add(hostname, numeric_address)
29
54
  end
30
55
 
31
56
  SINK.scan(
32
57
  hostname: hostname,
33
- addresses: [remote_address],
58
+ addresses: [numeric_address],
34
59
  request: context && context["ssrf.request"],
35
- operation: "open"
60
+ operation: operation
36
61
  )
37
62
  end
63
+ end
38
64
 
39
- def open(name, *)
40
- socket = super
41
-
42
- IPSocketExtensions.scan_socket(name, socket)
65
+ module IPSocketExtensions
66
+ extend Sinks::DSL
43
67
 
44
- socket
68
+ sink_after :open do |socket, remote_host|
69
+ # Code coverage is disabled here because the tests are contrived and
70
+ # intentionally do not call open.
71
+ # :nocov:
72
+ Helpers.scan(remote_host, socket, "open")
73
+ # :nocov:
45
74
  end
46
75
  end
47
76
  end
48
77
  end
49
78
  end
50
79
 
51
- ::IPSocket.singleton_class.prepend(Aikido::Zen::Sinks::Socket::IPSocketExtensions)
80
+ Aikido::Zen::Sinks::Socket.load_sinks!
@@ -1,30 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../sink"
4
-
5
3
  module Aikido::Zen
6
4
  module Sinks
7
5
  module SQLite3
6
+ def self.load_sinks!
7
+ if Gem.loaded_specs["sqlite3"]
8
+ require "sqlite3"
9
+
10
+ ::SQLite3::Database.prepend(DatabaseExtensions)
11
+ ::SQLite3::Statement.prepend(StatementExtensions)
12
+ end
13
+ end
14
+
8
15
  SINK = Sinks.add("sqlite3", scanners: [Scanners::SQLInjectionScanner])
9
16
 
10
- module DatabaseExt
11
- def exec_batch(sql, *)
12
- SINK.scan(query: sql, dialect: :sqlite, operation: "exec_batch")
17
+ module Helpers
18
+ def self.scan(query, operation)
19
+ SINK.scan(
20
+ query: query,
21
+ dialect: :sqlite,
22
+ operation: operation
23
+ )
24
+ end
25
+ end
26
+
27
+ module DatabaseExtensions
28
+ extend Sinks::DSL
29
+
30
+ private
13
31
 
14
- super
32
+ # SQLite3::Database#exec_batch is an internal native private method.
33
+ sink_before :exec_batch do |sql|
34
+ Helpers.scan(sql, "exec_batch")
15
35
  end
16
36
  end
17
37
 
18
- module StatementExt
19
- def initialize(_, sql, *)
20
- SINK.scan(query: sql, dialect: :sqlite, operation: "statement.execute")
38
+ module StatementExtensions
39
+ extend Sinks::DSL
21
40
 
22
- super
41
+ sink_before :initialize do |_db, sql|
42
+ Helpers.scan(sql, "statement.execute")
23
43
  end
24
44
  end
25
45
  end
26
46
  end
27
47
  end
28
48
 
29
- ::SQLite3::Database.prepend(Aikido::Zen::Sinks::SQLite3::DatabaseExt)
30
- ::SQLite3::Statement.prepend(Aikido::Zen::Sinks::SQLite3::StatementExt)
49
+ Aikido::Zen::Sinks::SQLite3.load_sinks!
@@ -1,21 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../sink"
4
-
5
3
  module Aikido::Zen
6
4
  module Sinks
7
5
  module Trilogy
6
+ def self.load_sinks!
7
+ if Gem.loaded_specs["trilogy"]
8
+ require "trilogy"
9
+
10
+ ::Trilogy.prepend(TrilogyExtensions)
11
+ end
12
+ end
13
+
8
14
  SINK = Sinks.add("trilogy", scanners: [Scanners::SQLInjectionScanner])
9
15
 
10
- module Extensions
11
- def query(query, *)
12
- SINK.scan(query: query, dialect: :mysql, operation: "query")
16
+ module Helpers
17
+ def self.scan(query, operation)
18
+ SINK.scan(query: query, dialect: :mysql, operation: operation)
19
+ end
20
+ end
21
+
22
+ module TrilogyExtensions
23
+ extend Sinks::DSL
13
24
 
14
- super
25
+ sink_before :query do |query|
26
+ Helpers.scan(query, "query")
15
27
  end
16
28
  end
17
29
  end
18
30
  end
19
31
  end
20
32
 
21
- ::Trilogy.prepend(Aikido::Zen::Sinks::Trilogy::Extensions)
33
+ Aikido::Zen::Sinks::Trilogy.load_sinks!
@@ -1,30 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "sink"
3
+ # Code coverage is disabled in this file because it is environment-specific and
4
+ # not intended to be tested directly.
5
+ # :nocov:
4
6
 
5
- require_relative "sinks/socket"
7
+ require_relative "sink"
8
+ require_relative "sinks_dsl"
6
9
 
7
10
  require_relative "sinks/action_controller" if defined?(::ActionController)
8
- require_relative "sinks/file" if defined?(::File)
9
11
 
10
12
  # Sadly, in ruby versions lower than 3.0, it's not possible to patch the
11
13
  # Kernel module because how the `prepend` method is applied
12
14
  # (https://stackoverflow.com/questions/78110397/prepend-kernel-module-function-globally#comment137713906_78112924)
13
- if RUBY_VERSION >= "3.0"
14
- require_relative "sinks/kernel" if defined?(::Kernel)
15
- end
16
- require_relative "sinks/resolv" if defined?(::Resolv)
17
- require_relative "sinks/net_http" if defined?(::Net::HTTP)
18
- require_relative "sinks/http" if defined?(::HTTP)
19
- require_relative "sinks/httpx" if defined?(::HTTPX)
20
- require_relative "sinks/httpclient" if defined?(::HTTPClient)
21
- require_relative "sinks/excon" if defined?(::Excon)
22
- require_relative "sinks/curb" if defined?(::Curl)
23
- require_relative "sinks/patron" if defined?(::Patron)
15
+ require_relative "sinks/kernel" if RUBY_VERSION >= "3.0"
16
+
17
+ require_relative "sinks/file"
18
+ require_relative "sinks/socket"
19
+ require_relative "sinks/resolv"
20
+ require_relative "sinks/net_http"
21
+
22
+ # http.rb aims to support and is tested against Ruby 3.0+:
23
+ # https://github.com/httprb/http?tab=readme-ov-file#supported-ruby-versions
24
+ require_relative "sinks/http" if RUBY_VERSION >= "3.0"
25
+
26
+ require_relative "sinks/httpx"
27
+ require_relative "sinks/httpclient"
28
+ require_relative "sinks/excon"
29
+ require_relative "sinks/curb"
30
+ require_relative "sinks/patron"
24
31
  require_relative "sinks/typhoeus" if defined?(::Typhoeus)
25
- require_relative "sinks/async_http" if defined?(::Async::HTTP)
26
- require_relative "sinks/em_http" if defined?(::EventMachine::HttpRequest)
27
- require_relative "sinks/mysql2" if defined?(::Mysql2)
28
- require_relative "sinks/pg" if defined?(::PG)
29
- require_relative "sinks/sqlite3" if defined?(::SQLite3)
30
- require_relative "sinks/trilogy" if defined?(::Trilogy)
32
+ require_relative "sinks/async_http"
33
+ require_relative "sinks/em_http"
34
+ require_relative "sinks/mysql2"
35
+ require_relative "sinks/pg"
36
+ require_relative "sinks/sqlite3"
37
+ require_relative "sinks/trilogy"
38
+
39
+ # :nocov: