aikido-zen 1.0.0.pre.beta.1-arm64-darwin → 1.0.1.beta.3-arm64-darwin

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.aikido +6 -0
  3. data/README.md +67 -83
  4. data/lib/aikido/zen/api_client.rb +6 -3
  5. data/lib/aikido/zen/config.rb +11 -2
  6. data/lib/aikido/zen/context/rails_request.rb +3 -1
  7. data/lib/aikido/zen/context.rb +4 -0
  8. data/lib/aikido/zen/internals.rb +48 -13
  9. data/lib/aikido/zen/libzen-v0.1.39-arm64-darwin.dylib +0 -0
  10. data/lib/aikido/zen/middleware/request_tracker.rb +6 -4
  11. data/lib/aikido/zen/rails_engine.rb +5 -9
  12. data/lib/aikido/zen/request/heuristic_router.rb +6 -0
  13. data/lib/aikido/zen/request/rails_router.rb +5 -0
  14. data/lib/aikido/zen/sink.rb +5 -0
  15. data/lib/aikido/zen/sinks/async_http.rb +35 -16
  16. data/lib/aikido/zen/sinks/curb.rb +52 -26
  17. data/lib/aikido/zen/sinks/em_http.rb +39 -25
  18. data/lib/aikido/zen/sinks/excon.rb +63 -45
  19. data/lib/aikido/zen/sinks/file.rb +67 -71
  20. data/lib/aikido/zen/sinks/http.rb +38 -19
  21. data/lib/aikido/zen/sinks/httpclient.rb +51 -22
  22. data/lib/aikido/zen/sinks/httpx.rb +37 -18
  23. data/lib/aikido/zen/sinks/kernel.rb +18 -57
  24. data/lib/aikido/zen/sinks/mysql2.rb +19 -7
  25. data/lib/aikido/zen/sinks/net_http.rb +37 -19
  26. data/lib/aikido/zen/sinks/patron.rb +41 -24
  27. data/lib/aikido/zen/sinks/pg.rb +50 -27
  28. data/lib/aikido/zen/sinks/resolv.rb +37 -16
  29. data/lib/aikido/zen/sinks/socket.rb +33 -17
  30. data/lib/aikido/zen/sinks/sqlite3.rb +31 -12
  31. data/lib/aikido/zen/sinks/trilogy.rb +19 -7
  32. data/lib/aikido/zen/sinks.rb +29 -20
  33. data/lib/aikido/zen/sinks_dsl.rb +226 -0
  34. data/lib/aikido/zen/version.rb +2 -2
  35. data/lib/aikido/zen.rb +42 -1
  36. data/placeholder/.gitignore +4 -0
  37. data/placeholder/README.md +11 -0
  38. data/placeholder/Rakefile +75 -0
  39. data/placeholder/lib/placeholder.rb.template +3 -0
  40. data/placeholder/placeholder.gemspec.template +20 -0
  41. data/tasklib/libzen.rake +70 -66
  42. metadata +17 -13
  43. data/CHANGELOG.md +0 -25
  44. data/lib/aikido/zen/libzen-v0.1.37.aarch64.dylib +0 -0
  45. data/lib/aikido.rb +0 -3
@@ -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,13 +10,17 @@ 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)
18
24
  # We're patching IPSocket.open(..) method.
19
25
  # The IPSocket class hierarchy is:
20
26
  # IPSocket
@@ -29,36 +35,46 @@ module Aikido::Zen
29
35
  return unless socket.instance_of?(TCPSocket)
30
36
 
31
37
  # ["AF_INET", 80, "10.0.0.1", "10.0.0.1"]
32
- addr_family, *, remote_address = socket.peeraddr
38
+ address_family, _port, _hostname, numeric_address = socket.peeraddr(:numeric)
33
39
 
34
40
  # We only care about IPv4 (AF_INET) or IPv6 (AF_INET6) sockets
35
41
  # This might be overcautious, since this is _IP_Socket, so you
36
42
  # would expect it's only used for IP connections?
37
- return unless addr_family.start_with?("AF_INET")
38
43
 
39
- if (context = Aikido::Zen.current_context)
40
- context["dns.lookups"] ||= Aikido::Zen::Scanners::SSRF::DNSLookups.new
41
- 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)
42
54
  end
43
55
 
44
56
  SINK.scan(
45
57
  hostname: hostname,
46
- addresses: [remote_address],
58
+ addresses: [numeric_address],
47
59
  request: context && context["ssrf.request"],
48
- operation: "open"
60
+ operation: operation
49
61
  )
50
62
  end
63
+ end
51
64
 
52
- def open(name, *)
53
- socket = super
54
-
55
- IPSocketExtensions.scan_socket(name, socket)
65
+ module IPSocketExtensions
66
+ extend Sinks::DSL
56
67
 
57
- 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:
58
74
  end
59
75
  end
60
76
  end
61
77
  end
62
78
  end
63
79
 
64
- ::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 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
+
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 Aikido::Zen.satisfy "trilogy", ">= 2.0"
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:
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ module Sinks
5
+ module DSL
6
+ extend self
7
+
8
+ # In the context of `Aikido::Zen::Sinks::DSL`, the terms safe and presafe
9
+ # are defined as follows:
10
+ #
11
+ # safe: the desired state for a sink, particularly with respect to rescue.
12
+ #
13
+ # A sink is considered safe when unintended errors in the sink are handled,
14
+ # and-so are prevented from disrupting the operation of the original method
15
+ # (by raising unintended errors).
16
+ #
17
+ # presafe: the default state of a sink, particularly with respect to rescue.
18
+ #
19
+ # A sink is in the presafe state before and while unintended errors in the
20
+ # sink are not handled.
21
+ #
22
+ # Sink methods (like all methods) are in the presafe state when defined and
23
+ # become safe when unexpected errors cannot cause harm. The `safe` method
24
+ # is used to establish a safe state for the duration of the block executed.
25
+ # It is sometimes useful to be able to reestablish the presafe safe while
26
+ # inside a `safe` block; the `presafe` method allows this.
27
+ #
28
+ # Methods that contain the term presafe in their name should be used with
29
+ # appropriate care and understanding.
30
+ #
31
+ # IMPORTANT: All sinks should be safe!
32
+ #
33
+ # While this DSL proves useful for defining safe sink methods that follow
34
+ # common patterns, there are exceptions. It is always possible to define
35
+ # sink methods without using this DSL, but this should only be done when
36
+ # absolutely necessary. The sink methods defined using this DSL are safe,
37
+ # unless explicitly declared presafe.
38
+ #
39
+ # IMPORTANT: No sinks should be presafe in production!
40
+ #
41
+ # We are all responsible for ensuring that the sinks we implement are safe
42
+ # for production use. This DSL is only here to assist, by taking care of
43
+ # delicate edge cases and reducing the space for errors.
44
+ #
45
+ # When writing sink methods manually, some principles should be considered,
46
+ # to ensure safety for production:
47
+ #
48
+ # 1. Sink methods should ensure that the original method is always called,
49
+ # passing all parameters (positional, keyword, and block) exactly as they
50
+ # were passed to the original method, and return the result returned by
51
+ # the original method exactly as it was returned by the original method.
52
+ # (Unless intervention is required.)
53
+ #
54
+ # 2. Sink methods should not predict the signature of the original method,
55
+ # and-so restrict it from varying. The original method implementation is
56
+ # the sole and ultimate reference for its own behavior. We are observers
57
+ # (unless intervention is required).
58
+ #
59
+ # 3. Unexpected errors that are encountered in sink methods should not be
60
+ # capable of interfering with or preventing the normal operation of the
61
+ # original method. This includes but is not limited to exceptions that
62
+ # that may be raised when the sink method is called. Safe sink methods
63
+ # should return control to their caller (unless intervention is required).
64
+ #
65
+ # These are the guidelines adhered to by the `Aikido::Zen::Sinks::DSL`.
66
+
67
+ # The error with an original error as its cause to re-raise in `safe`.
68
+ class PresafeError < StandardError
69
+ end
70
+
71
+ # Safely execute the given block
72
+ #
73
+ # All standard errors are suppressed except `Aikido::Zen::UnderAttackError`s.
74
+ # This ensures that unexpected errors do not interrupt the execution of the
75
+ # original method, while all detected attacks are raised.
76
+ #
77
+ # When an error is wrapped in `PresafeError` the original error is reraised.
78
+ #
79
+ # @yield the block to execute
80
+ def safe
81
+ yield
82
+ rescue Aikido::Zen::UnderAttackError
83
+ raise
84
+ rescue PresafeError => err
85
+ raise err.cause
86
+ rescue => err
87
+ Aikido::Zen.config.logger.debug("[safe] #{err.class}: #{err.message}")
88
+ end
89
+
90
+ # Presafely execute the given block
91
+ #
92
+ # Safely wrap standard errors in `PresafeError` so that the original error is
93
+ # reraised when rescued in `safe`.
94
+ #
95
+ # @yield the block to execute
96
+ def presafe
97
+ yield
98
+ rescue => err
99
+ raise PresafeError, cause: err
100
+ end
101
+
102
+ # Define a method `method_name` that presafely executes the given block before
103
+ # the original method.
104
+ #
105
+ # @param method_name [Symbol, String] the name of the method to define
106
+ # @yield the block to execute before the original method
107
+ # @yieldparam args [Array] the positional arguments passed to the original method
108
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
109
+ #
110
+ # @return [void]
111
+ def presafe_sink_before(method_name, &block)
112
+ define_method(method_name) do |*args, **kwargs, &blk|
113
+ instance_exec(*args, **kwargs, &block)
114
+ super(*args, **kwargs, &blk)
115
+ end
116
+ end
117
+
118
+ # Define a method `method_name` that safely executes the given block before
119
+ # the original method.
120
+ #
121
+ # @param method_name [Symbol, String] the name of the method to define
122
+ # @yield the block to execute before the original method
123
+ # @yieldparam args [Array] the positional arguments passed to the original method
124
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
125
+ #
126
+ # @return [void]
127
+ #
128
+ # @note the block is executed within `safe` to handle errors safely; the original method is executed outside of `safe` to preserve the original behavior
129
+ def sink_before(method_name, &block)
130
+ presafe_sink_before(method_name) do |*args, **kwargs|
131
+ DSL.safe do
132
+ instance_exec(*args, **kwargs, &block)
133
+ end
134
+ end
135
+ end
136
+
137
+ # Define a method `method_name` that presafely executes the given block after
138
+ # the original method.
139
+ #
140
+ # @param method_name [Symbol, String] the name of the method to define
141
+ # @yield the block to execute after the original method
142
+ # @yieldparam result [Object] the result returned by the original method
143
+ # @yieldparam args [Array] the positional arguments passed to the original method
144
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
145
+ #
146
+ # @return [void]
147
+ def presafe_sink_after(method_name, &block)
148
+ define_method(method_name) do |*args, **kwargs, &blk|
149
+ result = super(*args, **kwargs, &blk)
150
+ instance_exec(result, *args, **kwargs, &block)
151
+ result
152
+ end
153
+ end
154
+
155
+ # Define a method `method_name` that safely executes the given block after
156
+ # the original method.
157
+ #
158
+ # @param method_name [Symbol, String] the name of the method to define
159
+ # @yield the block to execute after the original method
160
+ # @yieldparam result [Object] the result returned by the original method
161
+ # @yieldparam args [Array] the positional arguments passed to the original method
162
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
163
+ #
164
+ # @return [void]
165
+ #
166
+ # @note the block is executed within `safe` to handle errors safely; the original method is executed outside of `safe` to preserve the original behavior
167
+ def sink_after(method_name, &block)
168
+ presafe_sink_after(method_name) do |result, *args, **kwargs|
169
+ DSL.safe do
170
+ instance_exec(result, *args, **kwargs, &block)
171
+ end
172
+ end
173
+ end
174
+
175
+ # Define a method `method_name` that presafely executes the given block around
176
+ # the original method.
177
+ #
178
+ # @param method_name [Symbol, String] the name of the method to define
179
+ # @yield the block to execute around the original method
180
+ # @yieldparam super_call [Proc] the proc that calls the original method
181
+ # @yieldparam args [Array] the positional arguments passed to the original method
182
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
183
+ #
184
+ # @return [void]
185
+ def presafe_sink_around(method_name, &block)
186
+ define_method(method_name) do |*args, **kwargs, &blk|
187
+ result = nil
188
+ super_call = proc do
189
+ result = super(*args, **kwargs, &blk)
190
+ end
191
+ instance_exec(super_call, *args, **kwargs, &block)
192
+ result
193
+ end
194
+ end
195
+
196
+ # Define a method `method_name` that safely executes the given block around
197
+ # the original method.
198
+ #
199
+ # @param method_name [Symbol, String] the name of the method to define
200
+ # @yield the block to execute around the original method
201
+ # @yieldparam super_call [Proc] the proc that calls the original method
202
+ # @yieldparam args [Array] the positional arguments passed to the original method
203
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
204
+ #
205
+ # @return [void]
206
+ #
207
+ # @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
209
+ 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
214
+ DSL.presafe do
215
+ presafe_super_call.call
216
+ end
217
+ end
218
+ DSL.safe do
219
+ instance_exec(super_call, *args, **kwargs, &block)
220
+ end
221
+ presafe_super_call.call unless super_called
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Aikido
4
4
  module Zen
5
- VERSION = "1.0.0-beta.1"
5
+ VERSION = "1.0.1.beta.3"
6
6
 
7
7
  # The version of libzen_internals that we build against.
8
- LIBZEN_VERSION = "0.1.37"
8
+ LIBZEN_VERSION = "0.1.39"
9
9
  end
10
10
  end
data/lib/aikido/zen.rb CHANGED
@@ -1,5 +1,8 @@
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
+
3
6
  require_relative "zen/version"
4
7
  require_relative "zen/errors"
5
8
  require_relative "zen/actor"
@@ -20,10 +23,48 @@ require_relative "zen/outbound_connection_monitor"
20
23
  require_relative "zen/runtime_settings"
21
24
  require_relative "zen/rate_limiter"
22
25
  require_relative "zen/scanners"
23
- require_relative "zen/rails_engine" if defined?(::Rails)
24
26
 
25
27
  module Aikido
26
28
  module Zen
29
+ # Enable protection. Until this method is called no sinks are loaded
30
+ # and the Aikido Agent does not start.
31
+ #
32
+ # @return [void]
33
+ def self.protect!
34
+ if config.disabled?
35
+ config.logger.warn("Zen has been disabled and will not run.")
36
+ return
37
+ end
38
+
39
+ # IMPORTANT: Any files that load sinks or start the Aikido Agent
40
+ # should be required here only.
41
+
42
+ if Aikido::Zen.satisfy "rails", ">= 7.0"
43
+ require_relative "zen/rails_engine"
44
+ end
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
49
+ end
50
+
51
+ # @!visibility private
52
+ # Returns whether the loaded gem specification satisfies the listed requirements.
53
+ #
54
+ # Returns false if the gem specification is not loaded.
55
+ #
56
+ # @param name [String] the gem name
57
+ # @param requirements [Array<String>] a variable number of gem requirement strings
58
+ #
59
+ # @return [Boolean] true if the gem specification is loaded and all gem requirements are satisfied
60
+ def self.satisfy(name, *requirements)
61
+ spec = Gem.loaded_specs[name]
62
+
63
+ return false if spec.nil?
64
+
65
+ Gem::Requirement.new(*requirements).satisfied_by?(spec.version)
66
+ end
67
+
27
68
  # @return [Aikido::Zen::Config] the agent configuration.
28
69
  def self.config
29
70
  @config ||= Config.new
@@ -0,0 +1,4 @@
1
+ LICENSE
2
+ *.gemspec
3
+ /lib/*.rb
4
+ *.gem
@@ -0,0 +1,11 @@
1
+ # Security Placeholder
2
+
3
+ This gem has been published by [Aikido Security](https://aikido.dev) to help prevent supply chain attacks and protect the integrity of the Ruby ecosystem.
4
+
5
+ It is **not intended for direct use** and contains no functional code.
6
+
7
+ If you are looking for the actual library, please use [`aikido-zen`](https://rubygems.org/gems/aikido-zen).
8
+
9
+ ---
10
+
11
+ This package exists solely for security purposes. For more information, visit [aikido.dev](https://aikido.dev).