aikido-zen 1.0.1.beta.3 → 1.0.1.beta.5

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.
@@ -3,14 +3,6 @@
3
3
  module Aikido::Zen
4
4
  module Sinks
5
5
  module PG
6
- def self.load_sinks!
7
- if Aikido::Zen.satisfy "pg", ">= 1.0"
8
- require "pg"
9
-
10
- ::PG::Connection.prepend(PG::ConnectionExtensions)
11
- end
12
- end
13
-
14
6
  SINK = Sinks.add("pg", scanners: [Scanners::SQLInjectionScanner])
15
7
 
16
8
  module Helpers
@@ -43,26 +35,32 @@ module Aikido::Zen
43
35
  end
44
36
  end
45
37
 
46
- module ConnectionExtensions
47
- extend Sinks::DSL
38
+ def self.load_sinks!
39
+ if Aikido::Zen.satisfy "pg", ">= 1.0"
40
+ require "pg"
48
41
 
49
- %i[
50
- send_query exec sync_exec async_exec
51
- send_query_params exec_params sync_exec_params async_exec_params
52
- ].each do |method_name|
53
- presafe_sink_before method_name do |query|
54
- Helpers.safe do
55
- Helpers.scan(query, method_name)
42
+ ::PG::Connection.class_eval do
43
+ extend Sinks::DSL
44
+
45
+ %i[
46
+ send_query exec sync_exec async_exec
47
+ send_query_params exec_params sync_exec_params async_exec_params
48
+ ].each do |method_name|
49
+ presafe_sink_before method_name do |query|
50
+ Helpers.safe do
51
+ Helpers.scan(query, method_name)
52
+ end
53
+ end
56
54
  end
57
- end
58
- end
59
55
 
60
- %i[
61
- send_prepare prepare async_prepare sync_prepare
62
- ].each do |method_name|
63
- presafe_sink_before method_name do |_, query|
64
- Helpers.safe do
65
- Helpers.scan(query, method_name)
56
+ %i[
57
+ send_prepare prepare async_prepare sync_prepare
58
+ ].each do |method_name|
59
+ presafe_sink_before method_name do |_, query|
60
+ Helpers.safe do
61
+ Helpers.scan(query, method_name)
62
+ end
63
+ end
66
64
  end
67
65
  end
68
66
  end
@@ -6,13 +6,6 @@ require_relative "../scanners/ssrf_scanner"
6
6
  module Aikido::Zen
7
7
  module Sinks
8
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
-
16
9
  SINK = Sinks.add("resolv", scanners: [
17
10
  Scanners::StoredSSRFScanner,
18
11
  Scanners::SSRFScanner
@@ -35,23 +28,30 @@ module Aikido::Zen
35
28
  end
36
29
  end
37
30
 
38
- module ResolvExtensions
39
- def each_address(*args, **kwargs, &blk)
40
- # each_address is defined "manually" because no sink method pattern
41
- # is applicable.
31
+ def self.load_sinks!
32
+ # In stdlib but not always required
33
+ require "resolv"
42
34
 
43
- name, = args
35
+ ::Resolv.class_eval do
36
+ alias_method :each_address__internal_for_aikido_zen, :each_address
44
37
 
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.
38
+ def each_address(*args, **kwargs, &blk)
39
+ # each_address is defined "manually" because no sink method pattern
40
+ # is applicable.
41
+
42
+ name, = args
43
+
44
+ addresses = []
45
+ each_address__internal_for_aikido_zen(*args, **kwargs) do |address|
46
+ addresses << address
47
+ blk.call(address)
48
+ end
49
+ ensure
50
+ # Ensure partial results are scanned.
52
51
 
53
- Sinks::DSL.safe do
54
- Helpers.scan(name, addresses, "lookup")
52
+ Sinks::DSL.safe do
53
+ Helpers.scan(name, addresses, "lookup")
54
+ end
55
55
  end
56
56
  end
57
57
  end
@@ -10,10 +10,6 @@ module Aikido::Zen
10
10
  # there's no way to access the internal DNS resolution that happens in C
11
11
  # when using the socket primitives.
12
12
  module Socket
13
- def self.load_sinks!
14
- ::IPSocket.singleton_class.prepend(Socket::IPSocketExtensions)
15
- end
16
-
17
13
  SINK = Sinks.add("socket", scanners: [
18
14
  Scanners::StoredSSRFScanner,
19
15
  Scanners::SSRFScanner
@@ -62,15 +58,17 @@ module Aikido::Zen
62
58
  end
63
59
  end
64
60
 
65
- module IPSocketExtensions
66
- extend Sinks::DSL
61
+ def self.load_sinks!
62
+ ::IPSocket.singleton_class.class_eval do
63
+ extend Sinks::DSL
67
64
 
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:
65
+ sink_after :open do |socket, remote_host|
66
+ # Code coverage is disabled here because the tests are contrived and
67
+ # intentionally do not call open.
68
+ # :nocov:
69
+ Helpers.scan(remote_host, socket, "open")
70
+ # :nocov:
71
+ end
74
72
  end
75
73
  end
76
74
  end
@@ -3,15 +3,6 @@
3
3
  module Aikido::Zen
4
4
  module Sinks
5
5
  module SQLite3
6
- def self.load_sinks!
7
- if Aikido::Zen.satisfy "sqlite3", ">= 1.0"
8
- require "sqlite3"
9
-
10
- ::SQLite3::Database.prepend(DatabaseExtensions)
11
- ::SQLite3::Statement.prepend(StatementExtensions)
12
- end
13
- end
14
-
15
6
  SINK = Sinks.add("sqlite3", scanners: [Scanners::SQLInjectionScanner])
16
7
 
17
8
  module Helpers
@@ -24,22 +15,28 @@ module Aikido::Zen
24
15
  end
25
16
  end
26
17
 
27
- module DatabaseExtensions
28
- extend Sinks::DSL
18
+ def self.load_sinks!
19
+ if Aikido::Zen.satisfy "sqlite3", ">= 1.0"
20
+ require "sqlite3"
29
21
 
30
- private
22
+ ::SQLite3::Database.class_eval do
23
+ extend Sinks::DSL
31
24
 
32
- # SQLite3::Database#exec_batch is an internal native private method.
33
- sink_before :exec_batch do |sql|
34
- Helpers.scan(sql, "exec_batch")
35
- end
36
- end
25
+ private
26
+
27
+ # SQLite3::Database#exec_batch is an internal native private method.
28
+ sink_before :exec_batch do |sql|
29
+ Helpers.scan(sql, "exec_batch")
30
+ end
31
+ end
37
32
 
38
- module StatementExtensions
39
- extend Sinks::DSL
33
+ ::SQLite3::Statement.class_eval do
34
+ extend Sinks::DSL
40
35
 
41
- sink_before :initialize do |_db, sql|
42
- Helpers.scan(sql, "statement.execute")
36
+ sink_before :initialize do |_db, sql|
37
+ Helpers.scan(sql, "statement.execute")
38
+ end
39
+ end
43
40
  end
44
41
  end
45
42
  end
@@ -3,14 +3,6 @@
3
3
  module Aikido::Zen
4
4
  module Sinks
5
5
  module Trilogy
6
- def self.load_sinks!
7
- if Aikido::Zen.satisfy "trilogy", ">= 2.0"
8
- require "trilogy"
9
-
10
- ::Trilogy.prepend(TrilogyExtensions)
11
- end
12
- end
13
-
14
6
  SINK = Sinks.add("trilogy", scanners: [Scanners::SQLInjectionScanner])
15
7
 
16
8
  module Helpers
@@ -19,11 +11,17 @@ module Aikido::Zen
19
11
  end
20
12
  end
21
13
 
22
- module TrilogyExtensions
23
- extend Sinks::DSL
14
+ def self.load_sinks!
15
+ if Aikido::Zen.satisfy "trilogy", ">= 2.0"
16
+ require "trilogy"
17
+
18
+ ::Trilogy.class_eval do
19
+ extend Sinks::DSL
24
20
 
25
- sink_before :query do |query|
26
- Helpers.scan(query, "query")
21
+ sink_before :query do |query|
22
+ Helpers.scan(query, "query")
23
+ end
24
+ end
27
25
  end
28
26
  end
29
27
  end
@@ -9,10 +9,7 @@ require_relative "sinks_dsl"
9
9
 
10
10
  require_relative "sinks/action_controller" if defined?(::ActionController)
11
11
 
12
- # Sadly, in ruby versions lower than 3.0, it's not possible to patch the
13
- # Kernel module because how the `prepend` method is applied
14
- # (https://stackoverflow.com/questions/78110397/prepend-kernel-module-function-globally#comment137713906_78112924)
15
- require_relative "sinks/kernel" if RUBY_VERSION >= "3.0"
12
+ require_relative "sinks/kernel"
16
13
 
17
14
  require_relative "sinks/file"
18
15
  require_relative "sinks/socket"
@@ -109,10 +109,14 @@ module Aikido::Zen
109
109
  #
110
110
  # @return [void]
111
111
  def presafe_sink_before(method_name, &block)
112
+ original = instance_method(method_name)
113
+
112
114
  define_method(method_name) do |*args, **kwargs, &blk|
113
115
  instance_exec(*args, **kwargs, &block)
114
- super(*args, **kwargs, &blk)
116
+ original.bind_call(self, *args, **kwargs, &blk)
115
117
  end
118
+ rescue NameError
119
+ Aikido::Zen.config.logger.warn("cannot wrap method `#{method_name}' for class `#{self}'")
116
120
  end
117
121
 
118
122
  # Define a method `method_name` that safely executes the given block before
@@ -145,11 +149,15 @@ module Aikido::Zen
145
149
  #
146
150
  # @return [void]
147
151
  def presafe_sink_after(method_name, &block)
152
+ original = instance_method(method_name)
153
+
148
154
  define_method(method_name) do |*args, **kwargs, &blk|
149
- result = super(*args, **kwargs, &blk)
155
+ result = original.bind_call(self, *args, **kwargs, &blk)
150
156
  instance_exec(result, *args, **kwargs, &block)
151
157
  result
152
158
  end
159
+ rescue NameError
160
+ Aikido::Zen.config.logger.warn("cannot wrap method `#{method_name}' for class `#{self}'")
153
161
  end
154
162
 
155
163
  # Define a method `method_name` that safely executes the given block after
@@ -177,20 +185,24 @@ module Aikido::Zen
177
185
  #
178
186
  # @param method_name [Symbol, String] the name of the method to define
179
187
  # @yield the block to execute around the original method
180
- # @yieldparam super_call [Proc] the proc that calls the original method
188
+ # @yieldparam original_call [Proc] the proc that calls the original method
181
189
  # @yieldparam args [Array] the positional arguments passed to the original method
182
190
  # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
183
191
  #
184
192
  # @return [void]
185
193
  def presafe_sink_around(method_name, &block)
194
+ original = instance_method(method_name)
195
+
186
196
  define_method(method_name) do |*args, **kwargs, &blk|
187
197
  result = nil
188
- super_call = proc do
189
- result = super(*args, **kwargs, &blk)
198
+ original_call = proc do
199
+ result = original.bind_call(self, *args, **kwargs, &blk)
190
200
  end
191
- instance_exec(super_call, *args, **kwargs, &block)
201
+ instance_exec(original_call, *args, **kwargs, &block)
192
202
  result
193
203
  end
204
+ rescue NameError
205
+ Aikido::Zen.config.logger.warn("cannot wrap method `#{method_name}' for class `#{self}'")
194
206
  end
195
207
 
196
208
  # Define a method `method_name` that safely executes the given block around
@@ -198,27 +210,27 @@ module Aikido::Zen
198
210
  #
199
211
  # @param method_name [Symbol, String] the name of the method to define
200
212
  # @yield the block to execute around the original method
201
- # @yieldparam super_call [Proc] the proc that calls the original method
213
+ # @yieldparam original_call [Proc] the proc that calls the original method
202
214
  # @yieldparam args [Array] the positional arguments passed to the original method
203
215
  # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
204
216
  #
205
217
  # @return [void]
206
218
  #
207
219
  # @note the block is executed within `safe` to handle errors safely; the original method is executed within `presafe` to preserve the original behavior
208
- # @note if the block does not call `super_call`, the original method is called automatically after the block is executed
220
+ # @note if the block does not call `original_call`, the original method is called automatically after the block is executed
209
221
  def sink_around(method_name, &block)
210
- presafe_sink_around(method_name) do |presafe_super_call, *args, **kwargs|
211
- super_called = false
212
- super_call = proc do
213
- super_called = true
222
+ presafe_sink_around(method_name) do |presafe_original_call, *args, **kwargs|
223
+ original_called = false
224
+ original_call = proc do
225
+ original_called = true
214
226
  DSL.presafe do
215
- presafe_super_call.call
227
+ presafe_original_call.call
216
228
  end
217
229
  end
218
230
  DSL.safe do
219
- instance_exec(super_call, *args, **kwargs, &block)
231
+ instance_exec(original_call, *args, **kwargs, &block)
220
232
  end
221
- presafe_super_call.call unless super_called
233
+ presafe_original_call.call unless original_called
222
234
  end
223
235
  end
224
236
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Aikido
4
4
  module Zen
5
- VERSION = "1.0.1.beta.3"
5
+ VERSION = "1.0.1.beta.5"
6
6
 
7
7
  # The version of libzen_internals that we build against.
8
8
  LIBZEN_VERSION = "0.1.39"
data/lib/aikido/zen.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # IMPORTANT: Any files that load sinks or start the Aikido Agent should
4
- # be required in `Aikido::Zen.protect!`.
5
-
6
3
  require_relative "zen/version"
7
4
  require_relative "zen/errors"
8
5
  require_relative "zen/actor"
@@ -29,6 +26,9 @@ module Aikido
29
26
  # Enable protection. Until this method is called no sinks are loaded
30
27
  # and the Aikido Agent does not start.
31
28
  #
29
+ # This method should be called only once, in the application after the
30
+ # initialization process is complete.
31
+ #
32
32
  # @return [void]
33
33
  def self.protect!
34
34
  if config.disabled?
@@ -36,16 +36,14 @@ module Aikido
36
36
  return
37
37
  end
38
38
 
39
- # IMPORTANT: Any files that load sinks or start the Aikido Agent
40
- # should be required here only.
39
+ return unless config.protect?
41
40
 
42
- if Aikido::Zen.satisfy "rails", ">= 7.0"
43
- require_relative "zen/rails_engine"
41
+ unless load_sources! && load_sinks!
42
+ config.logger.warn("Zen could not find any supported libraries or frameworks. Visit https://github.com/AikidoSec/firewall-ruby for more information.")
43
+ return
44
44
  end
45
45
 
46
- if Aikido::Zen::Sinks.registry.empty?
47
- warn "Zen could not find any supported libraries or frameworks. Visit https://github.com/AikidoSec/firewall-ruby for more information."
48
- end
46
+ middleware_installed!
49
47
  end
50
48
 
51
49
  # @!visibility private
@@ -173,15 +171,28 @@ module Aikido
173
171
  collector.middleware_installed!
174
172
  end
175
173
 
176
- # Load all sinks matching libraries loaded into memory. This method should
177
- # be called after all other dependencies have been loaded into memory (i.e.
178
- # at the end of the initialization process).
174
+ # @!visibility private
175
+ # Load all sources.
179
176
  #
180
- # If a new gem is required, this method can be called again safely.
177
+ # @return [Boolean] true if any sources were loaded
178
+ def self.load_sources!
179
+ if Aikido::Zen.satisfy("rails", ">= 7.0")
180
+ require_relative "zen/rails_engine"
181
+
182
+ return true
183
+ end
184
+
185
+ false
186
+ end
187
+
188
+ # @!visibility private
189
+ # Load all sinks.
181
190
  #
182
- # @return [void]
191
+ # @return [Boolean] true if any sinks were loaded
183
192
  def self.load_sinks!
184
193
  require_relative "zen/sinks"
194
+
195
+ !Aikido::Zen::Sinks.registry.empty?
185
196
  end
186
197
 
187
198
  # @!visibility private
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aikido-zen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1.beta.3
4
+ version: 1.0.1.beta.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aikido Security
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-06 00:00:00.000000000 Z
11
+ date: 2025-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby