aikido-zen 1.7.2 → 1.8.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41f1982e134a806c513cc166247059c3f290f6bbc88ff32dcf4bdc65da9f377d
4
- data.tar.gz: d97dac2f830555239836a143b815ca3c85c4993776183ce73bc5dc392fed9618
3
+ metadata.gz: c01eda1d0ea486bf41d00d6efc05f064ad13b3ae71e112af02b1c01e3d164592
4
+ data.tar.gz: 222be5ac12985a6f855335a3433d32632704d6ebb1c0f7249fe9bb85492554f2
5
5
  SHA512:
6
- metadata.gz: 526140c45a47c29f4f36e1508bc3305bafe535e92479c877deeb92a1f74c618fa19d7c46a7d67183310fbc414f05912b93c88f2cdb109730d04c0b4b6a54b301
7
- data.tar.gz: 6c43bc7461a52ded8331b6ba2188afd9d273e1b012f5912b2d9786c439e2890380003779925bd3f37fdd4422f4b34aefe13af6d29d0a6a7f3b4e351c5019dca1
6
+ metadata.gz: 4645441dc4301bccc6c09347f864149b071f9cdcd16811309dd7a7e9650848ff90a559ebc576338e7bc8bc41fc3f4d4c2deaa096112628da02bf71619e69ad77
7
+ data.tar.gz: 8e7e3717a0a38dcb0d0c38abaf2142569404917f0fff5da720fcd4d180eebb27799d7b836a5222389a33a1a963e431f2459fe2299ab1d6e0c85234b615ce997d
@@ -1,11 +1,23 @@
1
1
  # Bypass Zen for a specific request
2
2
 
3
- To disable Zen for a specific request, call `Aikido::Zen.request_bypassed!` from a Rack middleware. This bypasses Zen's security checks and stats collection for that request.
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
4
 
5
5
  > [!NOTE]
6
- > Zen already has a built-in feature for bypassing specific IP addresses. This feature offers more flexibility, letting you bypass Zen for a request based on any criteria you choose.
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
7
 
8
- `Aikido::Zen.request_bypassed!` needs the request's context to already be set up, so your middleware must run after Zen's `ContextSetter` middleware. The following example shows how to bypass a specific request in a Rails application:
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:
9
21
 
10
22
  ```ruby
11
23
  # config/application.rb
@@ -35,4 +47,4 @@ end
35
47
  ```
36
48
 
37
49
  > [!WARNING]
38
- > Use this feature with caution, as it can potentially expose your application to security risks if not used properly.
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,
@@ -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
 
@@ -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
 
@@ -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
 
@@ -15,6 +15,7 @@ module Aikido::Zen
15
15
  # @param name [String] name of the library being patched. (This must
16
16
  # match the name of the gem, or we won't report that gem as
17
17
  # supported.)
18
+ # @param kind [String] category of operation this sink represents.
18
19
  # @param scanners [Array<#call>] a list of objects that respond to
19
20
  # #call with a Hash and return an Attack or nil.
20
21
  # @param opts [Hash<Symbol, Object>] any other options to pass to
@@ -23,9 +24,9 @@ module Aikido::Zen
23
24
  # @return [void]
24
25
  # @raise [ArgumentError] if a Sink with this name has already been
25
26
  # registered.
26
- def self.add(name, scanners:, **opts)
27
+ def self.add(name, kind, scanners:, **opts)
27
28
  raise ArgumentError, "Sink #{name} already registered" if registry.key?(name.to_s)
28
- registry[name.to_s] = Sink.new(name.to_s, scanners: scanners, **opts)
29
+ registry[name.to_s] = Sink.new(name.to_s, kind, scanners: scanners, **opts)
29
30
  end
30
31
  end
31
32
 
@@ -42,6 +43,9 @@ module Aikido::Zen
42
43
  # @return [String] name of the patched library (e.g. "mysql2").
43
44
  attr_reader :name
44
45
 
46
+ # @return [String] category of operation this sink represents.
47
+ attr_reader :kind
48
+
45
49
  # @return [Array<#call>] list of registered scanners for this sink.
46
50
  attr_reader :scanners
47
51
 
@@ -53,10 +57,11 @@ module Aikido::Zen
53
57
 
54
58
  DEFAULT_REPORTER = ->(scan) { Aikido::Zen.track_scan(scan) }
55
59
 
56
- def initialize(name, scanners:, operation: name, reporter: DEFAULT_REPORTER)
60
+ def initialize(name, kind, scanners:, operation: name, reporter: DEFAULT_REPORTER)
57
61
  raise ArgumentError, "scanners cannot be empty" if scanners.empty?
58
62
 
59
63
  @name = name
64
+ @kind = kind
60
65
  @operation = operation
61
66
  @scanners = scanners
62
67
  @reporter = reporter
@@ -6,7 +6,7 @@ module Aikido::Zen
6
6
  module Sinks
7
7
  module Async
8
8
  module HTTP
9
- SINK = Sinks.add("async-http", scanners: [
9
+ SINK = Sinks.add("async-http", "outgoing_http_op", scanners: [
10
10
  Scanners::SSRFScanner
11
11
  ])
12
12
 
@@ -50,12 +50,10 @@ module Aikido::Zen
50
50
 
51
51
  connection = OutboundConnection.from_uri(uri)
52
52
 
53
- settings = Aikido::Zen.runtime_settings
54
-
55
53
  unless Aikido::Zen.request_bypassed?
56
54
  Aikido::Zen.track_outbound(connection)
57
55
 
58
- if settings.block_outbound?(connection)
56
+ if Aikido::Zen.block_outbound?(connection)
59
57
  Sinks::DSL.presafe do
60
58
  raise OutboundConnectionBlockedError.new(connection)
61
59
  end
@@ -5,7 +5,7 @@ require_relative "../scanners/ssrf_scanner"
5
5
  module Aikido::Zen
6
6
  module Sinks
7
7
  module Curl
8
- SINK = Sinks.add("curb", scanners: [
8
+ SINK = Sinks.add("curb", "outgoing_http_op", scanners: [
9
9
  Scanners::SSRFScanner
10
10
  ])
11
11
 
@@ -62,12 +62,10 @@ module Aikido::Zen
62
62
 
63
63
  connection = OutboundConnection.from_uri(URI(url))
64
64
 
65
- settings = Aikido::Zen.runtime_settings
66
-
67
65
  unless Aikido::Zen.request_bypassed?
68
66
  Aikido::Zen.track_outbound(connection)
69
67
 
70
- if settings.block_outbound?(connection)
68
+ if Aikido::Zen.block_outbound?(connection)
71
69
  Sinks::DSL.presafe do
72
70
  raise OutboundConnectionBlockedError.new(connection)
73
71
  end
@@ -6,7 +6,7 @@ module Aikido::Zen
6
6
  module Sinks
7
7
  module EventMachine
8
8
  module HttpRequest
9
- SINK = Sinks.add("em-http-request", scanners: [
9
+ SINK = Sinks.add("em-http-request", "outgoing_http_op", scanners: [
10
10
  Scanners::SSRFScanner
11
11
  ])
12
12
 
@@ -48,12 +48,10 @@ module Aikido::Zen
48
48
  port: req.port
49
49
  )
50
50
 
51
- settings = Aikido::Zen.runtime_settings
52
-
53
51
  unless Aikido::Zen.request_bypassed?
54
52
  Aikido::Zen.track_outbound(connection)
55
53
 
56
- if settings.block_outbound?(connection)
54
+ if Aikido::Zen.block_outbound?(connection)
57
55
  Sinks::DSL.presafe do
58
56
  raise OutboundConnectionBlockedError.new(connection)
59
57
  end
@@ -5,7 +5,7 @@ require_relative "../scanners/ssrf_scanner"
5
5
  module Aikido::Zen
6
6
  module Sinks
7
7
  module Excon
8
- SINK = Sinks.add("excon", scanners: [
8
+ SINK = Sinks.add("excon", "outgoing_http_op", scanners: [
9
9
  Scanners::SSRFScanner
10
10
  ])
11
11
 
@@ -54,12 +54,10 @@ module Aikido::Zen
54
54
 
55
55
  connection = OutboundConnection.from_uri(request.uri)
56
56
 
57
- settings = Aikido::Zen.runtime_settings
58
-
59
57
  unless Aikido::Zen.request_bypassed?
60
58
  Aikido::Zen.track_outbound(connection)
61
59
 
62
- if settings.block_outbound?(connection)
60
+ if Aikido::Zen.block_outbound?(connection)
63
61
  Sinks::DSL.presafe do
64
62
  raise OutboundConnectionBlockedError.new(connection)
65
63
  end
@@ -3,7 +3,7 @@
3
3
  module Aikido::Zen
4
4
  module Sinks
5
5
  module File
6
- SINK = Sinks.add("File", scanners: [Scanners::PathTraversalScanner])
6
+ SINK = Sinks.add("File", "fs_op", scanners: [Scanners::PathTraversalScanner])
7
7
 
8
8
  module Helpers
9
9
  def self.scan(filepath, operation)
@@ -5,7 +5,7 @@ require_relative "../scanners/ssrf_scanner"
5
5
  module Aikido::Zen
6
6
  module Sinks
7
7
  module HTTP
8
- SINK = Sinks.add("http", scanners: [
8
+ SINK = Sinks.add("http", "outgoing_http_op", scanners: [
9
9
  Scanners::SSRFScanner
10
10
  ])
11
11
 
@@ -68,12 +68,10 @@ module Aikido::Zen
68
68
 
69
69
  connection = Helpers.build_outbound(req)
70
70
 
71
- settings = Aikido::Zen.runtime_settings
72
-
73
71
  unless Aikido::Zen.request_bypassed?
74
72
  Aikido::Zen.track_outbound(connection)
75
73
 
76
- if settings.block_outbound?(connection)
74
+ if Aikido::Zen.block_outbound?(connection)
77
75
  Sinks::DSL.presafe do
78
76
  raise OutboundConnectionBlockedError.new(connection)
79
77
  end
@@ -5,7 +5,7 @@ require_relative "../scanners/ssrf_scanner"
5
5
  module Aikido::Zen
6
6
  module Sinks
7
7
  module HTTPClient
8
- SINK = Sinks.add("httpclient", scanners: [
8
+ SINK = Sinks.add("httpclient", "outgoing_http_op", scanners: [
9
9
  Scanners::SSRFScanner
10
10
  ])
11
11
 
@@ -48,12 +48,10 @@ module Aikido::Zen
48
48
  context["ssrf.request"] = wrapped_request
49
49
  end
50
50
 
51
- settings = Aikido::Zen.runtime_settings
52
-
53
51
  unless Aikido::Zen.request_bypassed?
54
52
  Aikido::Zen.track_outbound(connection)
55
53
 
56
- if settings.block_outbound?(connection)
54
+ if Aikido::Zen.block_outbound?(connection)
57
55
  Sinks::DSL.presafe do
58
56
  raise OutboundConnectionBlockedError.new(connection)
59
57
  end
@@ -5,7 +5,7 @@ require_relative "../scanners/ssrf_scanner"
5
5
  module Aikido::Zen
6
6
  module Sinks
7
7
  module HTTPX
8
- SINK = Sinks.add("httpx", scanners: [
8
+ SINK = Sinks.add("httpx", "outgoing_http_op", scanners: [
9
9
  Scanners::SSRFScanner
10
10
  ])
11
11
 
@@ -53,12 +53,10 @@ module Aikido::Zen
53
53
 
54
54
  connection = OutboundConnection.from_uri(request.uri)
55
55
 
56
- settings = Aikido::Zen.runtime_settings
57
-
58
56
  unless Aikido::Zen.request_bypassed?
59
57
  Aikido::Zen.track_outbound(connection)
60
58
 
61
- if settings.block_outbound?(connection)
59
+ if Aikido::Zen.block_outbound?(connection)
62
60
  Sinks::DSL.presafe do
63
61
  raise OutboundConnectionBlockedError.new(connection)
64
62
  end
@@ -3,7 +3,7 @@
3
3
  module Aikido::Zen
4
4
  module Sinks
5
5
  module Kernel
6
- SINK = Sinks.add("Kernel", scanners: [Scanners::ShellInjectionScanner])
6
+ SINK = Sinks.add("Kernel", "exec_op", scanners: [Scanners::ShellInjectionScanner])
7
7
 
8
8
  module Helpers
9
9
  def self.scan(command, operation)
@@ -3,7 +3,7 @@
3
3
  module Aikido::Zen
4
4
  module Sinks
5
5
  module Mysql2
6
- SINK = Sinks.add("mysql2", scanners: [Scanners::SQLInjectionScanner])
6
+ SINK = Sinks.add("mysql2", "sql_op", scanners: [Scanners::SQLInjectionScanner])
7
7
 
8
8
  module Helpers
9
9
  def self.scan(query, operation)
@@ -6,7 +6,7 @@ module Aikido::Zen
6
6
  module Sinks
7
7
  module Net
8
8
  module HTTP
9
- SINK = Sinks.add("net-http", scanners: [
9
+ SINK = Sinks.add("net-http", "outgoing_http_op", scanners: [
10
10
  Scanners::SSRFScanner
11
11
  ])
12
12
 
@@ -84,12 +84,10 @@ module Aikido::Zen
84
84
 
85
85
  connection = Helpers.build_outbound(self)
86
86
 
87
- settings = Aikido::Zen.runtime_settings
88
-
89
87
  unless Aikido::Zen.request_bypassed?
90
88
  Aikido::Zen.track_outbound(connection)
91
89
 
92
- if settings.block_outbound?(connection)
90
+ if Aikido::Zen.block_outbound?(connection)
93
91
  Sinks::DSL.presafe do
94
92
  raise OutboundConnectionBlockedError.new(connection)
95
93
  end
@@ -5,7 +5,7 @@ require_relative "../scanners/ssrf_scanner"
5
5
  module Aikido::Zen
6
6
  module Sinks
7
7
  module Patron
8
- SINK = Sinks.add("patron", scanners: [
8
+ SINK = Sinks.add("patron", "outgoing_http_op", scanners: [
9
9
  Scanners::SSRFScanner
10
10
  ])
11
11
 
@@ -57,12 +57,10 @@ module Aikido::Zen
57
57
 
58
58
  connection = OutboundConnection.from_uri(URI(request.url))
59
59
 
60
- settings = Aikido::Zen.runtime_settings
61
-
62
60
  unless Aikido::Zen.request_bypassed?
63
61
  Aikido::Zen.track_outbound(connection)
64
62
 
65
- if settings.block_outbound?(connection)
63
+ if Aikido::Zen.block_outbound?(connection)
66
64
  Sinks::DSL.presafe do
67
65
  raise OutboundConnectionBlockedError.new(connection)
68
66
  end
@@ -3,7 +3,7 @@
3
3
  module Aikido::Zen
4
4
  module Sinks
5
5
  module PG
6
- SINK = Sinks.add("pg", scanners: [Scanners::SQLInjectionScanner])
6
+ SINK = Sinks.add("pg", "sql_op", scanners: [Scanners::SQLInjectionScanner])
7
7
 
8
8
  module Helpers
9
9
  # For some reason, the ActiveRecord pg adaptor does not wrap exceptions
@@ -6,7 +6,7 @@ require_relative "../scanners/ssrf_scanner"
6
6
  module Aikido::Zen
7
7
  module Sinks
8
8
  module Resolv
9
- SINK = Sinks.add("resolv", scanners: [
9
+ SINK = Sinks.add("resolv", "outgoing_http_op", scanners: [
10
10
  Scanners::StoredSSRFScanner,
11
11
  Scanners::SSRFScanner
12
12
  ])
@@ -10,7 +10,7 @@ module Aikido::Zen
10
10
  # there's no way to access the internal DNS resolution that happens in C
11
11
  # when using the socket primitives.
12
12
  module Socket
13
- SINK = Sinks.add("socket", scanners: [
13
+ SINK = Sinks.add("socket", "outgoing_http_op", scanners: [
14
14
  Scanners::StoredSSRFScanner,
15
15
  Scanners::SSRFScanner
16
16
  ])
@@ -3,7 +3,7 @@
3
3
  module Aikido::Zen
4
4
  module Sinks
5
5
  module SQLite3
6
- SINK = Sinks.add("sqlite3", scanners: [Scanners::SQLInjectionScanner])
6
+ SINK = Sinks.add("sqlite3", "sql_op", scanners: [Scanners::SQLInjectionScanner])
7
7
 
8
8
  module Helpers
9
9
  def self.scan(query, operation)
@@ -3,7 +3,7 @@
3
3
  module Aikido::Zen
4
4
  module Sinks
5
5
  module Trilogy
6
- SINK = Sinks.add("trilogy", scanners: [Scanners::SQLInjectionScanner])
6
+ SINK = Sinks.add("trilogy", "sql_op", scanners: [Scanners::SQLInjectionScanner])
7
7
 
8
8
  module Helpers
9
9
  def self.scan(query, operation)
@@ -5,7 +5,7 @@ require_relative "../sink"
5
5
  module Aikido::Zen
6
6
  module Sinks
7
7
  module Typhoeus
8
- SINK = Sinks.add("typhoeus", scanners: [
8
+ SINK = Sinks.add("typhoeus", "outgoing_http_op", scanners: [
9
9
  Aikido::Zen::Scanners::SSRFScanner
10
10
  ])
11
11
 
@@ -24,12 +24,10 @@ module Aikido::Zen
24
24
 
25
25
  connection = Aikido::Zen::OutboundConnection.from_uri(URI(request.base_url))
26
26
 
27
- settings = Aikido::Zen.runtime_settings
28
-
29
27
  unless Aikido::Zen.request_bypassed?
30
28
  Aikido::Zen.track_outbound(connection)
31
29
 
32
- if settings.block_outbound?(connection)
30
+ if Aikido::Zen.block_outbound?(connection)
33
31
  raise OutboundConnectionBlockedError.new(connection)
34
32
  end
35
33
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Aikido
4
4
  module Zen
5
- VERSION = "1.7.2"
5
+ VERSION = "1.8.0"
6
6
 
7
7
  # The version of libzen_internals that we build against.
8
8
  LIBZEN_VERSION = "0.1.61"
data/lib/aikido/zen.rb CHANGED
@@ -224,6 +224,14 @@ module Aikido
224
224
  collector.track_outbound(connection)
225
225
  end
226
226
 
227
+ # @param connection [Aikido::Zen::OutboundConnection]
228
+ # @return [Boolean] whether this outbound connection should be blocked.
229
+ def self.block_outbound?(connection)
230
+ return false if current_context&.protection_disabled?
231
+
232
+ runtime_settings.block_outbound?(connection)
233
+ end
234
+
227
235
  # Track statistics about the result of a Sink's scan, and report it as
228
236
  # an Attack if one is detected.
229
237
  #
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.7.2
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aikido Security
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-17 00:00:00.000000000 Z
11
+ date: 2026-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby