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 +4 -4
- data/docs/request-bypassing.md +16 -4
- data/lib/aikido/zen/agent.rb +15 -0
- data/lib/aikido/zen/collector/event.rb +12 -6
- data/lib/aikido/zen/collector/sink_stats.rb +4 -0
- data/lib/aikido/zen/collector/stats.rb +6 -2
- data/lib/aikido/zen/collector.rb +6 -6
- data/lib/aikido/zen/config.rb +8 -5
- data/lib/aikido/zen/sink.rb +8 -3
- data/lib/aikido/zen/sinks/async_http.rb +2 -4
- data/lib/aikido/zen/sinks/curb.rb +2 -4
- data/lib/aikido/zen/sinks/em_http.rb +2 -4
- data/lib/aikido/zen/sinks/excon.rb +2 -4
- data/lib/aikido/zen/sinks/file.rb +1 -1
- data/lib/aikido/zen/sinks/http.rb +2 -4
- data/lib/aikido/zen/sinks/httpclient.rb +2 -4
- data/lib/aikido/zen/sinks/httpx.rb +2 -4
- data/lib/aikido/zen/sinks/kernel.rb +1 -1
- data/lib/aikido/zen/sinks/mysql2.rb +1 -1
- data/lib/aikido/zen/sinks/net_http.rb +2 -4
- data/lib/aikido/zen/sinks/patron.rb +2 -4
- data/lib/aikido/zen/sinks/pg.rb +1 -1
- data/lib/aikido/zen/sinks/resolv.rb +1 -1
- data/lib/aikido/zen/sinks/socket.rb +1 -1
- data/lib/aikido/zen/sinks/sqlite3.rb +1 -1
- data/lib/aikido/zen/sinks/trilogy.rb +1 -1
- data/lib/aikido/zen/sinks/typhoeus.rb +2 -4
- data/lib/aikido/zen/version.rb +1 -1
- data/lib/aikido/zen.rb +8 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c01eda1d0ea486bf41d00d6efc05f064ad13b3ae71e112af02b1c01e3d164592
|
|
4
|
+
data.tar.gz: 222be5ac12985a6f855335a3433d32632704d6ebb1c0f7249fe9bb85492554f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4645441dc4301bccc6c09347f864149b071f9cdcd16811309dd7a7e9650848ff90a559ebc576338e7bc8bc41fc3f4d4c2deaa096112628da02bf71619e69ad77
|
|
7
|
+
data.tar.gz: 8e7e3717a0a38dcb0d0c38abaf2142569404917f0fff5da720fcd4d180eebb27799d7b836a5222389a33a1a963e431f2459fe2299ab1d6e0c85234b615ce997d
|
data/docs/request-bypassing.md
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
# Bypass Zen for a specific request
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
>
|
|
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.
|
data/lib/aikido/zen/agent.rb
CHANGED
|
@@ -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
|
data/lib/aikido/zen/collector.rb
CHANGED
|
@@ -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
|
|
data/lib/aikido/zen/config.rb
CHANGED
|
@@ -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://
|
|
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 =
|
|
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
|
|
data/lib/aikido/zen/sink.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
60
|
+
if Aikido::Zen.block_outbound?(connection)
|
|
63
61
|
Sinks::DSL.presafe do
|
|
64
62
|
raise OutboundConnectionBlockedError.new(connection)
|
|
65
63
|
end
|
|
@@ -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
|
|
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
|
|
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
|
|
59
|
+
if Aikido::Zen.block_outbound?(connection)
|
|
62
60
|
Sinks::DSL.presafe do
|
|
63
61
|
raise OutboundConnectionBlockedError.new(connection)
|
|
64
62
|
end
|
|
@@ -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
|
|
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
|
|
63
|
+
if Aikido::Zen.block_outbound?(connection)
|
|
66
64
|
Sinks::DSL.presafe do
|
|
67
65
|
raise OutboundConnectionBlockedError.new(connection)
|
|
68
66
|
end
|
data/lib/aikido/zen/sinks/pg.rb
CHANGED
|
@@ -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
|
|
@@ -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
|
])
|
|
@@ -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
|
|
30
|
+
if Aikido::Zen.block_outbound?(connection)
|
|
33
31
|
raise OutboundConnectionBlockedError.new(connection)
|
|
34
32
|
end
|
|
35
33
|
end
|
data/lib/aikido/zen/version.rb
CHANGED
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.
|
|
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-
|
|
11
|
+
date: 2026-08-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|