aikido-zen 1.9.0.beta.4-arm64-linux → 1.9.1-arm64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/aikido/zen/agent.rb +16 -3
- data/lib/aikido/zen/api_client.rb +27 -21
- data/lib/aikido/zen/api_stream.rb +5 -3
- data/lib/aikido/zen/attack.rb +7 -1
- data/lib/aikido/zen/config.rb +11 -22
- data/lib/aikido/zen/sink.rb +23 -19
- data/lib/aikido/zen/system_info.rb +54 -9
- data/lib/aikido/zen/version.rb +1 -1
- data/lib/aikido/zen.rb +1 -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: df231e175ddfcbc1a8c21d8c0e23575d13de65a47214a0c191b9b94bdc7d7b51
|
|
4
|
+
data.tar.gz: 257407c5c9630af6ea66536e4ffc2ae9e8042b5eba98eb53f5baa61bf04d1ba8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d758d8a41ab1387576a0202135df2526f62aaa0d594104cce44812051e3941b24edcc7d06d86a9837d8542eab521c08f5d72513b687619c2609183f403a70b3f
|
|
7
|
+
data.tar.gz: 23997d7f758c63551deff8cefb2d067fa6c0816744f247f6ab15a89d7554aa6f9bfef0e8420a81ec4c66ad1043b7b6702990a6402cb64aed01063f0061478aae
|
data/lib/aikido/zen/agent.rb
CHANGED
|
@@ -136,9 +136,7 @@ module Aikido::Zen
|
|
|
136
136
|
|
|
137
137
|
# Recording the attack must never prevent us from blocking it.
|
|
138
138
|
begin
|
|
139
|
-
@config.logger.error(
|
|
140
|
-
format("Zen has %s a %s: %s", attack.blocked? ? "blocked" : "detected", attack.humanized_name, attack.as_json.to_json)
|
|
141
|
-
)
|
|
139
|
+
@config.logger.error(attack_log_message(attack))
|
|
142
140
|
|
|
143
141
|
report(Events::Attack.new(attack: attack)) if @api_client.can_make_requests?
|
|
144
142
|
|
|
@@ -227,6 +225,21 @@ module Aikido::Zen
|
|
|
227
225
|
|
|
228
226
|
private
|
|
229
227
|
|
|
228
|
+
def attack_log_message(attack)
|
|
229
|
+
input = attack.as_json
|
|
230
|
+
|
|
231
|
+
format(
|
|
232
|
+
"Zen has %s a %s: %s(...) originating from %s%s",
|
|
233
|
+
attack.blocked? ? "blocked" : "detected",
|
|
234
|
+
attack.humanized_name,
|
|
235
|
+
attack.operation,
|
|
236
|
+
input["source"],
|
|
237
|
+
# The path is built from names in the request, so it can't be trusted
|
|
238
|
+
# not to split the log line.
|
|
239
|
+
input["path"].to_s.gsub(/[\r\n]/, " ")
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
|
|
230
243
|
def settings_updated(event)
|
|
231
244
|
updated_at = Time.at(event[:data]["configUpdatedAt"].to_i)
|
|
232
245
|
|
|
@@ -16,12 +16,6 @@ module Aikido::Zen
|
|
|
16
16
|
@config = config
|
|
17
17
|
@system_info = system_info
|
|
18
18
|
@rate_limiter = rate_limiter
|
|
19
|
-
|
|
20
|
-
@should_fetch_settings_endpoint = @config.realtime_endpoint
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def should_fetch_settings_endpoint=(uri)
|
|
24
|
-
@should_fetch_settings_endpoint = URI(uri)
|
|
25
19
|
end
|
|
26
20
|
|
|
27
21
|
# @return [Boolean] whether we have a configured token.
|
|
@@ -44,16 +38,16 @@ module Aikido::Zen
|
|
|
44
38
|
|
|
45
39
|
response = request(
|
|
46
40
|
Net::HTTP::Get.new("/config", default_headers),
|
|
47
|
-
base_url: @
|
|
41
|
+
base_url: @config.realtime_endpoint,
|
|
42
|
+
timeout: @config.api_config_timeout
|
|
48
43
|
)
|
|
49
44
|
|
|
50
45
|
new_updated_at = Time.at(response["configUpdatedAt"].to_i)
|
|
51
46
|
new_updated_at > last_updated_at
|
|
52
47
|
end
|
|
53
48
|
|
|
54
|
-
# Fetches the runtime config from the server.
|
|
55
|
-
# other low-
|
|
56
|
-
# times, after which it will raise an error.
|
|
49
|
+
# Fetches the runtime config from the server. Requests are never retried;
|
|
50
|
+
# on a timeout or other low-level error this raises an error.
|
|
57
51
|
#
|
|
58
52
|
# @return [Hash] decoded JSON response from the server with the runtime
|
|
59
53
|
# settings.
|
|
@@ -61,7 +55,11 @@ module Aikido::Zen
|
|
|
61
55
|
def fetch_runtime_config
|
|
62
56
|
@config.logger.debug("Fetching new runtime config")
|
|
63
57
|
|
|
64
|
-
request(
|
|
58
|
+
request(
|
|
59
|
+
Net::HTTP::Get.new("/api/runtime/config", default_headers),
|
|
60
|
+
base_url: @config.api_endpoint,
|
|
61
|
+
timeout: @config.api_config_timeout
|
|
62
|
+
)
|
|
65
63
|
end
|
|
66
64
|
|
|
67
65
|
def fetch_runtime_firewall_lists
|
|
@@ -71,7 +69,11 @@ module Aikido::Zen
|
|
|
71
69
|
"Accept-Encoding" => "gzip"
|
|
72
70
|
})
|
|
73
71
|
|
|
74
|
-
request(
|
|
72
|
+
request(
|
|
73
|
+
Net::HTTP::Get.new("/api/runtime/firewall/lists", headers),
|
|
74
|
+
base_url: @config.api_endpoint,
|
|
75
|
+
timeout: @config.api_config_timeout
|
|
76
|
+
)
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
# @overload report(event)
|
|
@@ -111,7 +113,7 @@ module Aikido::Zen
|
|
|
111
113
|
@config.json_encoder.call(event)
|
|
112
114
|
end
|
|
113
115
|
|
|
114
|
-
request(req)
|
|
116
|
+
request(req, base_url: @config.api_endpoint, timeout: @config.api_events_timeout)
|
|
115
117
|
rescue Aikido::Zen::RateLimitedError
|
|
116
118
|
@rate_limiter.open!
|
|
117
119
|
raise
|
|
@@ -121,15 +123,16 @@ module Aikido::Zen
|
|
|
121
123
|
# response.
|
|
122
124
|
#
|
|
123
125
|
# @param request [Net::HTTPRequest]
|
|
124
|
-
# @param base_url [URI] which API to use.
|
|
126
|
+
# @param base_url [URI] which API to use.
|
|
127
|
+
# @param timeout [Numeric] seconds to wait on each connect, read and write.
|
|
125
128
|
#
|
|
126
129
|
# @return [Object] the result of decoding the JSON response from the server.
|
|
127
130
|
#
|
|
128
131
|
# @raise [Aikido::Zen::APIError] in case of a 4XX or 5XX response.
|
|
129
132
|
# @raise [Aikido::Zen::NetworkError] if an error occurs trying to make the
|
|
130
133
|
# request.
|
|
131
|
-
private def request(request, base_url:
|
|
132
|
-
Net::HTTP.start(base_url.host, base_url.port, http_settings(base_url)) do |http|
|
|
134
|
+
private def request(request, base_url:, timeout:)
|
|
135
|
+
Net::HTTP.start(base_url.host, base_url.port, http_settings(base_url, timeout)) do |http|
|
|
133
136
|
response = http.request(request)
|
|
134
137
|
|
|
135
138
|
case response
|
|
@@ -168,11 +171,14 @@ module Aikido::Zen
|
|
|
168
171
|
end
|
|
169
172
|
end
|
|
170
173
|
|
|
171
|
-
private def http_settings(base_url)
|
|
172
|
-
|
|
174
|
+
private def http_settings(base_url, timeout)
|
|
175
|
+
{
|
|
173
176
|
use_ssl: base_url.scheme == "https",
|
|
174
|
-
max_retries:
|
|
175
|
-
|
|
177
|
+
max_retries: 0,
|
|
178
|
+
open_timeout: timeout,
|
|
179
|
+
read_timeout: timeout,
|
|
180
|
+
write_timeout: timeout
|
|
181
|
+
}
|
|
176
182
|
end
|
|
177
183
|
|
|
178
184
|
private def default_headers
|
|
@@ -180,7 +186,7 @@ module Aikido::Zen
|
|
|
180
186
|
"Authorization" => @config.api_token,
|
|
181
187
|
"Accept" => "application/json",
|
|
182
188
|
"User-Agent" => "#{@system_info.library_name} v#{@system_info.library_version}"
|
|
183
|
-
}
|
|
189
|
+
}.merge(@system_info.agent_headers)
|
|
184
190
|
end
|
|
185
191
|
end
|
|
186
192
|
end
|
|
@@ -7,7 +7,9 @@ require "json"
|
|
|
7
7
|
module Aikido::Zen
|
|
8
8
|
class APIStream
|
|
9
9
|
def initialize(
|
|
10
|
-
|
|
10
|
+
zen: Aikido::Zen,
|
|
11
|
+
config: zen.config,
|
|
12
|
+
system_info: zen.system_info,
|
|
11
13
|
min_backoff: 5,
|
|
12
14
|
max_backoff: 60,
|
|
13
15
|
backoff_reset: 30,
|
|
@@ -16,6 +18,7 @@ module Aikido::Zen
|
|
|
16
18
|
read_timeout: 70
|
|
17
19
|
)
|
|
18
20
|
@config = config
|
|
21
|
+
@system_info = system_info
|
|
19
22
|
@min_backoff = min_backoff
|
|
20
23
|
@max_backoff = max_backoff
|
|
21
24
|
@backoff_reset = backoff_reset
|
|
@@ -123,8 +126,7 @@ module Aikido::Zen
|
|
|
123
126
|
request["Authorization"] = @token
|
|
124
127
|
request["Accept"] = "text/event-stream"
|
|
125
128
|
request["Cache-Control"] = "no-cache"
|
|
126
|
-
request[
|
|
127
|
-
request["X-Agent-Version"] = Aikido::Zen::VERSION
|
|
129
|
+
@system_info.agent_headers.each { |name, value| request[name] = value }
|
|
128
130
|
# Working around Net::HTTP cleverness; auto-negotiates gzip and buffers
|
|
129
131
|
# decompressed output internally.
|
|
130
132
|
request["Accept-Encoding"] = "identity"
|
data/lib/aikido/zen/attack.rb
CHANGED
|
@@ -43,13 +43,19 @@ module Aikido::Zen
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def as_json
|
|
46
|
-
{
|
|
46
|
+
attack_json = {
|
|
47
47
|
"kind" => kind,
|
|
48
48
|
"blocked" => blocked?,
|
|
49
49
|
"metadata" => metadata,
|
|
50
50
|
"operation" => @operation,
|
|
51
51
|
"stack" => @stack
|
|
52
52
|
}.compact.merge(input.as_json)
|
|
53
|
+
|
|
54
|
+
if (actor = @context&.request&.actor)
|
|
55
|
+
attack_json["user"] = {"id" => actor.id, "name" => actor.name}.compact
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
attack_json
|
|
53
59
|
end
|
|
54
60
|
|
|
55
61
|
def exception(*)
|
data/lib/aikido/zen/config.rb
CHANGED
|
@@ -45,8 +45,15 @@ module Aikido::Zen
|
|
|
45
45
|
# +https://guard.aikido.dev+.
|
|
46
46
|
attr_reader :realtime_endpoint
|
|
47
47
|
|
|
48
|
-
# @return [
|
|
49
|
-
|
|
48
|
+
# @return [Numeric] the timeout in seconds for reporting events to the
|
|
49
|
+
# API (+POST /api/runtime/events+). Applied to each connect, read and
|
|
50
|
+
# write wait. Defaults to 60 seconds.
|
|
51
|
+
attr_accessor :api_events_timeout
|
|
52
|
+
|
|
53
|
+
# @return [Numeric] the timeout in seconds for fetching config, runtime
|
|
54
|
+
# config and firewall lists from the API. Applied to each connect, read
|
|
55
|
+
# and write wait. Defaults to 30 seconds.
|
|
56
|
+
attr_accessor :api_config_timeout
|
|
50
57
|
|
|
51
58
|
# @return [Integer] the interval in seconds to poll the runtime API for
|
|
52
59
|
# settings changes. Defaults to evey 60 seconds.
|
|
@@ -254,7 +261,8 @@ module Aikido::Zen
|
|
|
254
261
|
self.api_token = ENV.fetch("AIKIDO_TOKEN", nil)
|
|
255
262
|
self.api_endpoint = ENV.fetch("AIKIDO_ENDPOINT") { regional_api_endpoint(api_token) }
|
|
256
263
|
self.realtime_endpoint = api_endpoint
|
|
257
|
-
self.
|
|
264
|
+
self.api_events_timeout = 60 # sec
|
|
265
|
+
self.api_config_timeout = 30 # sec
|
|
258
266
|
self.polling_interval = 60 # 1 min
|
|
259
267
|
self.initial_heartbeat_delays = [30, 60 * 2] # 30 sec, 2 min
|
|
260
268
|
self.extract_payloads_max_depth = 64
|
|
@@ -332,25 +340,6 @@ module Aikido::Zen
|
|
|
332
340
|
@logger.level = Logger::DEBUG if debugging
|
|
333
341
|
end
|
|
334
342
|
|
|
335
|
-
# @overload def api_timeouts=(timeouts)
|
|
336
|
-
# Configure granular connection timeouts for the Aikido Zen API. You
|
|
337
|
-
# can set any of these per call.
|
|
338
|
-
# @param timeouts [Hash]
|
|
339
|
-
# @option timeouts [Integer] :open_timeout Duration in seconds.
|
|
340
|
-
# @option timeouts [Integer] :read_timeout Duration in seconds.
|
|
341
|
-
# @option timeouts [Integer] :write_timeout Duration in seconds.
|
|
342
|
-
#
|
|
343
|
-
# @overload def api_timeouts=(duration)
|
|
344
|
-
# Configure the connection timeouts for the Aikido Zen API.
|
|
345
|
-
# @param duration [Integer] Duration in seconds to set for all three
|
|
346
|
-
# timeouts (open, read, and write).
|
|
347
|
-
def api_timeouts=(value)
|
|
348
|
-
value = {open_timeout: value, read_timeout: value, write_timeout: value} if value.respond_to?(:to_int)
|
|
349
|
-
|
|
350
|
-
@api_timeouts ||= {}
|
|
351
|
-
@api_timeouts.update(value)
|
|
352
|
-
end
|
|
353
|
-
|
|
354
343
|
def api_token_hash
|
|
355
344
|
return unless api_token
|
|
356
345
|
|
data/lib/aikido/zen/sink.rb
CHANGED
|
@@ -85,36 +85,40 @@ module Aikido::Zen
|
|
|
85
85
|
# @raise [Aikido::UnderAttackError] if an attack is detected and
|
|
86
86
|
# blocking_mode is enabled.
|
|
87
87
|
def scan(context: Aikido::Zen.current_context, **scan_params)
|
|
88
|
+
# Nested scans must return early without clearing the scanning state.
|
|
88
89
|
return if context&.scanning
|
|
90
|
+
|
|
89
91
|
context&.scanning = true
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
begin
|
|
94
|
+
return if context&.protection_disabled?
|
|
92
95
|
|
|
93
|
-
|
|
96
|
+
scan = Scan.new(sink: self, context: context)
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
scans_performed = 0
|
|
99
|
+
scan.perform do
|
|
100
|
+
result = nil
|
|
98
101
|
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
scanners.each do |scanner|
|
|
103
|
+
next if scanner.skips_on_nil_context? && context.nil?
|
|
101
104
|
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
result = scanner.call(scan: scan, sink: self, context: context, **scan_params)
|
|
106
|
+
scans_performed += 1
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
break result if result
|
|
109
|
+
rescue => error
|
|
110
|
+
scan.track_error(error, scanner)
|
|
111
|
+
end
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
result
|
|
114
|
+
end
|
|
112
115
|
|
|
113
|
-
|
|
116
|
+
@reporter.call(scan) if scans_performed > 0
|
|
114
117
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
scan
|
|
119
|
+
ensure
|
|
120
|
+
context&.scanning = false
|
|
121
|
+
end
|
|
118
122
|
end
|
|
119
123
|
end
|
|
120
124
|
end
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "socket"
|
|
4
|
+
require "securerandom"
|
|
4
5
|
require "rubygems"
|
|
5
6
|
|
|
6
7
|
require_relative "package"
|
|
7
8
|
|
|
8
9
|
module Aikido::Zen
|
|
9
|
-
# Provides information about the currently running
|
|
10
|
+
# Provides information about the currently running agent.
|
|
10
11
|
class SystemInfo
|
|
12
|
+
# @return [String] a unique identifier for the currently running agent.
|
|
13
|
+
attr_reader :session_id
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@session_id = generate_session_id
|
|
17
|
+
end
|
|
18
|
+
|
|
11
19
|
def attacks_block_requests?
|
|
12
20
|
!!Aikido::Zen.blocking_mode?
|
|
13
21
|
end
|
|
@@ -28,8 +36,29 @@ module Aikido::Zen
|
|
|
28
36
|
RUBY_VERSION
|
|
29
37
|
end
|
|
30
38
|
|
|
39
|
+
# @return [String, nil] the hostname, or nil if it cannot be determined.
|
|
31
40
|
def hostname
|
|
32
|
-
@hostname ||=
|
|
41
|
+
@hostname ||= begin
|
|
42
|
+
name = Socket.gethostname.to_s
|
|
43
|
+
name unless name.empty?
|
|
44
|
+
rescue SystemCallError
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Headers sent with every request to the Aikido server to describe and
|
|
50
|
+
# identify the currently running agent.
|
|
51
|
+
#
|
|
52
|
+
# @return [Hash{String => String}] the agent headers, keyed by header name.
|
|
53
|
+
def agent_headers
|
|
54
|
+
{
|
|
55
|
+
"X-Agent-Platform" => "ruby",
|
|
56
|
+
"X-Agent-Library" => library_name,
|
|
57
|
+
"X-Agent-Version" => library_version,
|
|
58
|
+
"X-Agent-Hostname" => hostname || "unknown",
|
|
59
|
+
"X-Agent-IP-Address" => ip_address || "unknown",
|
|
60
|
+
"X-Agent-Session-Id" => session_id
|
|
61
|
+
}
|
|
33
62
|
end
|
|
34
63
|
|
|
35
64
|
# @return [Array<Aikido::Zen::Package>] a list of loaded rubygems that are
|
|
@@ -41,14 +70,18 @@ module Aikido::Zen
|
|
|
41
70
|
.select(&:supported?)
|
|
42
71
|
end
|
|
43
72
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
73
|
+
# Find the first non-loopback IP address, preferring IPv4.
|
|
74
|
+
#
|
|
75
|
+
# @return [String, nil] the IP address, or nil if it cannot be determined.
|
|
47
76
|
def ip_address
|
|
48
|
-
@ip_address ||=
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
77
|
+
@ip_address ||= begin
|
|
78
|
+
address = Socket.ip_address_list
|
|
79
|
+
.reject { |ip| ip.ipv4_loopback? || ip.ipv6_loopback? || ip.unix? }
|
|
80
|
+
.min_by { |ip| ip.ipv4? ? 0 : 1 }
|
|
81
|
+
address&.ip_address
|
|
82
|
+
rescue SystemCallError
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
52
85
|
end
|
|
53
86
|
|
|
54
87
|
def os_name
|
|
@@ -76,5 +109,17 @@ module Aikido::Zen
|
|
|
76
109
|
"preventedPrototypePollution" => false
|
|
77
110
|
}
|
|
78
111
|
end
|
|
112
|
+
|
|
113
|
+
private def generate_session_id
|
|
114
|
+
# Code coverage is disabled here because `SecureRandom.uuid_v7` is only
|
|
115
|
+
# defined in Ruby 3.3+.
|
|
116
|
+
# :nocov:
|
|
117
|
+
if SecureRandom.respond_to?(:uuid_v7)
|
|
118
|
+
SecureRandom.uuid_v7
|
|
119
|
+
else
|
|
120
|
+
SecureRandom.uuid
|
|
121
|
+
end
|
|
122
|
+
# :nocov:
|
|
123
|
+
end
|
|
79
124
|
end
|
|
80
125
|
end
|
data/lib/aikido/zen/version.rb
CHANGED
data/lib/aikido/zen.rb
CHANGED
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.9.
|
|
4
|
+
version: 1.9.1
|
|
5
5
|
platform: arm64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Aikido Security
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|