aikido-zen 1.0.1.beta.5-arm64-linux → 1.0.2-arm64-linux

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +6 -0
  3. data/README.md +2 -0
  4. data/benchmarks/README.md +0 -1
  5. data/benchmarks/rails7.1_benchmark.js +1 -0
  6. data/benchmarks/rails7.1_sql_injection.js +52 -20
  7. data/docs/config.md +9 -1
  8. data/docs/proxy.md +10 -0
  9. data/docs/rails.md +55 -13
  10. data/docs/troubleshooting.md +62 -0
  11. data/lib/aikido/zen/actor.rb +34 -4
  12. data/lib/aikido/zen/agent/heartbeats_manager.rb +5 -5
  13. data/lib/aikido/zen/agent.rb +19 -17
  14. data/lib/aikido/zen/attack.rb +19 -9
  15. data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
  16. data/lib/aikido/zen/attack_wave.rb +88 -0
  17. data/lib/aikido/zen/cache.rb +91 -0
  18. data/lib/aikido/zen/capped_collections.rb +22 -4
  19. data/lib/aikido/zen/collector/event.rb +238 -0
  20. data/lib/aikido/zen/collector/hosts.rb +16 -1
  21. data/lib/aikido/zen/collector/routes.rb +13 -8
  22. data/lib/aikido/zen/collector/stats.rb +33 -22
  23. data/lib/aikido/zen/collector/users.rb +5 -3
  24. data/lib/aikido/zen/collector.rb +107 -28
  25. data/lib/aikido/zen/config.rb +54 -21
  26. data/lib/aikido/zen/context/rack_request.rb +3 -0
  27. data/lib/aikido/zen/context/rails_request.rb +3 -0
  28. data/lib/aikido/zen/context.rb +42 -9
  29. data/lib/aikido/zen/detached_agent/agent.rb +28 -27
  30. data/lib/aikido/zen/detached_agent/front_object.rb +10 -6
  31. data/lib/aikido/zen/detached_agent/server.rb +63 -26
  32. data/lib/aikido/zen/event.rb +47 -2
  33. data/lib/aikido/zen/helpers.rb +24 -0
  34. data/lib/aikido/zen/internals.rb +23 -3
  35. data/lib/aikido/zen/libzen-v0.1.48-arm64-linux.so +0 -0
  36. data/lib/aikido/zen/middleware/{check_allowed_addresses.rb → allowed_address_checker.rb} +1 -1
  37. data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
  38. data/lib/aikido/zen/middleware/{set_context.rb → context_setter.rb} +1 -1
  39. data/lib/aikido/zen/middleware/fork_detector.rb +23 -0
  40. data/lib/aikido/zen/middleware/rack_throttler.rb +3 -1
  41. data/lib/aikido/zen/middleware/request_tracker.rb +9 -4
  42. data/lib/aikido/zen/outbound_connection.rb +18 -1
  43. data/lib/aikido/zen/payload.rb +1 -1
  44. data/lib/aikido/zen/rails_engine.rb +5 -8
  45. data/lib/aikido/zen/request/rails_router.rb +17 -2
  46. data/lib/aikido/zen/request.rb +21 -36
  47. data/lib/aikido/zen/route.rb +57 -0
  48. data/lib/aikido/zen/runtime_settings/endpoints.rb +37 -8
  49. data/lib/aikido/zen/runtime_settings.rb +6 -5
  50. data/lib/aikido/zen/scanners/path_traversal/helpers.rb +10 -7
  51. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +5 -4
  52. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +3 -2
  53. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +3 -2
  54. data/lib/aikido/zen/scanners/ssrf_scanner.rb +2 -1
  55. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +8 -2
  56. data/lib/aikido/zen/sink.rb +1 -1
  57. data/lib/aikido/zen/sinks/action_controller.rb +3 -1
  58. data/lib/aikido/zen/sinks/file.rb +45 -4
  59. data/lib/aikido/zen/sinks/kernel.rb +1 -1
  60. data/lib/aikido/zen/sinks/socket.rb +7 -0
  61. data/lib/aikido/zen/sinks_dsl.rb +12 -0
  62. data/lib/aikido/zen/system_info.rb +1 -5
  63. data/lib/aikido/zen/version.rb +2 -2
  64. data/lib/aikido/zen.rb +77 -15
  65. data/tasklib/bench.rake +1 -1
  66. data/tasklib/libzen.rake +1 -0
  67. metadata +15 -5
  68. data/lib/aikido/zen/libzen-v0.1.39-arm64-linux.so +0 -0
@@ -18,11 +18,12 @@ module Aikido::Zen
18
18
  ::File.singleton_class.class_eval do
19
19
  extend Sinks::DSL
20
20
 
21
- # Create a copy of the original method for internal use only to prevent
21
+ # Create a copy of the original methods for internal use only to prevent
22
22
  # recursion in PathTraversalScanner.
23
23
  #
24
- # IMPORTANT: The alias must be created before the method is overridden.
24
+ # IMPORTANT: The aliases must be created before the method is overridden.
25
25
  alias_method :expand_path__internal_for_aikido_zen, :expand_path
26
+ alias_method :join__internal_for_aikido_zen, :join
26
27
 
27
28
  sink_before :open do |path|
28
29
  Helpers.scan(path, "open")
@@ -80,8 +81,48 @@ module Aikido::Zen
80
81
  end
81
82
  end
82
83
 
83
- sink_after :join do |result|
84
- Helpers.scan(result, "join")
84
+ def join(*args, **kwargs, &blk)
85
+ if Aikido::Zen.config.harden?
86
+ # IMPORTANT: THE BEHAVIOR OF THIS METHOD IS CHANGED!
87
+ #
88
+ # File.join has undocumented behavior:
89
+ #
90
+ # File.join recursively joins nested string arrays.
91
+ #
92
+ # This prevents path traversal detection when an array originates
93
+ # from user input that was assumed to be a string.
94
+ #
95
+ # This undocumented behavior has been restricted to support path
96
+ # traversal detection.
97
+ #
98
+ # File.join no longer joins nested string arrays, but still accepts
99
+ # a single string array argument.
100
+
101
+ # File.join is often incorrectly called with a single array argument.
102
+ #
103
+ # i.e.
104
+ #
105
+ # File.join(["prefix", "filename"])
106
+ #
107
+ # This is considered acceptable.
108
+ #
109
+ # Calling File.join with a single string argument returns the string
110
+ # argument itself, having no practical effect. Therefore, it can be
111
+ # presumed that if File.join is called with a single array argument
112
+ # then this was its intended usage, and the array did not originate
113
+ # from user input that was assumed to be a string.
114
+ strings = args
115
+ strings = args.first if args.size == 1 && args.first.is_a?(Array)
116
+ strings.each do |string|
117
+ raise TypeError.new("Zen prevented implicit conversion of Array to String in hardened method. Visit https://github.com/AikidoSec/firewall-ruby for more information.") if string.is_a?(Array)
118
+ end
119
+ end
120
+
121
+ result = join__internal_for_aikido_zen(*args, **kwargs, &blk)
122
+ Sinks::DSL.safe do
123
+ Helpers.scan(result, "join")
124
+ end
125
+ result
85
126
  end
86
127
 
87
128
  sink_before :expand_path do |file_name|
@@ -16,7 +16,7 @@ module Aikido::Zen
16
16
  klass.class_eval do
17
17
  extend Sinks::DSL
18
18
 
19
- %i[system spawn].each do |method_name|
19
+ %i[system spawn `].each do |method_name|
20
20
  sink_before method_name do |*args|
21
21
  # Remove the optional environment argument before the command-line.
22
22
  args.shift if args.first.is_a?(Hash)
@@ -68,6 +68,13 @@ module Aikido::Zen
68
68
  # :nocov:
69
69
  Helpers.scan(remote_host, socket, "open")
70
70
  # :nocov:
71
+ rescue Aikido::Zen::UnderAttackError, Aikido::Zen::Sinks::DSL::PresafeError
72
+ # If the scan raises an exception that will escape the safe block,
73
+ # the open socket must be closed because it will not be returned,
74
+ # so the user cannot close it.
75
+ socket.close
76
+
77
+ raise
71
78
  end
72
79
  end
73
80
  end
@@ -109,6 +109,8 @@ module Aikido::Zen
109
109
  #
110
110
  # @return [void]
111
111
  def presafe_sink_before(method_name, &block)
112
+ raise ArgumentError, "block required" unless block
113
+
112
114
  original = instance_method(method_name)
113
115
 
114
116
  define_method(method_name) do |*args, **kwargs, &blk|
@@ -131,6 +133,8 @@ module Aikido::Zen
131
133
  #
132
134
  # @note the block is executed within `safe` to handle errors safely; the original method is executed outside of `safe` to preserve the original behavior
133
135
  def sink_before(method_name, &block)
136
+ raise ArgumentError, "block required" unless block
137
+
134
138
  presafe_sink_before(method_name) do |*args, **kwargs|
135
139
  DSL.safe do
136
140
  instance_exec(*args, **kwargs, &block)
@@ -149,6 +153,8 @@ module Aikido::Zen
149
153
  #
150
154
  # @return [void]
151
155
  def presafe_sink_after(method_name, &block)
156
+ raise ArgumentError, "block required" unless block
157
+
152
158
  original = instance_method(method_name)
153
159
 
154
160
  define_method(method_name) do |*args, **kwargs, &blk|
@@ -173,6 +179,8 @@ module Aikido::Zen
173
179
  #
174
180
  # @note the block is executed within `safe` to handle errors safely; the original method is executed outside of `safe` to preserve the original behavior
175
181
  def sink_after(method_name, &block)
182
+ raise ArgumentError, "block required" unless block
183
+
176
184
  presafe_sink_after(method_name) do |result, *args, **kwargs|
177
185
  DSL.safe do
178
186
  instance_exec(result, *args, **kwargs, &block)
@@ -191,6 +199,8 @@ module Aikido::Zen
191
199
  #
192
200
  # @return [void]
193
201
  def presafe_sink_around(method_name, &block)
202
+ raise ArgumentError, "block required" unless block
203
+
194
204
  original = instance_method(method_name)
195
205
 
196
206
  define_method(method_name) do |*args, **kwargs, &blk|
@@ -219,6 +229,8 @@ module Aikido::Zen
219
229
  # @note the block is executed within `safe` to handle errors safely; the original method is executed within `presafe` to preserve the original behavior
220
230
  # @note if the block does not call `original_call`, the original method is called automatically after the block is executed
221
231
  def sink_around(method_name, &block)
232
+ raise ArgumentError, "block required" unless block
233
+
222
234
  presafe_sink_around(method_name) do |presafe_original_call, *args, **kwargs|
223
235
  original_called = false
224
236
  original_call = proc do
@@ -8,12 +8,8 @@ require_relative "package"
8
8
  module Aikido::Zen
9
9
  # Provides information about the currently running Agent.
10
10
  class SystemInfo
11
- def initialize(config = Aikido::Zen.config)
12
- @config = config
13
- end
14
-
15
11
  def attacks_block_requests?
16
- !!@config.blocking_mode
12
+ !!Aikido::Zen.blocking_mode?
17
13
  end
18
14
 
19
15
  def attacks_are_only_reported?
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Aikido
4
4
  module Zen
5
- VERSION = "1.0.1.beta.5"
5
+ VERSION = "1.0.2"
6
6
 
7
7
  # The version of libzen_internals that we build against.
8
- LIBZEN_VERSION = "0.1.39"
8
+ LIBZEN_VERSION = "0.1.48"
9
9
  end
10
10
  end
data/lib/aikido/zen.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "zen/helpers"
3
4
  require_relative "zen/version"
4
5
  require_relative "zen/errors"
5
6
  require_relative "zen/actor"
@@ -11,14 +12,17 @@ require_relative "zen/agent"
11
12
  require_relative "zen/api_client"
12
13
  require_relative "zen/context"
13
14
  require_relative "zen/detached_agent"
14
- require_relative "zen/middleware/check_allowed_addresses"
15
15
  require_relative "zen/middleware/middleware"
16
+ require_relative "zen/middleware/fork_detector"
17
+ require_relative "zen/middleware/context_setter"
18
+ require_relative "zen/middleware/allowed_address_checker"
19
+ require_relative "zen/middleware/attack_wave_protector"
16
20
  require_relative "zen/middleware/request_tracker"
17
- require_relative "zen/middleware/set_context"
18
21
  require_relative "zen/outbound_connection"
19
22
  require_relative "zen/outbound_connection_monitor"
20
23
  require_relative "zen/runtime_settings"
21
24
  require_relative "zen/rate_limiter"
25
+ require_relative "zen/attack_wave"
22
26
  require_relative "zen/scanners"
23
27
 
24
28
  module Aikido
@@ -36,8 +40,6 @@ module Aikido
36
40
  return
37
41
  end
38
42
 
39
- return unless config.protect?
40
-
41
43
  unless load_sources! && load_sinks!
42
44
  config.logger.warn("Zen could not find any supported libraries or frameworks. Visit https://github.com/AikidoSec/firewall-ruby for more information.")
43
45
  return
@@ -78,6 +80,16 @@ module Aikido
78
80
  @runtime_settings = settings
79
81
  end
80
82
 
83
+ # @return [Boolean] whether the Aikido agent is currently blocking requests.
84
+ # Blocking mode is configured at startup and can be controlled through the
85
+ # Aikido dashboard at runtime.
86
+ def self.blocking_mode?
87
+ blocking_mode = runtime_settings.blocking_mode
88
+ return blocking_mode unless blocking_mode.nil?
89
+
90
+ config.blocking_mode
91
+ end
92
+
81
93
  # Gets information about the current system configuration, which is sent to
82
94
  # the server along with any events.
83
95
  def self.system_info
@@ -87,15 +99,9 @@ module Aikido
87
99
  # Manages runtime metrics extracted from your app, which are uploaded to the
88
100
  # Aikido servers if configured to do so.
89
101
  def self.collector
90
- check_and_handle_fork
91
102
  @collector ||= Collector.new
92
103
  end
93
104
 
94
- def self.detached_agent
95
- check_and_handle_fork
96
- @detached_agent ||= DetachedAgent::Agent.new
97
- end
98
-
99
105
  # Gets the current context object that holds all information about the
100
106
  # current request.
101
107
  #
@@ -121,6 +127,18 @@ module Aikido
121
127
  collector.track_request
122
128
  end
123
129
 
130
+ # Track statistics about an attack wave the app is handling.
131
+ #
132
+ # @param attack_wave [Aikido::Zen::Events::AttackWave]
133
+ # @return [void]
134
+ def self.track_attack_wave(attack_wave)
135
+ collector.track_attack_wave(being_blocked: false)
136
+ end
137
+
138
+ # Track statistics about a route that the app has discovered.
139
+ #
140
+ # @param request [Aikido::Zen::Request]
141
+ # @return [void]
124
142
  def self.track_discovered_route(request)
125
143
  collector.track_route(request)
126
144
  end
@@ -165,12 +183,22 @@ module Aikido
165
183
  end
166
184
  end
167
185
 
186
+ # Align with other Zen implementations, while keeping internal consistency.
187
+ class << self
188
+ alias_method :set_user, :track_user
189
+ end
190
+
168
191
  # Marks that the Zen middleware was installed properly
169
192
  # @return void
170
193
  def self.middleware_installed!
171
194
  collector.middleware_installed!
172
195
  end
173
196
 
197
+ # @return [Aikido::Zen::AttackWave::Detector] the attack wave detector.
198
+ def self.attack_wave_detector
199
+ @attack_wave_detector ||= AttackWave::Detector.new
200
+ end
201
+
174
202
  # @!visibility private
175
203
  # Load all sources.
176
204
  #
@@ -208,8 +236,12 @@ module Aikido
208
236
  @agent ||= Agent.start
209
237
  end
210
238
 
239
+ def self.detached_agent
240
+ @detached_agent ||= DetachedAgent::Agent.new
241
+ end
242
+
211
243
  def self.detached_agent_server
212
- @detached_agent_server ||= DetachedAgent::Server.start!
244
+ @detached_agent_server ||= DetachedAgent::Server.start
213
245
  end
214
246
 
215
247
  class << self
@@ -218,24 +250,54 @@ module Aikido
218
250
  LOCK = Mutex.new
219
251
 
220
252
  def start!
253
+ return unless start?
254
+
221
255
  @pid = Process.pid
256
+
222
257
  LOCK.synchronize do
223
258
  agent
224
259
  detached_agent_server
225
260
  end
226
261
  end
227
262
 
263
+ def start?
264
+ !config.api_token.nil? ||
265
+ config.blocking_mode? ||
266
+ config.debugging?
267
+ end
268
+
228
269
  def check_and_handle_fork
229
- if has_forked
230
- @detached_agent&.handle_fork
231
- end
270
+ handle_fork if forked?
232
271
  end
233
272
 
234
- def has_forked
273
+ def forked?
235
274
  pid_changed = Process.pid != @pid
236
275
  @pid = Process.pid
237
276
  pid_changed
238
277
  end
278
+
279
+ def handle_fork
280
+ @detached_agent&.handle_fork
281
+ end
282
+ end
283
+
284
+ # @!visibility private
285
+ # Returns the stack trace trimmed to where execution last entered Zen.
286
+ #
287
+ # @return [String]
288
+ def self.clean_stack_trace
289
+ stack_trace = caller_locations
290
+
291
+ spec = Gem.loaded_specs["aikido-zen"]
292
+
293
+ # Only trim stack frames from .../lib/aikido/zen/ in the aikido-zen gem,
294
+ # so calls in sample apps are preserved.
295
+ lib_path_start = File.expand_path(File.join(spec.full_gem_path, "lib", "aikido", "zen")) + File::SEPARATOR
296
+
297
+ index = stack_trace.index { |frame| !File.expand_path(frame.path).start_with?(lib_path_start) }
298
+ stack_trace = stack_trace[index..] if index
299
+
300
+ stack_trace.map(&:to_s).join("\n")
239
301
  end
240
302
  end
241
303
  end
data/tasklib/bench.rake CHANGED
@@ -87,7 +87,7 @@ Pathname.glob("sample_apps/*").select(&:directory?).each do |dir|
87
87
  end
88
88
 
89
89
  task :boot_unprotected_app do
90
- boot_server(dir, port: PORT_UNPROTECTED, env: {"AIKIDO_DISABLED" => "true"})
90
+ boot_server(dir, port: PORT_UNPROTECTED, env: {"AIKIDO_DISABLE" => "true"})
91
91
  end
92
92
  end
93
93
  end
data/tasklib/libzen.rake CHANGED
@@ -76,6 +76,7 @@ LIBZENS = [
76
76
  LibZen.new("arm64-darwin.dylib", "libzen_internals_aarch64-apple-darwin.dylib"),
77
77
  LibZen.new("arm64-linux.so", "libzen_internals_aarch64-unknown-linux-gnu.so"),
78
78
  LibZen.new("arm64-linux-musl.so", "libzen_internals_aarch64-unknown-linux-musl.so"),
79
+ LibZen.new("aarch64-linux.so", "libzen_internals_aarch64-unknown-linux-gnu.so"),
79
80
  LibZen.new("x86_64-darwin.dylib", "libzen_internals_x86_64-apple-darwin.dylib"),
80
81
  LibZen.new("x86_64-linux.so", "libzen_internals_x86_64-unknown-linux-gnu.so"),
81
82
  LibZen.new("x86_64-linux-musl.so", "libzen_internals_x86_64-unknown-linux-musl.so"),
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.5
4
+ version: 1.0.2
5
5
  platform: arm64-linux
6
6
  authors:
7
7
  - Aikido Security
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-19 00:00:00.000000000 Z
11
+ date: 2025-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -71,10 +71,13 @@ files:
71
71
  - README.md
72
72
  - Rakefile
73
73
  - benchmarks/README.md
74
+ - benchmarks/rails7.1_benchmark.js
74
75
  - benchmarks/rails7.1_sql_injection.js
75
76
  - docs/banner.svg
76
77
  - docs/config.md
78
+ - docs/proxy.md
77
79
  - docs/rails.md
80
+ - docs/troubleshooting.md
78
81
  - lib/aikido-zen.rb
79
82
  - lib/aikido/zen.rb
80
83
  - lib/aikido/zen/actor.rb
@@ -82,9 +85,13 @@ files:
82
85
  - lib/aikido/zen/agent/heartbeats_manager.rb
83
86
  - lib/aikido/zen/api_client.rb
84
87
  - lib/aikido/zen/attack.rb
88
+ - lib/aikido/zen/attack_wave.rb
89
+ - lib/aikido/zen/attack_wave/helpers.rb
85
90
  - lib/aikido/zen/background_worker.rb
91
+ - lib/aikido/zen/cache.rb
86
92
  - lib/aikido/zen/capped_collections.rb
87
93
  - lib/aikido/zen/collector.rb
94
+ - lib/aikido/zen/collector/event.rb
88
95
  - lib/aikido/zen/collector/hosts.rb
89
96
  - lib/aikido/zen/collector/routes.rb
90
97
  - lib/aikido/zen/collector/sink_stats.rb
@@ -100,13 +107,16 @@ files:
100
107
  - lib/aikido/zen/detached_agent/server.rb
101
108
  - lib/aikido/zen/errors.rb
102
109
  - lib/aikido/zen/event.rb
110
+ - lib/aikido/zen/helpers.rb
103
111
  - lib/aikido/zen/internals.rb
104
- - lib/aikido/zen/libzen-v0.1.39-arm64-linux.so
105
- - lib/aikido/zen/middleware/check_allowed_addresses.rb
112
+ - lib/aikido/zen/libzen-v0.1.48-arm64-linux.so
113
+ - lib/aikido/zen/middleware/allowed_address_checker.rb
114
+ - lib/aikido/zen/middleware/attack_wave_protector.rb
115
+ - lib/aikido/zen/middleware/context_setter.rb
116
+ - lib/aikido/zen/middleware/fork_detector.rb
106
117
  - lib/aikido/zen/middleware/middleware.rb
107
118
  - lib/aikido/zen/middleware/rack_throttler.rb
108
119
  - lib/aikido/zen/middleware/request_tracker.rb
109
- - lib/aikido/zen/middleware/set_context.rb
110
120
  - lib/aikido/zen/outbound_connection.rb
111
121
  - lib/aikido/zen/outbound_connection_monitor.rb
112
122
  - lib/aikido/zen/package.rb