aikido-zen 1.0.2.beta.10 → 1.0.3

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/docs/config.md +1 -1
  4. data/docs/troubleshooting.md +62 -0
  5. data/lib/aikido/zen/agent.rb +2 -2
  6. data/lib/aikido/zen/attack.rb +8 -6
  7. data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
  8. data/lib/aikido/zen/attack_wave.rb +88 -0
  9. data/lib/aikido/zen/cache.rb +91 -0
  10. data/lib/aikido/zen/capped_collections.rb +22 -4
  11. data/lib/aikido/zen/collector/event.rb +29 -0
  12. data/lib/aikido/zen/collector/hosts.rb +16 -1
  13. data/lib/aikido/zen/collector/stats.rb +17 -3
  14. data/lib/aikido/zen/collector/users.rb +2 -2
  15. data/lib/aikido/zen/collector.rb +14 -0
  16. data/lib/aikido/zen/config.rb +29 -6
  17. data/lib/aikido/zen/context/rack_request.rb +3 -0
  18. data/lib/aikido/zen/context/rails_request.rb +3 -0
  19. data/lib/aikido/zen/context.rb +9 -10
  20. data/lib/aikido/zen/event.rb +47 -2
  21. data/lib/aikido/zen/helpers.rb +24 -0
  22. data/lib/aikido/zen/middleware/{check_allowed_addresses.rb → allowed_address_checker.rb} +1 -1
  23. data/lib/aikido/zen/middleware/attack_protector.rb +29 -0
  24. data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
  25. data/lib/aikido/zen/middleware/{set_context.rb → context_setter.rb} +1 -1
  26. data/lib/aikido/zen/middleware/rack_throttler.rb +3 -1
  27. data/lib/aikido/zen/middleware/request_tracker.rb +8 -3
  28. data/lib/aikido/zen/outbound_connection.rb +11 -1
  29. data/lib/aikido/zen/rails_engine.rb +4 -2
  30. data/lib/aikido/zen/request/rails_router.rb +17 -2
  31. data/lib/aikido/zen/request.rb +2 -36
  32. data/lib/aikido/zen/route.rb +50 -0
  33. data/lib/aikido/zen/runtime_settings/endpoints.rb +37 -8
  34. data/lib/aikido/zen/runtime_settings.rb +5 -4
  35. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +3 -2
  36. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +3 -2
  37. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +3 -2
  38. data/lib/aikido/zen/scanners/ssrf_scanner.rb +2 -1
  39. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +5 -1
  40. data/lib/aikido/zen/sinks/action_controller.rb +3 -1
  41. data/lib/aikido/zen/sinks/pg.rb +14 -5
  42. data/lib/aikido/zen/sinks/socket.rb +7 -0
  43. data/lib/aikido/zen/sinks.rb +1 -0
  44. data/lib/aikido/zen/system_info.rb +1 -5
  45. data/lib/aikido/zen/version.rb +1 -1
  46. data/lib/aikido/zen.rb +56 -6
  47. data/tasklib/bench.rake +1 -1
  48. metadata +11 -4
@@ -12,8 +12,10 @@ module Aikido::Zen
12
12
  initializer "aikido.add_middleware" do |app|
13
13
  app.middleware.insert_before 0, Aikido::Zen::Middleware::ForkDetector
14
14
 
15
- app.middleware.use Aikido::Zen::Middleware::SetContext
16
- app.middleware.use Aikido::Zen::Middleware::CheckAllowedAddresses
15
+ app.middleware.use Aikido::Zen::Middleware::ContextSetter
16
+ app.middleware.use Aikido::Zen::Middleware::AllowedAddressChecker
17
+ app.middleware.use Aikido::Zen::Middleware::AttackProtector
18
+ app.middleware.use Aikido::Zen::Middleware::AttackWaveProtector
17
19
  # Request Tracker stats do not consider failed request or 40x, so the middleware
18
20
  # must be the last one wrapping the request.
19
21
  app.middleware.use Aikido::Zen::Middleware::RequestTracker
@@ -62,16 +62,31 @@ module Aikido::Zen
62
62
  nil
63
63
  end
64
64
 
65
- private def build_route(route, request, prefix: request.script_name)
65
+ private
66
+
67
+ def build_route(route, request, prefix: request.script_name)
66
68
  route_wrapper = ActionDispatch::Routing::RouteWrapper.new(route)
67
69
 
68
70
  path = if prefix.present?
69
- File.join(prefix.to_s, route_wrapper.path).chomp("/")
71
+ prefix_route_path(prefix.to_s, route_wrapper.path)
70
72
  else
71
73
  route_wrapper.path
72
74
  end
73
75
 
74
76
  Aikido::Zen::Route.new(verb: request.request_method, path: path)
75
77
  end
78
+
79
+ def prefix_route_path(string1, string2)
80
+ # The strings appear to start with "/", allowing them to be concatenated
81
+ # directly after removing trailing "/". However, as it is not currently
82
+ # known whether this is guaranteed, we insert a separator when necessary.
83
+
84
+ separator = string2.start_with?("/") ? "" : "/"
85
+
86
+ string1 = string1.chomp("/")
87
+ string2 = string2.chomp("/")
88
+
89
+ "#{string1}#{separator}#{string2}"
90
+ end
76
91
  end
77
92
  end
@@ -22,13 +22,11 @@ module Aikido::Zen
22
22
  @config = config
23
23
  @framework = framework
24
24
  @router = router
25
- @body_read = false
26
25
  end
27
26
 
28
27
  def __setobj__(delegate) # :nodoc:
29
28
  super
30
- @body_read = false
31
- @route = @normalized_header = @truncated_body = nil
29
+ @route = @normalized_header = nil
32
30
  end
33
31
 
34
32
  # @return [Aikido::Zen::Route] the framework route being requested.
@@ -41,8 +39,6 @@ module Aikido::Zen
41
39
  @schema ||= Aikido::Zen::Request::Schema.build
42
40
  end
43
41
 
44
- # @api private
45
- #
46
42
  # @return [String] the IP address of the client making the request.
47
43
  def client_ip
48
44
  return @client_ip if @client_ip
@@ -74,42 +70,12 @@ module Aikido::Zen
74
70
  }
75
71
  end
76
72
 
77
- # @api private
78
- #
79
- # Reads the first 16KiB of the request body, to include in attack reports
80
- # back to the Aikido server. This method should only be called if an attack
81
- # is detected during the current request.
82
- #
83
- # If the underlying IO object has been partially (or fully) read before,
84
- # this will attempt to restore the previous cursor position after reading it
85
- # if possible, or leave if rewund if not.
86
- #
87
- # @param max_size [Integer] number of bytes to read at most.
88
- #
89
- # @return [String]
90
- def truncated_body(max_size: 16384)
91
- return @truncated_body if @body_read
92
- return nil if body.nil?
93
-
94
- begin
95
- initial_pos = body.pos if body.respond_to?(:pos)
96
- body.rewind
97
- @truncated_body = body.read(max_size)
98
- ensure
99
- @body_read = true
100
- body.rewind
101
- body.seek(initial_pos) if initial_pos && body.respond_to?(:seek)
102
- end
103
- end
104
-
105
73
  def as_json
106
74
  {
107
- method: request_method.downcase,
75
+ method: request_method.upcase,
108
76
  url: url,
109
77
  ipAddress: client_ip,
110
78
  userAgent: user_agent,
111
- headers: normalized_headers.reject { |_, val| val.to_s.empty? },
112
- body: truncated_body,
113
79
  source: framework,
114
80
  route: route&.path
115
81
  }
@@ -39,8 +39,58 @@ module Aikido::Zen
39
39
  [verb, path].hash
40
40
  end
41
41
 
42
+ # Sort routes by wildcard matching order deterministically:
43
+ #
44
+ # 1. Exact path before wildcard path
45
+ # 2. Fewer wildcards in path relative to path length
46
+ # 3. Earliest wildcard position in path
47
+ # 4. Exact verb before wildcard verb
48
+ # 5. Lexicographic path (tie-break)
49
+ # 6. Lexicographic verb (tie-break)
50
+ #
51
+ # @return [Array] the sort key
52
+ def sort_key
53
+ @sort_key ||= begin
54
+ stars = []
55
+ i = -1
56
+ while (i = path.index("*", i + 1))
57
+ stars << i
58
+ end
59
+
60
+ [
61
+ stars.empty? ? 0 : 1,
62
+ stars.length - path.length,
63
+ stars,
64
+ (verb == "*") ? 1 : 0,
65
+ path,
66
+ verb
67
+ ].freeze
68
+ end
69
+ end
70
+
71
+ def match?(other)
72
+ other.is_a?(Route) &&
73
+ pattern(verb).match?(other.verb) &&
74
+ pattern(path).match?(other.path)
75
+ end
76
+
42
77
  def inspect
43
78
  "#<#{self.class.name} #{verb} #{path.inspect}>"
44
79
  end
80
+
81
+ # Construct a regular expression equivalent to the wildcard string,
82
+ # where '*' is the wildcard operator.
83
+ #
84
+ # The resulting pattern matches the entire input, allows an optional
85
+ # trailing slash, and is case-insensitive.
86
+ #
87
+ # All other special characters in the regular expression are escaped
88
+ # so that they are treated literally.
89
+ #
90
+ # @param string [String] wildcard string
91
+ # @return [Regexp] regular expression matching the wildcard string
92
+ private def pattern(string)
93
+ /^#{Regexp.escape(string).gsub("\\*", ".*")}\/?$/i
94
+ end
45
95
  end
46
96
  end
@@ -16,24 +16,53 @@ module Aikido::Zen
16
16
  # @param data [Array<Hash>]
17
17
  # @return [Aikido::Zen::RuntimeSettings::Endpoints]
18
18
  def self.from_json(data)
19
- data = Array(data).map { |item|
20
- route = Route.new(verb: item["method"], path: item["route"])
21
- settings = RuntimeSettings::ProtectionSettings.from_json(item)
19
+ endpoint_pairs = Array(data).map do |value|
20
+ route = Route.new(verb: value["method"], path: value["route"])
21
+ settings = RuntimeSettings::ProtectionSettings.from_json(value)
22
22
  [route, settings]
23
- }.to_h
23
+ end
24
24
 
25
- new(data)
25
+ # Sort endpoints by wildcard matching order
26
+ endpoint_pairs.sort_by! do |route, settings|
27
+ route.sort_key
28
+ end
29
+
30
+ new(endpoint_pairs.to_h)
26
31
  end
27
32
 
28
- def initialize(data = {})
29
- @endpoints = data
33
+ # @param endpoints [Hash] the endpoints in wildcard matching order
34
+ # @return [Aikido::Zen::RuntimeSettings::Endpoints]
35
+ def initialize(endpoints = {})
36
+ @endpoints = endpoints
30
37
  @endpoints.default = RuntimeSettings::ProtectionSettings.none
31
38
  end
32
39
 
33
40
  # @param route [Aikido::Zen::Route]
34
41
  # @return [Aikido::Zen::RuntimeSettings::ProtectionSettings]
35
42
  def [](route)
36
- @endpoints[route]
43
+ return @endpoints[route] if @endpoints.key?(route)
44
+
45
+ # Wildcard endpoint matching
46
+
47
+ @endpoints.each do |pattern, settings|
48
+ return settings if pattern.match?(route)
49
+ end
50
+
51
+ @endpoints.default
52
+ end
53
+
54
+ # @param route [Aikido::Zen::Route]
55
+ # @return [Array<Aikido::Zen::RuntimeSettings::ProtectionSettings>]
56
+ def match(route)
57
+ matches = []
58
+
59
+ @endpoints.each do |pattern, settings|
60
+ matches << settings if pattern.match?(route)
61
+ end
62
+
63
+ matches << @endpoints.default if matches.empty?
64
+
65
+ matches
37
66
  end
38
67
 
39
68
  # @!visibility private
@@ -11,11 +11,11 @@ module Aikido::Zen
11
11
  #
12
12
  # You can subscribe to changes with +#add_observer(object, func_name)+, which
13
13
  # will call the function passing the settings as an argument.
14
- RuntimeSettings = Struct.new(:updated_at, :heartbeat_interval, :endpoints, :blocked_user_ids, :skip_protection_for_ips, :received_any_stats) do
14
+ RuntimeSettings = Struct.new(:updated_at, :heartbeat_interval, :endpoints, :blocked_user_ids, :allowed_ips, :received_any_stats, :blocking_mode) do
15
15
  def initialize(*)
16
16
  super
17
17
  self.endpoints ||= RuntimeSettings::Endpoints.new
18
- self.skip_protection_for_ips ||= RuntimeSettings::IPSet.new
18
+ self.allowed_ips ||= RuntimeSettings::IPSet.new
19
19
  end
20
20
 
21
21
  # @!attribute [rw] updated_at
@@ -35,7 +35,7 @@ module Aikido::Zen
35
35
  # @!attribute [rw] blocked_user_ids
36
36
  # @return [Array]
37
37
 
38
- # @!attribute [rw] skip_protection_for_ips
38
+ # @!attribute [rw] allowed_ips
39
39
  # @return [Aikido::Zen::RuntimeSettings::IPSet]
40
40
 
41
41
  # Parse and interpret the JSON response from the core API with updated
@@ -53,8 +53,9 @@ module Aikido::Zen
53
53
  self.heartbeat_interval = data["heartbeatIntervalInMS"].to_i / 1000
54
54
  self.endpoints = RuntimeSettings::Endpoints.from_json(data["endpoints"])
55
55
  self.blocked_user_ids = data["blockedUserIds"]
56
- self.skip_protection_for_ips = RuntimeSettings::IPSet.from_json(data["allowedIPAddresses"])
56
+ self.allowed_ips = RuntimeSettings::IPSet.from_json(data["allowedIPAddresses"])
57
57
  self.received_any_stats = data["receivedAnyStats"]
58
+ self.blocking_mode = data["block"]
58
59
 
59
60
  updated_at != last_updated_at
60
61
  end
@@ -21,14 +21,15 @@ module Aikido::Zen
21
21
  # user input is detected to be attempting a Path Traversal Attack, or +nil+ if not.
22
22
  def self.call(filepath:, sink:, context:, operation:)
23
23
  context.payloads.each do |payload|
24
- next unless new(filepath, payload.value).attack?
24
+ next unless new(filepath, payload.value.to_s).attack?
25
25
 
26
26
  return Attacks::PathTraversalAttack.new(
27
27
  sink: sink,
28
28
  input: payload,
29
29
  filepath: filepath,
30
30
  context: context,
31
- operation: "#{sink.operation}.#{operation}"
31
+ operation: "#{sink.operation}.#{operation}",
32
+ stack: Aikido::Zen.clean_stack_trace
32
33
  )
33
34
  end
34
35
 
@@ -16,14 +16,15 @@ module Aikido::Zen
16
16
  #
17
17
  def self.call(command:, sink:, context:, operation:)
18
18
  context.payloads.each do |payload|
19
- next unless new(command, payload.value).attack?
19
+ next unless new(command, payload.value.to_s).attack?
20
20
 
21
21
  return Attacks::ShellInjectionAttack.new(
22
22
  sink: sink,
23
23
  input: payload,
24
24
  command: command,
25
25
  context: context,
26
- operation: "#{sink.operation}.#{operation}"
26
+ operation: "#{sink.operation}.#{operation}",
27
+ stack: Aikido::Zen.clean_stack_trace
27
28
  )
28
29
  end
29
30
 
@@ -32,7 +32,7 @@ module Aikido::Zen
32
32
  end
33
33
 
34
34
  context.payloads.each do |payload|
35
- next unless new(query, payload.value, dialect).attack?
35
+ next unless new(query, payload.value.to_s, dialect).attack?
36
36
 
37
37
  return Attacks::SQLInjectionAttack.new(
38
38
  sink: sink,
@@ -40,7 +40,8 @@ module Aikido::Zen
40
40
  input: payload,
41
41
  dialect: dialect,
42
42
  context: context,
43
- operation: "#{sink.operation}.#{operation}"
43
+ operation: "#{sink.operation}.#{operation}",
44
+ stack: Aikido::Zen.clean_stack_trace
44
45
  )
45
46
  end
46
47
 
@@ -51,7 +51,8 @@ module Aikido::Zen
51
51
  request: request,
52
52
  input: payload,
53
53
  context: context,
54
- operation: "#{sink.operation}.#{operation}"
54
+ operation: "#{sink.operation}.#{operation}",
55
+ stack: Aikido::Zen.clean_stack_trace
55
56
  )
56
57
 
57
58
  return attack
@@ -20,7 +20,8 @@ module Aikido::Zen
20
20
  address: offending_address,
21
21
  sink: sink,
22
22
  context: context,
23
- operation: "#{sink.operation}.#{operation}"
23
+ operation: "#{sink.operation}.#{operation}",
24
+ stack: Aikido::Zen.clean_stack_trace
24
25
  )
25
26
  end
26
27
 
@@ -44,6 +45,9 @@ module Aikido::Zen
44
45
 
45
46
  DANGEROUS_ADDRESSES = [
46
47
  IPAddr.new("169.254.169.254"),
48
+ IPAddr.new("100.100.100.200"),
49
+ IPAddr.new("::ffff:169.254.169.254"),
50
+ IPAddr.new("::ffff:100.100.100.200"),
47
51
  IPAddr.new("fd00:ec2::254")
48
52
  ]
49
53
  end
@@ -43,8 +43,10 @@ module Aikido::Zen
43
43
  end
44
44
 
45
45
  private def should_throttle?(request)
46
+ # Bypass rate limiting for allowed IPs
47
+ return false if @settings.allowed_ips.include?(request.ip)
48
+
46
49
  return false unless @settings.endpoints[request.route].rate_limiting.enabled?
47
- return false if @settings.skip_protection_for_ips.include?(request.ip)
48
50
 
49
51
  result = @detached_agent.calculate_rate_limits(request)
50
52
  return false unless result
@@ -42,9 +42,15 @@ module Aikido::Zen
42
42
  ::PG::Connection.class_eval do
43
43
  extend Sinks::DSL
44
44
 
45
- %i[
46
- send_query exec sync_exec async_exec
47
- send_query_params exec_params sync_exec_params async_exec_params
45
+ [
46
+ :send_query,
47
+ :exec,
48
+ :sync_exec,
49
+ :async_exec,
50
+ :send_query_params,
51
+ :exec_params,
52
+ :sync_exec_params,
53
+ :async_exec_params
48
54
  ].each do |method_name|
49
55
  presafe_sink_before method_name do |query|
50
56
  Helpers.safe do
@@ -53,8 +59,11 @@ module Aikido::Zen
53
59
  end
54
60
  end
55
61
 
56
- %i[
57
- send_prepare prepare async_prepare sync_prepare
62
+ [
63
+ :send_prepare,
64
+ :prepare,
65
+ :async_prepare,
66
+ :sync_prepare
58
67
  ].each do |method_name|
59
68
  presafe_sink_before method_name do |_, query|
60
69
  Helpers.safe do
@@ -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
@@ -28,6 +28,7 @@ require_relative "sinks/patron"
28
28
  require_relative "sinks/typhoeus" if defined?(::Typhoeus)
29
29
  require_relative "sinks/async_http"
30
30
  require_relative "sinks/em_http"
31
+
31
32
  require_relative "sinks/mysql2"
32
33
  require_relative "sinks/pg"
33
34
  require_relative "sinks/sqlite3"
@@ -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,7 +2,7 @@
2
2
 
3
3
  module Aikido
4
4
  module Zen
5
- VERSION = "1.0.2.beta.10"
5
+ VERSION = "1.0.3"
6
6
 
7
7
  # The version of libzen_internals that we build against.
8
8
  LIBZEN_VERSION = "0.1.48"
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"
@@ -13,13 +14,16 @@ require_relative "zen/context"
13
14
  require_relative "zen/detached_agent"
14
15
  require_relative "zen/middleware/middleware"
15
16
  require_relative "zen/middleware/fork_detector"
16
- require_relative "zen/middleware/set_context"
17
- require_relative "zen/middleware/check_allowed_addresses"
17
+ require_relative "zen/middleware/context_setter"
18
+ require_relative "zen/middleware/allowed_address_checker"
19
+ require_relative "zen/middleware/attack_protector"
20
+ require_relative "zen/middleware/attack_wave_protector"
18
21
  require_relative "zen/middleware/request_tracker"
19
22
  require_relative "zen/outbound_connection"
20
23
  require_relative "zen/outbound_connection_monitor"
21
24
  require_relative "zen/runtime_settings"
22
25
  require_relative "zen/rate_limiter"
26
+ require_relative "zen/attack_wave"
23
27
  require_relative "zen/scanners"
24
28
 
25
29
  module Aikido
@@ -77,6 +81,16 @@ module Aikido
77
81
  @runtime_settings = settings
78
82
  end
79
83
 
84
+ # @return [Boolean] whether the Aikido agent is currently blocking requests.
85
+ # Blocking mode is configured at startup and can be controlled through the
86
+ # Aikido dashboard at runtime.
87
+ def self.blocking_mode?
88
+ blocking_mode = runtime_settings.blocking_mode
89
+ return blocking_mode unless blocking_mode.nil?
90
+
91
+ config.blocking_mode
92
+ end
93
+
80
94
  # Gets information about the current system configuration, which is sent to
81
95
  # the server along with any events.
82
96
  def self.system_info
@@ -89,10 +103,6 @@ module Aikido
89
103
  @collector ||= Collector.new
90
104
  end
91
105
 
92
- def self.detached_agent
93
- @detached_agent ||= DetachedAgent::Agent.new
94
- end
95
-
96
106
  # Gets the current context object that holds all information about the
97
107
  # current request.
98
108
  #
@@ -118,6 +128,18 @@ module Aikido
118
128
  collector.track_request
119
129
  end
120
130
 
131
+ # Track statistics about an attack wave the app is handling.
132
+ #
133
+ # @param attack_wave [Aikido::Zen::Events::AttackWave]
134
+ # @return [void]
135
+ def self.track_attack_wave(attack_wave)
136
+ collector.track_attack_wave(being_blocked: false)
137
+ end
138
+
139
+ # Track statistics about a route that the app has discovered.
140
+ #
141
+ # @param request [Aikido::Zen::Request]
142
+ # @return [void]
121
143
  def self.track_discovered_route(request)
122
144
  collector.track_route(request)
123
145
  end
@@ -173,6 +195,11 @@ module Aikido
173
195
  collector.middleware_installed!
174
196
  end
175
197
 
198
+ # @return [Aikido::Zen::AttackWave::Detector] the attack wave detector.
199
+ def self.attack_wave_detector
200
+ @attack_wave_detector ||= AttackWave::Detector.new
201
+ end
202
+
176
203
  # @!visibility private
177
204
  # Load all sources.
178
205
  #
@@ -210,6 +237,10 @@ module Aikido
210
237
  @agent ||= Agent.start
211
238
  end
212
239
 
240
+ def self.detached_agent
241
+ @detached_agent ||= DetachedAgent::Agent.new
242
+ end
243
+
213
244
  def self.detached_agent_server
214
245
  @detached_agent_server ||= DetachedAgent::Server.start
215
246
  end
@@ -250,5 +281,24 @@ module Aikido
250
281
  @detached_agent&.handle_fork
251
282
  end
252
283
  end
284
+
285
+ # @!visibility private
286
+ # Returns the stack trace trimmed to where execution last entered Zen.
287
+ #
288
+ # @return [String]
289
+ def self.clean_stack_trace
290
+ stack_trace = caller_locations
291
+
292
+ spec = Gem.loaded_specs["aikido-zen"]
293
+
294
+ # Only trim stack frames from .../lib/aikido/zen/ in the aikido-zen gem,
295
+ # so calls in sample apps are preserved.
296
+ lib_path_start = File.expand_path(File.join(spec.full_gem_path, "lib", "aikido", "zen")) + File::SEPARATOR
297
+
298
+ index = stack_trace.index { |frame| !File.expand_path(frame.path).start_with?(lib_path_start) }
299
+ stack_trace = stack_trace[index..] if index
300
+
301
+ stack_trace.map(&:to_s).join("\n")
302
+ end
253
303
  end
254
304
  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
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.2.beta.10
4
+ version: 1.0.3
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-10-30 00:00:00.000000000 Z
11
+ date: 2025-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -74,6 +74,7 @@ files:
74
74
  - docs/config.md
75
75
  - docs/proxy.md
76
76
  - docs/rails.md
77
+ - docs/troubleshooting.md
77
78
  - lib/aikido-zen.rb
78
79
  - lib/aikido/zen.rb
79
80
  - lib/aikido/zen/actor.rb
@@ -81,7 +82,10 @@ files:
81
82
  - lib/aikido/zen/agent/heartbeats_manager.rb
82
83
  - lib/aikido/zen/api_client.rb
83
84
  - lib/aikido/zen/attack.rb
85
+ - lib/aikido/zen/attack_wave.rb
86
+ - lib/aikido/zen/attack_wave/helpers.rb
84
87
  - lib/aikido/zen/background_worker.rb
88
+ - lib/aikido/zen/cache.rb
85
89
  - lib/aikido/zen/capped_collections.rb
86
90
  - lib/aikido/zen/collector.rb
87
91
  - lib/aikido/zen/collector/event.rb
@@ -100,13 +104,16 @@ files:
100
104
  - lib/aikido/zen/detached_agent/server.rb
101
105
  - lib/aikido/zen/errors.rb
102
106
  - lib/aikido/zen/event.rb
107
+ - lib/aikido/zen/helpers.rb
103
108
  - lib/aikido/zen/internals.rb
104
- - lib/aikido/zen/middleware/check_allowed_addresses.rb
109
+ - lib/aikido/zen/middleware/allowed_address_checker.rb
110
+ - lib/aikido/zen/middleware/attack_protector.rb
111
+ - lib/aikido/zen/middleware/attack_wave_protector.rb
112
+ - lib/aikido/zen/middleware/context_setter.rb
105
113
  - lib/aikido/zen/middleware/fork_detector.rb
106
114
  - lib/aikido/zen/middleware/middleware.rb
107
115
  - lib/aikido/zen/middleware/rack_throttler.rb
108
116
  - lib/aikido/zen/middleware/request_tracker.rb
109
- - lib/aikido/zen/middleware/set_context.rb
110
117
  - lib/aikido/zen/outbound_connection.rb
111
118
  - lib/aikido/zen/outbound_connection_monitor.rb
112
119
  - lib/aikido/zen/package.rb