aikido-zen 1.7.2.beta.1-aarch64-linux → 1.8.0-aarch64-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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/docs/request-bypassing.md +50 -0
  3. data/lib/aikido/zen/agent.rb +15 -0
  4. data/lib/aikido/zen/attack_wave/helpers.rb +10 -0
  5. data/lib/aikido/zen/attack_wave.rb +52 -16
  6. data/lib/aikido/zen/cache.rb +3 -3
  7. data/lib/aikido/zen/collector/event.rb +12 -6
  8. data/lib/aikido/zen/collector/routes.rb +2 -2
  9. data/lib/aikido/zen/collector/sink_stats.rb +4 -0
  10. data/lib/aikido/zen/collector/stats.rb +6 -2
  11. data/lib/aikido/zen/collector.rb +6 -11
  12. data/lib/aikido/zen/config.rb +8 -5
  13. data/lib/aikido/zen/context.rb +5 -0
  14. data/lib/aikido/zen/idor/protector.rb +1 -1
  15. data/lib/aikido/zen/middleware/allowed_address_checker.rb +3 -2
  16. data/lib/aikido/zen/middleware/attack_protector.rb +1 -1
  17. data/lib/aikido/zen/middleware/attack_wave_protector.rb +29 -27
  18. data/lib/aikido/zen/middleware/ip_list_checker.rb +1 -5
  19. data/lib/aikido/zen/middleware/rack_throttler.rb +1 -1
  20. data/lib/aikido/zen/middleware/request_tracker.rb +8 -8
  21. data/lib/aikido/zen/middleware/user_agent_checker.rb +1 -5
  22. data/lib/aikido/zen/sink.rb +8 -3
  23. data/lib/aikido/zen/sinks/action_controller.rb +1 -1
  24. data/lib/aikido/zen/sinks/async_http.rb +3 -7
  25. data/lib/aikido/zen/sinks/curb.rb +3 -7
  26. data/lib/aikido/zen/sinks/em_http.rb +3 -7
  27. data/lib/aikido/zen/sinks/excon.rb +3 -7
  28. data/lib/aikido/zen/sinks/file.rb +1 -1
  29. data/lib/aikido/zen/sinks/http.rb +3 -7
  30. data/lib/aikido/zen/sinks/httpclient.rb +3 -7
  31. data/lib/aikido/zen/sinks/httpx.rb +3 -7
  32. data/lib/aikido/zen/sinks/kernel.rb +1 -1
  33. data/lib/aikido/zen/sinks/mysql2.rb +1 -1
  34. data/lib/aikido/zen/sinks/net_http.rb +3 -7
  35. data/lib/aikido/zen/sinks/patron.rb +3 -7
  36. data/lib/aikido/zen/sinks/pg.rb +1 -1
  37. data/lib/aikido/zen/sinks/resolv.rb +1 -1
  38. data/lib/aikido/zen/sinks/socket.rb +1 -1
  39. data/lib/aikido/zen/sinks/sqlite3.rb +1 -1
  40. data/lib/aikido/zen/sinks/trilogy.rb +1 -1
  41. data/lib/aikido/zen/sinks/typhoeus.rb +3 -7
  42. data/lib/aikido/zen/version.rb +1 -1
  43. data/lib/aikido/zen/worker_process/agent/client.rb +19 -0
  44. data/lib/aikido/zen/worker_process/agent/server.rb +14 -3
  45. data/lib/aikido/zen.rb +68 -0
  46. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57c43e61f0b2a3e40d012d5737b7df660f3bd1bfc1097926b6ac13e4e53a9f01
4
- data.tar.gz: 813f4f2e3d923e371620115c90a2147bd7401a851d2b48deab2601b5300db63d
3
+ metadata.gz: c4c9820f5bed416d8a88ff5620690ff8a2dea5f404f967e6b1650562c05cb2cc
4
+ data.tar.gz: b08ee75b98ecd70862a4352d2a57713877d9ffaec6ba6820470c915c99169a12
5
5
  SHA512:
6
- metadata.gz: 4e5d9c802009572c20ee6327d1dac8fa22cdfad7a14c07d9c943fd91b2d0f5c8cdab2d112f26aeecfba61e9211251a352de75285484cb5530fd15449ba8c1715
7
- data.tar.gz: 6d359a6862e30f592d0c5caaea8b9e0be0d59fe021eb764371398513d7dc13ddaf8a8999cbf2ffce755a55b8d03c34dc5f5aa67bf68367cd71f38fa9b15a0f52
6
+ metadata.gz: 35f2cba1d894bb9b2e5048b2fcbd32cde9691c2236befc958e44f77f4a46f7b539d0b336d54ace283086f1d1f85ad3c0609af9df20d662088f8feeb5db8ebe2b
7
+ data.tar.gz: 18d284076efbb1b74c60ffd7a2e9bcb59a201a27941171027a31a289794a3a35362c5ba85113e35d682b7831b996c5317b58a8ee184d6342429c4dbfa80ab609
@@ -0,0 +1,50 @@
1
+ # Bypass Zen for a specific request
2
+
3
+ Call `Aikido::Zen.request_bypassed!` from a Rack middleware to bypass Zen for this request. A bypassed request is fully excluded from Zen inspection and enforcement: Zen will not analyze the request, generate findings, or apply blocking rules for that traffic. Your application handles the request normally.
4
+
5
+ > [!NOTE]
6
+ > Zen's built-in [Bypassed IPs](https://help.aikido.dev/zen-firewall/zen-features/bypassed-ips) feature uses request bypassing internally, triggered by a matching IP/CIDR. `Aikido::Zen.request_bypassed!` lets you bypass requests using your custom logic.
7
+
8
+ ## What gets bypassed
9
+
10
+ * **Attack protection** — SQL injection, path traversal, command injection, and SSRF attacks are not blocked or reported
11
+ * **Rate limiting** — never triggered.
12
+ * **IP blocking** — Known Threat Actors, Tor traffic blocking/monitoring, country blocking, and custom IP allow/block lists are not checked.
13
+ * **Bot traffic blocking** — not checked.
14
+ * **User blocking** — blocked users are not blocked.
15
+ * **Statistics** — the request isn't counted, and doesn't count against your monitored request quota.
16
+ * **Attack wave protection** — the request doesn't count towards wave detection.
17
+
18
+ ## Usage
19
+
20
+ Insert your middleware directly after `Aikido::Zen::Middleware::ContextSetter`, so it runs after the request context is set up but before Zen's other middleware:
21
+
22
+ ```ruby
23
+ # config/application.rb
24
+ class RequestBypasser
25
+ def initialize(app)
26
+ @app = app
27
+ end
28
+
29
+ def call(env)
30
+ if your_custom_logic?(env)
31
+ Aikido::Zen.request_bypassed! # Bypasses Zen for this request
32
+ end
33
+
34
+ @app.call(env)
35
+ end
36
+ end
37
+
38
+ module YourApp
39
+ class Application < Rails::Application
40
+ # ...
41
+
42
+ initializer "your_app.insert_request_bypasser", after: "aikido.add_middleware" do |app|
43
+ app.middleware.insert_after Aikido::Zen::Middleware::ContextSetter, RequestBypasser
44
+ end
45
+ end
46
+ end
47
+ ```
48
+
49
+ > [!WARNING]
50
+ > A bypassed request gets zero protection from Zen — no attack detection, no rate limiting, no blocking, no tracking. Ensure that your custom logic only bypasses requests that you fully trust.
@@ -220,6 +220,8 @@ module Aikido::Zen
220
220
  updated_at = Time.at(event[:data]["configUpdatedAt"].to_i)
221
221
 
222
222
  if should_fetch_settings?(updated_at)
223
+ return if realtime_settings_updates_too_fast?
224
+
223
225
  if update_settings_from_runtime_config!(@api_client.fetch_runtime_config, reason: "after server-sent event")
224
226
  updated_settings!
225
227
 
@@ -239,6 +241,19 @@ module Aikido::Zen
239
241
  updated_at > last_updated_at
240
242
  end
241
243
 
244
+ def realtime_settings_updates_too_fast?
245
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
246
+
247
+ last_updated_at = @realtime_settings_updates_last_updated_at
248
+ if last_updated_at && now - last_updated_at < @config.realtime_settings_updates_min_time_between_events
249
+ return true
250
+ end
251
+
252
+ @realtime_settings_updates_last_updated_at = now
253
+
254
+ false
255
+ end
256
+
242
257
  def heartbeats
243
258
  @heartbeats ||= Aikido::Zen::Agent::HeartbeatsManager.new(
244
259
  config: @config,
@@ -13,6 +13,16 @@ module Aikido::Zen
13
13
  false
14
14
  end
15
15
 
16
+ # Builds the sample for a request that has been classified as suspicious.
17
+ #
18
+ # @param context [Aikido::Zen::Context]
19
+ # @return [Aikido::Zen::AttackWave::Sample]
20
+ def self.sample_for(context)
21
+ request = context.request
22
+
23
+ Sample.new(verb: request.request_method, path: original_fullpath(request))
24
+ end
25
+
16
26
  def self.suspicious_request?(context, status_code)
17
27
  request = context.request
18
28
 
@@ -5,6 +5,11 @@ require_relative "attack_wave/helpers"
5
5
 
6
6
  module Aikido::Zen
7
7
  module AttackWave
8
+ # Tracks per-client-IP attack wave state.
9
+ #
10
+ # In multiprocess deployments, the main process has a single instance
11
+ # that may be accessed concurrently from each forked worker process's
12
+ # Aikido::Zen::RPC::Server connection thread.
8
13
  class Detector
9
14
  # @return [Aikido::Zen::CappedSet]
10
15
  attr_reader :samples
@@ -12,6 +17,8 @@ module Aikido::Zen
12
17
  def initialize(config: Aikido::Zen.config, clock: nil)
13
18
  @config = config
14
19
 
20
+ @mutex = Mutex.new
21
+
15
22
  @event_times = Cache.new(@config.attack_wave_max_cache_entries, ttl: @config.attack_wave_min_time_between_events, clock: clock)
16
23
 
17
24
  @request_counts = Cache.new(@config.attack_wave_max_cache_entries, 0, ttl: @config.attack_wave_min_time_between_requests, clock: clock)
@@ -21,29 +28,54 @@ module Aikido::Zen
21
28
  end
22
29
  end
23
30
 
24
- def attack_wave?(context, status_code = nil)
25
- client_ip = context.request.client_ip
26
-
27
- return false unless client_ip
31
+ # @api private
32
+ # @note Visible for testing.
33
+ #
34
+ # Whether the client IP is within the cooldown period after triggering
35
+ # an attack wave.
36
+ #
37
+ # @param client_ip [String]
38
+ # @return [Boolean]
39
+ def flagged?(client_ip)
40
+ !!@event_times[client_ip]
41
+ end
28
42
 
29
- return false if @event_times[client_ip]
43
+ # @api private
44
+ # @note Visible for testing.
45
+ #
46
+ # Flags the client IP as having triggered an attack wave for the
47
+ # cooldown period.
48
+ #
49
+ # @param client_ip [String]
50
+ # @return [void]
51
+ def flag!(client_ip)
52
+ @event_times[client_ip] = Time.now.utc
53
+ end
30
54
 
31
- return false unless AttackWave::Helpers.web_scanner?(context, status_code)
55
+ # Records a suspicious sample and, if it crosses the threshold for
56
+ # triggering an attack wave, flags the client IP as having just
57
+ # triggered an attack wave.
58
+ #
59
+ # This method is synchronized to prevent concurrent calls for the
60
+ # same client IP from crossing the threshold in the same instant.
61
+ #
62
+ # @param client_ip [String]
63
+ # @param sample [Aikido::Zen::AttackWave::Sample]
64
+ # @return [Array<Aikido::Zen::AttackWave::Sample>, nil]
65
+ def record(client_ip, sample)
66
+ @mutex.synchronize do
67
+ return nil if flagged?(client_ip)
32
68
 
33
- request_count = @request_counts[client_ip] += 1
69
+ request_count = @request_counts[client_ip] += 1
34
70
 
35
- context.request.then do |request|
36
- @samples[client_ip] <<= Sample.new(
37
- verb: request.request_method,
38
- path: AttackWave::Helpers.original_fullpath(request)
39
- )
40
- end
71
+ @samples[client_ip] <<= sample
41
72
 
42
- return false if request_count < @config.attack_wave_threshold
73
+ return nil if request_count < @config.attack_wave_threshold
43
74
 
44
- @event_times[client_ip] = Time.now.utc
75
+ flag!(client_ip)
45
76
 
46
- true
77
+ @samples[client_ip].to_a
78
+ end
47
79
  end
48
80
  end
49
81
 
@@ -117,6 +149,10 @@ module Aikido::Zen
117
149
  end
118
150
 
119
151
  class Sample
152
+ def self.from_json(data)
153
+ new(verb: data["method"], path: data["url"])
154
+ end
155
+
120
156
  # @return [String]
121
157
  attr_reader :verb
122
158
 
@@ -5,7 +5,7 @@ module Aikido::Zen
5
5
  extend Forwardable
6
6
 
7
7
  # @api private
8
- # Visible for testing.
8
+ # @note Visible for testing.
9
9
  def_delegators :@data,
10
10
  :size, :empty?
11
11
 
@@ -54,13 +54,13 @@ module Aikido::Zen
54
54
  end
55
55
 
56
56
  # @api private
57
- # Visible for testing.
57
+ # @note Visible for testing.
58
58
  def to_a
59
59
  @data.map { |key, entry| [key, entry.value] }
60
60
  end
61
61
 
62
62
  # @api private
63
- # Visible for testing.
63
+ # @note Visible for testing.
64
64
  def to_h
65
65
  to_a.to_h
66
66
  end
@@ -157,14 +157,16 @@ module Aikido::Zen
157
157
  def self.from_json(data)
158
158
  new(
159
159
  data["sink_name"],
160
+ data["sink_kind"],
160
161
  data["duration"],
161
162
  has_errors: data["has_errors"]
162
163
  )
163
164
  end
164
165
 
165
- def initialize(sink_name, duration, has_errors:)
166
+ def initialize(sink_name, sink_kind, duration, has_errors:)
166
167
  super()
167
168
  @sink_name = sink_name
169
+ @sink_kind = sink_kind
168
170
  @duration = duration
169
171
  @has_errors = has_errors
170
172
  end
@@ -172,17 +174,18 @@ module Aikido::Zen
172
174
  def as_json
173
175
  super.update({
174
176
  "sink_name" => @sink_name,
177
+ "sink_kind" => @sink_kind,
175
178
  "duration" => @duration,
176
179
  "has_errors" => @has_errors
177
180
  })
178
181
  end
179
182
 
180
183
  def handle(collector)
181
- collector.handle_track_scan(@sink_name, @duration, has_errors: @has_errors)
184
+ collector.handle_track_scan(@sink_name, @sink_kind, @duration, has_errors: @has_errors)
182
185
  end
183
186
 
184
187
  def inspect
185
- "#<#{self.class.name} #{@sink_name} #{format "%0.6f", @duration} #{@has_errors}>"
188
+ "#<#{self.class.name} #{@sink_name} #{@sink_kind} #{format "%0.6f", @duration} #{@has_errors}>"
186
189
  end
187
190
  end
188
191
 
@@ -192,29 +195,32 @@ module Aikido::Zen
192
195
  def self.from_json(data)
193
196
  new(
194
197
  data["sink_name"],
198
+ data["sink_kind"],
195
199
  being_blocked: data["being_blocked"]
196
200
  )
197
201
  end
198
202
 
199
- def initialize(sink_name, being_blocked:)
203
+ def initialize(sink_name, sink_kind, being_blocked:)
200
204
  super()
201
205
  @sink_name = sink_name
206
+ @sink_kind = sink_kind
202
207
  @being_blocked = being_blocked
203
208
  end
204
209
 
205
210
  def as_json
206
211
  super.update({
207
212
  "sink_name" => @sink_name,
213
+ "sink_kind" => @sink_kind,
208
214
  "being_blocked" => @being_blocked
209
215
  })
210
216
  end
211
217
 
212
218
  def handle(collector)
213
- collector.handle_track_attack(@sink_name, being_blocked: @being_blocked)
219
+ collector.handle_track_attack(@sink_name, @sink_kind, being_blocked: @being_blocked)
214
220
  end
215
221
 
216
222
  def inspect
217
- "#<#{self.class.name} #{@sink_name} #{@being_blocked}>"
223
+ "#<#{self.class.name} #{@sink_name} #{@sink_kind} #{@being_blocked}>"
218
224
  end
219
225
  end
220
226
 
@@ -8,7 +8,7 @@ module Aikido::Zen
8
8
  # Keeps track of the visited routes.
9
9
  class Collector::Routes
10
10
  # @api private
11
- # Visible for testing.
11
+ # @note Visible for testing.
12
12
  attr_reader :visits
13
13
 
14
14
  def initialize(config = Aikido::Zen.config)
@@ -35,7 +35,7 @@ module Aikido::Zen
35
35
  end
36
36
 
37
37
  # @api private
38
- # Visible for testing.
38
+ # @note Visible for testing.
39
39
  def [](route)
40
40
  @visits[route]
41
41
  end
@@ -7,6 +7,9 @@ module Aikido::Zen
7
7
  #
8
8
  # Tracks data specific to a single Sink.
9
9
  class Collector::SinkStats
10
+ # @return [String] category of operation this sink represents.
11
+ attr_accessor :kind
12
+
10
13
  # @return [Integer] number of total calls to Sink#scan.
11
14
  attr_accessor :scans
12
15
 
@@ -62,6 +65,7 @@ module Aikido::Zen
62
65
 
63
66
  def as_json
64
67
  {
68
+ "kind" => @kind,
65
69
  "total" => @scans,
66
70
  "interceptorThrewError" => @errors,
67
71
  "withoutContext" => 0,
@@ -94,21 +94,25 @@ module Aikido::Zen
94
94
  end
95
95
 
96
96
  # @param sink_name [String] the name of the sink
97
+ # @param sink_kind [String] the category of operation the sink represents
97
98
  # @param duration [Float] the length the scan in seconds
98
99
  # @param has_errors [Boolean] whether errors occurred during the scan
99
100
  # @return [void]
100
- def add_scan(sink_name, duration, has_errors:)
101
+ def add_scan(sink_name, sink_kind, duration, has_errors:)
101
102
  stats = @sinks[sink_name]
103
+ stats.kind = sink_kind
102
104
  stats.scans += 1
103
105
  stats.errors += 1 if has_errors
104
106
  stats.add_timing(duration)
105
107
  end
106
108
 
107
109
  # @param sink_name [String] the name of the sink
110
+ # @param sink_kind [String] the category of operation the sink represents
108
111
  # @param being_blocked [Boolean] whether the Agent blocked the request
109
112
  # @return [void]
110
- def add_attack(sink_name, being_blocked:)
113
+ def add_attack(sink_name, sink_kind, being_blocked:)
111
114
  stats = @sinks[sink_name]
115
+ stats.kind = sink_kind
112
116
  stats.attacks += 1
113
117
  stats.blocked_attacks += 1 if being_blocked
114
118
  end
@@ -146,12 +146,12 @@ module Aikido::Zen
146
146
  # @param scan [Aikido::Zen::Scan]
147
147
  # @return [void]
148
148
  def track_scan(scan)
149
- add_event(Events::TrackScan.new(scan.sink.name, scan.duration, has_errors: scan.errors?))
149
+ add_event(Events::TrackScan.new(scan.sink.name, scan.sink.kind, scan.duration, has_errors: scan.errors?))
150
150
  end
151
151
 
152
- def handle_track_scan(sink_name, duration, has_errors:)
152
+ def handle_track_scan(sink_name, sink_kind, duration, has_errors:)
153
153
  synchronize(@stats) do |stats|
154
- stats.add_scan(sink_name, duration, has_errors: has_errors)
154
+ stats.add_scan(sink_name, sink_kind, duration, has_errors: has_errors)
155
155
  end
156
156
  end
157
157
 
@@ -160,12 +160,12 @@ module Aikido::Zen
160
160
  # @param attack [Aikido::Zen::Attack]
161
161
  # @return [void]
162
162
  def track_attack(attack)
163
- add_event(Events::TrackAttack.new(attack.sink.name, being_blocked: attack.blocked?))
163
+ add_event(Events::TrackAttack.new(attack.sink.name, attack.sink.kind, being_blocked: attack.blocked?))
164
164
  end
165
165
 
166
- def handle_track_attack(sink_name, being_blocked:)
166
+ def handle_track_attack(sink_name, sink_kind, being_blocked:)
167
167
  synchronize(@stats) do |stats|
168
- stats.add_attack(sink_name, being_blocked: being_blocked)
168
+ stats.add_attack(sink_name, sink_kind, being_blocked: being_blocked)
169
169
  end
170
170
  end
171
171
 
@@ -210,7 +210,6 @@ module Aikido::Zen
210
210
  end
211
211
 
212
212
  # @api private
213
- #
214
213
  # @note Visible for testing.
215
214
  def stats
216
215
  handle
@@ -218,7 +217,6 @@ module Aikido::Zen
218
217
  end
219
218
 
220
219
  # @api private
221
- #
222
220
  # @note Visible for testing.
223
221
  def users
224
222
  handle
@@ -226,7 +224,6 @@ module Aikido::Zen
226
224
  end
227
225
 
228
226
  # @api private
229
- #
230
227
  # @note Visible for testing.
231
228
  def hosts
232
229
  handle
@@ -234,7 +231,6 @@ module Aikido::Zen
234
231
  end
235
232
 
236
233
  # @api private
237
- #
238
234
  # @note Visible for testing.
239
235
  def routes
240
236
  handle
@@ -242,7 +238,6 @@ module Aikido::Zen
242
238
  end
243
239
 
244
240
  # @api private
245
- #
246
241
  # @note Visible for testing.
247
242
  def middleware_installed?
248
243
  @middleware_installed.true?
@@ -42,7 +42,7 @@ module Aikido::Zen
42
42
  attr_reader :api_endpoint
43
43
 
44
44
  # @return [URI] The HTTP host for the Aikido Runtime API. Defaults to
45
- # +https://runtime.aikido.dev+.
45
+ # +https://guard.aikido.dev+.
46
46
  attr_reader :realtime_endpoint
47
47
 
48
48
  # @return [Hash] HTTP timeouts for communicating with the API.
@@ -229,6 +229,11 @@ module Aikido::Zen
229
229
  attr_accessor :realtime_settings_updates_enabled
230
230
  alias_method :realtime_settings_updates_enabled?, :realtime_settings_updates_enabled
231
231
 
232
+ # @return [Integer] the minimum time in milliseconds between realtime
233
+ # settings updates.
234
+ # Defaults to 9 seconds in milliseconds.
235
+ attr_accessor :realtime_settings_updates_min_time_between_events
236
+
232
237
  def initialize
233
238
  self.insert_middleware_after = ::ActionDispatch::RemoteIp
234
239
  self.disabled = read_boolean_from_env(ENV.fetch("AIKIDO_DISABLE", false)) || read_boolean_from_env(ENV.fetch("AIKIDO_DISABLED", false))
@@ -236,7 +241,7 @@ module Aikido::Zen
236
241
  self.debugging = read_boolean_from_env(ENV.fetch("AIKIDO_DEBUG", false))
237
242
  self.api_token = ENV.fetch("AIKIDO_TOKEN", nil)
238
243
  self.api_endpoint = ENV.fetch("AIKIDO_ENDPOINT") { regional_api_endpoint(api_token) }
239
- self.realtime_endpoint = ENV.fetch("AIKIDO_REALTIME_ENDPOINT", DEFAULT_RUNTIME_BASE_URL)
244
+ self.realtime_endpoint = api_endpoint
240
245
  self.api_timeouts = 10
241
246
  self.polling_interval = 60 # 1 min
242
247
  self.initial_heartbeat_delays = [30, 60 * 2] # 30 sec, 2 min
@@ -277,6 +282,7 @@ module Aikido::Zen
277
282
  self.idor_excluded_table_names = []
278
283
  self.idor_max_cache_entries = 1000
279
284
  self.realtime_settings_updates_enabled = false
285
+ self.realtime_settings_updates_min_time_between_events = 9 * 1000 # 9 sec (ms)
280
286
  end
281
287
 
282
288
  # Set the base URL for API requests.
@@ -377,9 +383,6 @@ module Aikido::Zen
377
383
  "AU" => "https://guard.au.aikido.dev"
378
384
  }.freeze
379
385
 
380
- # @!visibility private
381
- DEFAULT_RUNTIME_BASE_URL = "https://runtime.aikido.dev"
382
-
383
386
  # @!visibility private
384
387
  DEFAULT_JSON_ENCODER = JSON.method(:dump)
385
388
 
@@ -24,6 +24,10 @@ module Aikido::Zen
24
24
  attr_accessor :scanning
25
25
  alias_method :scanning?, :scanning
26
26
 
27
+ # @return [Boolean, nil] whether the request is bypassed.
28
+ attr_accessor :request_bypassed
29
+ alias_method :request_bypassed?, :request_bypassed
30
+
27
31
  # @return [Boolean] whether attack protection for the currently requested
28
32
  # endpoint was disabled on the Aikido dashboard, or if the source IP for
29
33
  # this request is in the "Bypass List".
@@ -48,6 +52,7 @@ module Aikido::Zen
48
52
 
49
53
  @metadata = {}
50
54
  @scanning = false
55
+ @request_bypassed = nil
51
56
  @protection_disabled = false
52
57
  @idor_protection_enabled = false
53
58
  end
@@ -7,7 +7,7 @@ module Aikido::Zen
7
7
 
8
8
  class Protector
9
9
  # @api private
10
- # Visible for testing.
10
+ # @note Visible for testing.
11
11
  attr_accessor :cache
12
12
 
13
13
  def initialize(config: Aikido::Zen.config)
@@ -5,8 +5,9 @@ module Aikido::Zen
5
5
  # Middleware that only allows allowed IPs when allowed IPs are configured for
6
6
  # any matching route in the Aikido dashboard.
7
7
  class AllowedAddressChecker
8
- def initialize(app, config: Aikido::Zen.config, settings: Aikido::Zen.runtime_settings)
8
+ def initialize(app, zen: Aikido::Zen, config: zen.config, settings: zen.runtime_settings)
9
9
  @app = app
10
+ @zen = zen
10
11
  @config = config
11
12
  @settings = settings
12
13
  end
@@ -22,7 +23,7 @@ module Aikido::Zen
22
23
  end
23
24
 
24
25
  private def allowed?(request)
25
- return true if @settings.bypassed_ips.include?(request.client_ip)
26
+ return true if @zen.request_bypassed?
26
27
 
27
28
  matches = @settings.endpoints.matched_settings(request.route)
28
29
 
@@ -19,7 +19,7 @@ module Aikido::Zen
19
19
  end
20
20
 
21
21
  private def protection_disabled?(request)
22
- return true if @settings.bypassed_ips.include?(request.client_ip)
22
+ return true if @zen.request_bypassed?
23
23
 
24
24
  !@settings.endpoints.matched_settings(request.route).all?(&:protected?)
25
25
  end
@@ -4,10 +4,9 @@ module Aikido
4
4
  module Zen
5
5
  module Middleware
6
6
  class AttackWaveProtector
7
- def initialize(app, zen: Aikido::Zen, settings: Aikido::Zen.runtime_settings)
7
+ def initialize(app, zen: Aikido::Zen)
8
8
  @app = app
9
9
  @zen = zen
10
- @settings = settings
11
10
  end
12
11
 
13
12
  def call(env)
@@ -21,43 +20,46 @@ module Aikido
21
20
  end
22
21
 
23
22
  # @api private
24
- # Visible for testing.
25
- def attack_wave?(context, status_code = nil)
23
+ # @note Visible for testing.
24
+ #
25
+ # @param context [Aikido::Zen::Context]
26
+ # @param status_code [Integer, nil]
27
+ # @return [Array<Aikido::Zen::AttackWave::Sample>, nil]
28
+ def detect_attack_wave(context, status_code = nil)
26
29
  request = context.request
27
- return false if request.nil?
30
+ return nil if request.nil?
28
31
 
29
- return false if @settings.bypassed_ips.include?(request.client_ip)
32
+ return nil if @zen.request_bypassed?
30
33
 
31
- @zen.attack_wave_detector.attack_wave?(context, status_code)
34
+ @zen.detect_attack_wave(context, status_code)
32
35
  end
33
36
 
34
37
  # @api private
35
- # Visible for testing.
38
+ # @note Visible for testing.
36
39
  def protect(context, status_code = nil)
37
- if attack_wave?(context, status_code)
38
- client_ip = context.request.client_ip
40
+ samples = detect_attack_wave(context, status_code)
41
+ return unless samples
39
42
 
40
- request = Aikido::Zen::AttackWave::Request.new(
41
- ip_address: client_ip,
42
- user_agent: context.request.user_agent,
43
- source: context.request.framework
44
- )
43
+ client_ip = context.request.client_ip
45
44
 
46
- samples = @zen.attack_wave_detector.samples[client_ip].to_a
45
+ request = Aikido::Zen::AttackWave::Request.new(
46
+ ip_address: client_ip,
47
+ user_agent: context.request.user_agent,
48
+ source: context.request.framework
49
+ )
47
50
 
48
- attack = Aikido::Zen::AttackWave::Attack.new(
49
- samples: samples,
50
- user: context.request.actor
51
- )
51
+ attack = Aikido::Zen::AttackWave::Attack.new(
52
+ samples: samples,
53
+ user: context.request.actor
54
+ )
52
55
 
53
- attack_wave = Aikido::Zen::Events::AttackWave.new(
54
- request: request,
55
- attack: attack
56
- )
56
+ attack_wave = Aikido::Zen::Events::AttackWave.new(
57
+ request: request,
58
+ attack: attack
59
+ )
57
60
 
58
- @zen.track_attack_wave(attack_wave)
59
- @zen.agent.report(attack_wave)
60
- end
61
+ @zen.track_attack_wave(attack_wave)
62
+ @zen.agent.report(attack_wave)
61
63
  end
62
64
  end
63
65
  end
@@ -15,7 +15,7 @@ module Aikido::Zen
15
15
 
16
16
  client_ip = request.client_ip
17
17
 
18
- return @app.call(env) if bypassed_ip?(client_ip)
18
+ return @app.call(env) if @zen.request_bypassed?
19
19
 
20
20
  if !@settings.allowed_ip?(client_ip)
21
21
  return @config.blocked_responder.call(request, :ip_allowed_list)
@@ -38,10 +38,6 @@ module Aikido::Zen
38
38
 
39
39
  @app.call(env)
40
40
  end
41
-
42
- def bypassed_ip?(client_ip)
43
- @settings.bypassed_ips.include?(client_ip)
44
- end
45
41
  end
46
42
  end
47
43
  end
@@ -34,7 +34,7 @@ module Aikido::Zen
34
34
  private
35
35
 
36
36
  def should_throttle?(request)
37
- return false if @settings.bypassed_ips.include?(request.client_ip)
37
+ return false if @zen.request_bypassed?
38
38
 
39
39
  return false if request.actor && @settings.user_excluded_from_rate_limiting?(request.actor.id)
40
40