aikido-zen 1.2.3-aarch64-linux → 1.3.0.beta.1-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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '018f7a0fedee31b326cdef13a4d04581636119525b75855aba889f4d0bfc7c3b'
4
- data.tar.gz: bece462adb907db93958c400ff677131027e892cf9ed59f043532d1aa4a29f8c
3
+ metadata.gz: a9731f6fde05155a7a4be45a4d4188b298238a12c825e99c16e1299d89494bbd
4
+ data.tar.gz: 25177ade099650dab6af4b9a7bdaecb0d736f2ae2814f3a0c657d80f1bb7e1a1
5
5
  SHA512:
6
- metadata.gz: ab2c5c1eda478b46d7d7182ed2eaf8f664f5aeecaa4b70f05a27f7d81b12152079bfc59465d0bdd2a5f38cdbaf1f3db5cdc3142bb9a24bee08963272c39b9b20
7
- data.tar.gz: 979e3e2d7a61b98e1961691edb5598a0fffa76b1321cccc1b00cfef783ba1557182b03b97db7660391284a2ebca39d5a6f2cf363c5c2e965ab5159a58b4c25c9
6
+ metadata.gz: f61e0ffbe02c7803fb6b2446569d97817653100d33a6a5f90f7018580e53695e8a4a732f44ec9ef296b442808f1524ee8ce36934f9ca2093abee5e172e1e9669
7
+ data.tar.gz: c844e9c259592413fcac64f09f70cd131462c6900c8541d8a866f98046077949d3ea083b68b698c18b1a4e089d89ea8e8b71d818c35d2513bcbc0ee5a437f7e9
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "benchmark"
4
+ require "aikido-zen"
5
+
6
+ random_ipv4_ranges = (0..).lazy.map do
7
+ ip_int = rand(2**32)
8
+ prefix = rand(8..32)
9
+ network = IPAddr.new(ip_int, Socket::AF_INET).mask(prefix)
10
+ "#{network}/#{prefix}"
11
+ end
12
+
13
+ random_ipv6_ranges = (0..).lazy.map do
14
+ ip_int = rand(2**128)
15
+ prefix = rand(1..128)
16
+ network = IPAddr.new(ip_int, Socket::AF_INET6).mask(prefix)
17
+ "#{network}/#{prefix}"
18
+ end
19
+
20
+ random_ip_ranges = (0..).lazy.map do
21
+ (rand < 0.5) ? random_ipv4_ranges.next : random_ipv6_ranges.next
22
+ end
23
+
24
+ if __FILE__ == $0
25
+ ip_ranges = random_ip_ranges.take(1000)
26
+
27
+ ip_list = Aikido::Zen::RuntimeSettings::IPList.from_json({
28
+ "key" => "key",
29
+ "source" => "source",
30
+ "description" => "description",
31
+ "ips" => ip_ranges
32
+ })
33
+
34
+ result = Benchmark.measure do
35
+ ip_ranges.all? { |ip_range| ip_list.include?(ip_range) }
36
+ end
37
+
38
+ puts result
39
+ end
@@ -160,6 +160,9 @@ module Aikido::Zen
160
160
  if Aikido::Zen.runtime_settings.update_from_runtime_config_json(response)
161
161
  updated_settings!
162
162
  @config.logger.info("Updated runtime settings after heartbeat")
163
+
164
+ Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(@api_client.fetch_runtime_firewall_lists)
165
+ @config.logger.info("Updated runtime firewall list after heartbeat")
163
166
  end
164
167
  end
165
168
  end
@@ -95,6 +95,33 @@ module Aikido::Zen
95
95
  end
96
96
  end
97
97
 
98
+ class TrackIPList < Event
99
+ register "track_ip_list"
100
+
101
+ def self.from_json(data)
102
+ new(data[:ip_list_keys])
103
+ end
104
+
105
+ def initialize(ip_list_keys)
106
+ super()
107
+ @ip_list_keys = ip_list_keys
108
+ end
109
+
110
+ def as_json
111
+ super.update({
112
+ ip_list_keys: @ip_list_keys
113
+ })
114
+ end
115
+
116
+ def handle(collector)
117
+ collector.handle_track_ip_list(@ip_list_keys)
118
+ end
119
+
120
+ def inspect
121
+ "#<#{self.class.name} #{@ip_list_keys.inspect}>"
122
+ end
123
+ end
124
+
98
125
  class TrackAttackWave < Event
99
126
  register "track_attack_wave"
100
127
 
@@ -8,7 +8,7 @@ module Aikido::Zen
8
8
  # Tracks information about how the Aikido Agent is used in the app.
9
9
  class Collector::Stats
10
10
  # @!visibility private
11
- attr_reader :started_at, :ended_at, :requests, :aborted_requests, :rate_limited_requests, :user_agents, :attack_waves, :blocked_attack_waves, :sinks
11
+ attr_reader :started_at, :ended_at, :requests, :aborted_requests, :rate_limited_requests, :user_agents, :ip_lists, :attack_waves, :blocked_attack_waves, :sinks
12
12
 
13
13
  # @!visibility private
14
14
  attr_writer :ended_at
@@ -22,6 +22,7 @@ module Aikido::Zen
22
22
  @aborted_requests = 0
23
23
  @rate_limited_requests = 0
24
24
  @user_agents = Hash.new { |h, k| h[k] = 0 }
25
+ @ip_lists = Hash.new { |h, k| h[k] = 0 }
25
26
  @attack_waves = 0
26
27
  @blocked_attack_waves = 0
27
28
  @sinks = Hash.new { |h, k| h[k] = Collector::SinkStats.new(k, @config) }
@@ -79,6 +80,12 @@ module Aikido::Zen
79
80
  user_agent_keys&.each { |user_agent_key| @user_agents[user_agent_key] += 1 }
80
81
  end
81
82
 
83
+ # @param user_agent_keys [Array<String>] the user agent keys
84
+ # @return [void]
85
+ def add_ip_list(ip_list_keys)
86
+ ip_list_keys&.each { |ip_list_key| @ip_lists[ip_list_key] += 1 }
87
+ end
88
+
82
89
  # @param being_blocked [Boolean] whether the Agent blocked the request
83
90
  # @return [void]
84
91
  def add_attack_wave(being_blocked:)
@@ -127,6 +134,9 @@ module Aikido::Zen
127
134
  },
128
135
  userAgents: {
129
136
  breakdown: @user_agents
137
+ },
138
+ ipAddresses: {
139
+ breakdown: @ip_lists
130
140
  }
131
141
  }
132
142
  end
@@ -101,10 +101,10 @@ module Aikido::Zen
101
101
 
102
102
  # Track stats about monitored and blocked user agents
103
103
  #
104
- # @param [Array<String>, nil] the user agent keys
104
+ # @param user_agent_keys [Array<String>, nil] the user agent keys
105
105
  # @return [void]
106
106
  def track_user_agent(user_agent_keys)
107
- return if user_agent_keys.nil?
107
+ return if user_agent_keys.nil? || user_agent_keys.empty?
108
108
 
109
109
  add_event(Events::TrackUserAgent.new(user_agent_keys))
110
110
  end
@@ -113,6 +113,20 @@ module Aikido::Zen
113
113
  synchronize(@stats) { |stats| stats.add_user_agent(user_agent_keys) }
114
114
  end
115
115
 
116
+ # Track stats about blocked and monitored IP lists
117
+ #
118
+ # @param ip_list_keys [Array<String>, nil]
119
+ # @return [void]
120
+ def track_ip_list(ip_list_keys)
121
+ return if ip_list_keys.nil? || ip_list_keys.empty?
122
+
123
+ add_event(Events::TrackIPList.new(ip_list_keys))
124
+ end
125
+
126
+ def handle_track_ip_list(ip_list_keys)
127
+ synchronize(@stats) { |stats| stats.add_ip_list(ip_list_keys) }
128
+ end
129
+
116
130
  # Track stats about an attack detected by our scanners.
117
131
  #
118
132
  # @param attack [Aikido::Zen::Events::AttackWave]
@@ -322,10 +322,18 @@ module Aikido::Zen
322
322
  DEFAULT_DETACHED_AGENT_SOCKET_PATH = "aikido-detached-agent.%h.sock"
323
323
 
324
324
  # @!visibility private
325
- DEFAULT_BLOCKED_RESPONDER = ->(request, blocking_type) do
325
+ DEFAULT_BLOCKED_RESPONDER = ->(request, blocking_type, reason = nil) do
326
326
  message = case blocking_type
327
327
  when :ip
328
- format("Your IP address is not allowed to access this resource. (Your IP: %s)", request.ip)
328
+ "Your IP address is not allowed to access this resource. (Your IP: #{request.ip})"
329
+ when :ip_allowed_list
330
+ "Your IP address is not allowed to access this resource. (Your IP: #{request.client_ip})"
331
+ when :ip_blocked_list
332
+ if reason.nil?
333
+ "Your IP is blocked."
334
+ else
335
+ "Your IP is blocked due to #{reason}."
336
+ end
329
337
  when :user_agent
330
338
  "You are not allowed to access this resource because you have been identified as a bot."
331
339
  else
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ module Middleware
5
+ class IPListChecker
6
+ def initialize(app, zen: Aikido::Zen, config: zen.config, settings: zen.runtime_settings)
7
+ @app = app
8
+ @zen = zen
9
+ @config = config
10
+ @settings = settings
11
+ end
12
+
13
+ def call(env)
14
+ request = Aikido::Zen::Middleware.request_from(env)
15
+
16
+ client_ip = request.client_ip
17
+
18
+ return @app.call(env) if bypassed_ip?(client_ip)
19
+
20
+ if !@settings.allowed_ip?(client_ip)
21
+ return @config.blocked_responder.call(request, :ip_allowed_list)
22
+ end
23
+
24
+ monitored_ip_list_keys = @settings.monitored_ip_list_keys(client_ip)
25
+ @zen.track_ip_list(monitored_ip_list_keys)
26
+
27
+ blocked_ip_lists = @settings.blocked_ip_lists.filter { |ip_list| ip_list.include?(client_ip) }
28
+
29
+ if !blocked_ip_lists.empty?
30
+ @zen.track_ip_list(blocked_ip_lists.map(&:key))
31
+
32
+ return @config.blocked_responder.call(
33
+ request,
34
+ :ip_blocked_list,
35
+ blocked_ip_lists.first.description
36
+ )
37
+ end
38
+
39
+ @app.call(env)
40
+ end
41
+
42
+ def bypassed_ip?(client_ip)
43
+ @settings.bypassed_ips.include?(client_ip)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -17,6 +17,7 @@ module Aikido::Zen
17
17
  Aikido::Zen::Middleware::ForkDetector,
18
18
  Aikido::Zen::Middleware::ContextSetter,
19
19
  Aikido::Zen::Middleware::AllowedAddressChecker,
20
+ Aikido::Zen::Middleware::IPListChecker,
20
21
  Aikido::Zen::Middleware::UserAgentChecker,
21
22
  Aikido::Zen::Middleware::AttackProtector,
22
23
  Aikido::Zen::Middleware::AttackWaveProtector,
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ class RuntimeSettings::IPList
5
+ attr_reader :key
6
+ attr_reader :source
7
+ attr_reader :description
8
+ attr_reader :ips
9
+
10
+ attr_reader :ipv4_ranges
11
+ attr_reader :ipv6_ranges
12
+
13
+ def self.from_json(data)
14
+ new(
15
+ key: data["key"],
16
+ source: data["source"],
17
+ description: data["description"],
18
+ ips: Array(data["ips"]).map { |ip| IPAddr.new(ip) }
19
+ )
20
+ end
21
+
22
+ def initialize(key:, source:, description:, ips:)
23
+ @key = key
24
+ @source = source
25
+ @description = description
26
+ @ips = ips
27
+
28
+ @ipv4_ranges = []
29
+ @ipv6_ranges = []
30
+
31
+ ips.each do |ip|
32
+ if ip.ipv4?
33
+ @ipv4_ranges << ip.to_range
34
+ elsif ip.ipv6?
35
+ @ipv6_ranges << ip.to_range
36
+ else
37
+ raise ArgumentError, "Unsupported IP address family: #{ip.inspect}"
38
+ end
39
+ end
40
+
41
+ @ipv4_ranges.sort_by!(&:begin)
42
+ @ipv6_ranges.sort_by!(&:begin)
43
+ end
44
+
45
+ def inspect
46
+ "#<#{self.class} #{@key}>"
47
+ end
48
+
49
+ def include?(ip)
50
+ native_ip = nativize_ip(ip)
51
+ return false if native_ip.nil?
52
+
53
+ if native_ip.ipv4?
54
+ ranges_cover?(@ipv4_ranges, native_ip)
55
+ elsif native_ip.ipv6?
56
+ ranges_cover?(@ipv6_ranges, native_ip)
57
+ else
58
+ raise ArgumentError, "Unsupported IP address family: #{ip.inspect}"
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def nativize_ip(ip)
65
+ case ip
66
+ when IPAddr
67
+ ip.native
68
+ when String
69
+ begin
70
+ IPAddr.new(ip).native
71
+ rescue IPAddr::InvalidAddressError
72
+ nil
73
+ end
74
+ when nil
75
+ nil
76
+ else
77
+ raise ArgumentError, "no explicit conversion of #{ip.class} to IPAddr"
78
+ end
79
+ end
80
+
81
+ def ranges_cover?(ranges, ip)
82
+ index = ranges.bsearch_index { |range| range.begin > ip }
83
+ index = index ? index - 1 : ranges.size - 1
84
+ return false if index < 0
85
+
86
+ ranges[index].cover?(ip)
87
+ end
88
+ end
89
+ end
@@ -20,6 +20,8 @@ module Aikido::Zen
20
20
 
21
21
  def include?(ip)
22
22
  native_ip = nativize_ip(ip)
23
+ return false if native_ip.nil?
24
+
23
25
  @ips.any? { |pattern| pattern === native_ip }
24
26
  end
25
27
  alias_method :===, :include?
@@ -11,11 +11,14 @@ module Aikido::Zen
11
11
  #
12
12
  # You can subscribe to changes with +#add_observer(object, func_name)+, which
13
13
  # will call the function passing the settings as an argument
14
- RuntimeSettings = Struct.new(:updated_at, :heartbeat_interval, :endpoints, :blocked_user_ids, :bypassed_ips, :received_any_stats, :blocking_mode, :blocked_user_agent_regexp, :monitored_user_agent_regexp, :user_agent_details, :block_new_outbound, :domains, :excluded_user_ids_from_rate_limiting) do
14
+ RuntimeSettings = Struct.new(:updated_at, :heartbeat_interval, :endpoints, :blocked_user_ids, :bypassed_ips, :received_any_stats, :blocking_mode, :blocked_user_agent_regexp, :monitored_user_agent_regexp, :user_agent_details, :blocked_ip_lists, :allowed_ip_lists, :monitored_ip_lists, :block_new_outbound, :domains, :excluded_user_ids_from_rate_limiting) do
15
15
  def initialize(*)
16
16
  super
17
17
  self.endpoints ||= RuntimeSettings::Endpoints.new
18
18
  self.bypassed_ips ||= RuntimeSettings::IPSet.new
19
+ self.blocked_ip_lists ||= []
20
+ self.allowed_ip_lists ||= []
21
+ self.monitored_ip_lists ||= []
19
22
  self.domains ||= RuntimeSettings::Domains.new
20
23
  end
21
24
 
@@ -42,6 +45,15 @@ module Aikido::Zen
42
45
  # @!attribute [rw] blocking_mode
43
46
  # @return [Boolean]
44
47
 
48
+ # @!attribute [rw] blocked_ip_lists
49
+ # @return [Array<Aikido::Zen::RuntimeSettings::IPList>]
50
+
51
+ # @!attribute [rw] allowed_ip_lists
52
+ # @return [Array<Aikido::Zen::RuntimeSettings::IPList>]
53
+
54
+ # @!attribute [rw] monitored_ip_lists
55
+ # @return [Array<Aikido::Zen::RuntimeSettings::IPList>]
56
+
45
57
  # @!attribute [rw] blocked_user_agent_regexp
46
58
  # @return [Regexp]
47
59
 
@@ -112,6 +124,24 @@ module Aikido::Zen
112
124
  pattern: pattern
113
125
  }
114
126
  end
127
+
128
+ self.blocked_ip_lists = []
129
+
130
+ data["blockedIPAddresses"]&.each do |ip_list|
131
+ blocked_ip_lists << RuntimeSettings::IPList.from_json(ip_list)
132
+ end
133
+
134
+ self.allowed_ip_lists = []
135
+
136
+ data["allowedIPAddresses"]&.each do |ip_list|
137
+ allowed_ip_lists << RuntimeSettings::IPList.from_json(ip_list)
138
+ end
139
+
140
+ self.monitored_ip_lists = []
141
+
142
+ data["monitoredIPAddresses"]&.each do |ip_list|
143
+ monitored_ip_lists << RuntimeSettings::IPList.from_json(ip_list)
144
+ end
115
145
  end
116
146
 
117
147
  # Construct a regular expression from the non-nil and non-empty string,
@@ -165,9 +195,25 @@ module Aikido::Zen
165
195
  def user_agent_keys(user_agent)
166
196
  return [] if user_agent_details.nil?
167
197
 
168
- user_agent_details
169
- .filter { |record| record[:pattern].match?(user_agent) }
170
- .map { |record| record[:key] }
198
+ user_agent_details.filter_map { |record| record[:key] if record[:pattern].match?(user_agent) }
199
+ end
200
+
201
+ def allowed_ip?(ip)
202
+ allowed_ip_lists.empty? || allowed_ip_lists.any? { |ip_list| ip_list.include?(ip) }
203
+ end
204
+
205
+ def blocked_ip?(ip)
206
+ blocked_ip_lists.any? { |ip_list| ip_list.include?(ip) }
207
+ end
208
+
209
+ def monitored_ip?(ip)
210
+ monitored_ip_lists.any? { |ip_list| ip_list.include?(ip) }
211
+ end
212
+
213
+ def monitored_ip_list_keys(ip)
214
+ return [] if ip.nil?
215
+
216
+ monitored_ip_lists.filter_map { |ip_list| ip_list.key if ip_list.include?(ip) }
171
217
  end
172
218
 
173
219
  def block_outbound?(connection)
@@ -181,5 +227,6 @@ module Aikido::Zen
181
227
  end
182
228
 
183
229
  require_relative "runtime_settings/ip_set"
230
+ require_relative "runtime_settings/ip_list"
184
231
  require_relative "runtime_settings/endpoints"
185
232
  require_relative "runtime_settings/domains"
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Aikido
4
4
  module Zen
5
- VERSION = "1.2.3"
5
+ VERSION = "1.3.0.beta.1"
6
6
 
7
7
  # The version of libzen_internals that we build against.
8
8
  LIBZEN_VERSION = "0.1.60"
data/lib/aikido/zen.rb CHANGED
@@ -17,6 +17,7 @@ require_relative "zen/middleware/middleware"
17
17
  require_relative "zen/middleware/fork_detector"
18
18
  require_relative "zen/middleware/context_setter"
19
19
  require_relative "zen/middleware/allowed_address_checker"
20
+ require_relative "zen/middleware/ip_list_checker"
20
21
  require_relative "zen/middleware/user_agent_checker"
21
22
  require_relative "zen/middleware/attack_protector"
22
23
  require_relative "zen/middleware/attack_wave_protector"
@@ -133,7 +134,7 @@ module Aikido
133
134
  collector.track_rate_limited_request
134
135
  end
135
136
 
136
- # Track monitored and blocked user agents.
137
+ # Track blocked and monitored user agents.
137
138
  #
138
139
  # @param user_agent_keys [Array<String>, nil] the user agent keys
139
140
  # from matching runtime firewall list user agent details.
@@ -142,6 +143,15 @@ module Aikido
142
143
  collector.track_user_agent(user_agent_keys)
143
144
  end
144
145
 
146
+ # Track blocked and monitored IP lists.
147
+ #
148
+ # @param ip_list_keys [Array<String>, nil] the IP list keys
149
+ # from matching runtime firewall list IP lists.
150
+ # @return [void]
151
+ def self.track_ip_list(ip_list_keys)
152
+ collector.track_ip_list(ip_list_keys)
153
+ end
154
+
145
155
  # Track statistics about an attack wave the app is handling.
146
156
  #
147
157
  # @param attack_wave [Aikido::Zen::Events::AttackWave]
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.2.3
4
+ version: 1.3.0.beta.1
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Aikido Security
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-14 00:00:00.000000000 Z
11
+ date: 2026-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -86,6 +86,7 @@ files:
86
86
  - README.md
87
87
  - Rakefile
88
88
  - benchmarks/README.md
89
+ - benchmarks/ip_list/benchmark.rb
89
90
  - benchmarks/rails7.1_benchmark.js
90
91
  - benchmarks/rails7.1_sql_injection.js
91
92
  - docs/banner.svg
@@ -131,6 +132,7 @@ files:
131
132
  - lib/aikido/zen/middleware/attack_wave_protector.rb
132
133
  - lib/aikido/zen/middleware/context_setter.rb
133
134
  - lib/aikido/zen/middleware/fork_detector.rb
135
+ - lib/aikido/zen/middleware/ip_list_checker.rb
134
136
  - lib/aikido/zen/middleware/middleware.rb
135
137
  - lib/aikido/zen/middleware/rack_throttler.rb
136
138
  - lib/aikido/zen/middleware/request_tracker.rb
@@ -157,6 +159,7 @@ files:
157
159
  - lib/aikido/zen/runtime_settings/domain_settings.rb
158
160
  - lib/aikido/zen/runtime_settings/domains.rb
159
161
  - lib/aikido/zen/runtime_settings/endpoints.rb
162
+ - lib/aikido/zen/runtime_settings/ip_list.rb
160
163
  - lib/aikido/zen/runtime_settings/ip_set.rb
161
164
  - lib/aikido/zen/runtime_settings/protection_settings.rb
162
165
  - lib/aikido/zen/runtime_settings/rate_limit_settings.rb