aikido-zen 1.0.1.beta.5-x86_64-darwin → 1.0.2.beta.2-x86_64-darwin

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: b1393fa45d0bfc2d1b2e9297c8f723a722a3a2c7bc23ba9154d7970d438a7641
4
- data.tar.gz: 603014b3cac01c79a4ab4f50fbee852bfda042b860292bfb7aefb1c8002d12a4
3
+ metadata.gz: fc66399239a086499db7f6a4d78f0bb783202c9c63f9d3aada803e62b025b84a
4
+ data.tar.gz: c5617a0b3c1e26d5ea2fc594d924a200baaf86be479b6858dea0c199539ef7e3
5
5
  SHA512:
6
- metadata.gz: 14bc6ccd5afb21b194fff8992999f07da8cb8689bf9f5af89b19a5a10a5db2d005294e792e6a3aab67cd5af167480fa6a702e70ed4d3c38f05cc41b157a3dc08
7
- data.tar.gz: b703d4a16f581d24e4ca981b2cdb3b9f36144f045b7ec49b4711ee3ccc501ffa4210d2e47f7960a8203f4e1ad7d2317741dc0c070cdd0365fdaa37c1cf8c9b62
6
+ metadata.gz: 101653bce864194699688ee00a8d35729fbe742f01f4a723bca9fecdd77ee4abf7916b8bef2fdeee66198afa6e0df7d840dceadaa3d02b51a9ab146b54caa359
7
+ data.tar.gz: 3109917e4c136fe9ee2a6938b52e4496130372e277af22b31cd909a4bdbf991a2e99bdf68d0dda276f36dde6c493c930d7000e1119e9f7f7c52e87511b34c83d
data/docs/proxy.md ADDED
@@ -0,0 +1,10 @@
1
+ # Proxy settings
2
+
3
+ We'll automatically use the `HTTP_X_FORWARDED_FOR` header to determine the client's IP address when behind a trusted proxy.
4
+
5
+ If you need to use a different header to determine the client's IP address, you can set the `AIKIDO_CLIENT_IP_HEADER` environment variable to the name of that header. This will override the default `HTTP_X_FORWARDED_FOR` header.
6
+
7
+ ```bash
8
+ # For Fly.io Platform
9
+ AIKIDO_CLIENT_IP_HEADER=HTTP_FLY_CLIENT_IP bin/rails server
10
+ ```
data/docs/rails.md CHANGED
@@ -2,19 +2,63 @@
2
2
 
3
3
  To install Zen, add the gem:
4
4
 
5
- ```
5
+ ```sh
6
6
  bundle add aikido-zen
7
7
  ```
8
8
 
9
+ And require it before `Bundler.require` in `config/application.rb`:
10
+
11
+ ```ruby
12
+ # config/application.rb
13
+ require_relative "boot"
14
+
15
+ require "rails/all"
16
+
17
+ require "aikido-zen"
18
+ Aikido::Zen.protect!
19
+
20
+ # Require the gems listed in Gemfile, including any gems
21
+ # you've limited to :test, :development, or :production.
22
+ Bundler.require(*Rails.groups)
23
+
24
+ ...
25
+ ```
26
+
9
27
  That's it! Zen will start to run inside your app when it starts getting
10
28
  requests.
11
29
 
30
+ ## Rate limiting and user blocking
31
+
32
+ If you want to add the rate limiting feature to your app, modify your code like this:
33
+
34
+ ```ruby
35
+ # app/controllers/application_controller.rb
36
+ class ApplicationController < ActionController::Base
37
+ private
38
+
39
+ def current_user
40
+ return unless session[:user_id]
41
+ User.find(session[:user_id])
42
+ end
43
+
44
+ def authenticate_user!
45
+ # Your authentication logic here
46
+ # ...
47
+ # Optional, if you want to use user based rate limiting or block specific users
48
+ Aikido::Zen.set_user(
49
+ id: current_user.id,
50
+ name: current_user.name
51
+ )
52
+ end
53
+ end
54
+ ```
55
+
12
56
  ## Configuration
13
57
 
14
58
  Zen exposes its configuration object to the Rails configuration, which you can
15
59
  modify in an initializer if desired:
16
60
 
17
- ``` ruby
61
+ ```ruby
18
62
  # config/initializers/zen.rb
19
63
  Rails.application.config.zen.api_timeouts = 20
20
64
  ```
@@ -22,7 +66,7 @@ Rails.application.config.zen.api_timeouts = 20
22
66
  You can access the configuration object both as `Aikido::Zen.config` or
23
67
  `Rails.configuration.zen`.
24
68
 
25
- See our [configuration guide](docs/config.md) for more details.
69
+ See our [configuration guide](./config.md) for more details.
26
70
 
27
71
  ## Using Rails encrypted credentials
28
72
 
@@ -30,7 +74,7 @@ If you're using Rails' [encrypted credentials][creds], and prefer not storing
30
74
  sensitive values in your env vars, you can easily configure Zen for it. For
31
75
  example, assuming the following credentials structure:
32
76
 
33
- ``` yaml
77
+ ```yaml
34
78
  # config/credentials.yml.enc
35
79
  zen:
36
80
  token: "AIKIDO_RUNTIME_..."
@@ -38,7 +82,7 @@ zen:
38
82
 
39
83
  You can just tell Zen to use it like so:
40
84
 
41
- ``` ruby
85
+ ```ruby
42
86
  # config/initializers/zen.rb
43
87
  Rails.application.config.zen.token = Rails.application.credentials.zen.token
44
88
  ```
@@ -61,7 +105,7 @@ way.
61
105
  By default, Zen will use the Rails logger, prefixing messages with `[aikido]`.
62
106
  You can redirect the log to a separate stream by overriding the logger:
63
107
 
64
- ```
108
+ ```ruby
65
109
  # config/initializers/zen.rb
66
110
  Rails.application.config.zen.logger = Logger.new(...)
67
111
  ```
@@ -61,10 +61,10 @@ module Aikido::Zen
61
61
  # @return [Logger]
62
62
  attr_reader :logger
63
63
 
64
- # @return [string] Path of the socket where the detached agent will listen.
64
+ # @return [String] Path of the socket where the detached agent will listen.
65
65
  # By default, is stored under the root application path with file name
66
66
  # `aikido-detached-agent.sock`
67
- attr_reader :detached_agent_socket_path
67
+ attr_accessor :detached_agent_socket_path
68
68
 
69
69
  # @return [Boolean] is the agent in debugging mode?
70
70
  attr_accessor :debugging
@@ -150,6 +150,9 @@ module Aikido::Zen
150
150
  # allow known hosts that should be able to resolve to the IMDS service.
151
151
  attr_accessor :imds_allowed_hosts
152
152
 
153
+ # @return [String] environment specific HTTP header providing the client IP.
154
+ attr_accessor :client_ip_header
155
+
153
156
  def initialize
154
157
  self.disabled = read_boolean_from_env(ENV.fetch("AIKIDO_DISABLED", false))
155
158
  self.blocking_mode = read_boolean_from_env(ENV.fetch("AIKIDO_BLOCK", false))
@@ -163,8 +166,9 @@ module Aikido::Zen
163
166
  self.json_decoder = DEFAULT_JSON_DECODER
164
167
  self.debugging = read_boolean_from_env(ENV.fetch("AIKIDO_DEBUG", false))
165
168
  self.logger = Logger.new($stdout, progname: "aikido", level: debugging ? Logger::DEBUG : Logger::INFO)
166
- self.max_performance_samples = 5000
167
169
  self.detached_agent_socket_path = ENV.fetch("AIKIDO_DETACHED_AGENT_SOCKET_PATH", DEFAULT_DETACHED_AGENT_SOCKET_PATH)
170
+ self.client_ip_header = ENV.fetch("AIKIDO_CLIENT_IP_HEADER", nil)
171
+ self.max_performance_samples = 5000
168
172
  self.max_compressed_stats = 100
169
173
  self.max_outbound_connections = 200
170
174
  self.max_users_tracked = 1000
@@ -222,9 +226,8 @@ module Aikido::Zen
222
226
  @api_timeouts.update(value)
223
227
  end
224
228
 
225
- def detached_agent_socket_path=(path)
226
- @detached_agent_socket_path = path
227
- @detached_agent_socket_path = "drbunix:" + @detached_agent_socket_path unless @detached_agent_socket_path.start_with?("drbunix:")
229
+ def detached_agent_socket_uri
230
+ "drbunix:" + @detached_agent_socket_path
228
231
  end
229
232
 
230
233
  private
@@ -32,7 +32,7 @@ module Aikido::Zen::DetachedAgent
32
32
  @polling_interval = polling_interval
33
33
  @worker = worker
34
34
  @collector = collector
35
- @detached_agent_front = DRbObject.new_with_uri(config.detached_agent_socket_path)
35
+ @detached_agent_front = DRbObject.new_with_uri(config.detached_agent_socket_uri)
36
36
  @has_forked = false
37
37
  schedule_tasks
38
38
  end
@@ -1,41 +1,78 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Aikido::Zen::DetachedAgent
4
6
  class Server
7
+ # Initialize and start a detached agent server instance.
8
+ #
9
+ # @return [Aikido::Zen::DetachedAgent::Server]
10
+ def self.start(**opts)
11
+ new(**opts).tap(&:start!)
12
+ end
13
+
5
14
  def initialize(config: Aikido::Zen.config)
6
- @detached_agent_front = FrontObject.new
7
- @drb_server = DRb.start_service(config.detached_agent_socket_path, @detached_agent_front)
15
+ @started_at = nil
8
16
 
9
- # We don't want to see drb logs unless in debug mode
10
- @drb_server.verbose = config.logger.debug?
11
- end
17
+ @config = config
12
18
 
13
- def alive?
14
- @drb_server.alive?
19
+ @socket_path = config.detached_agent_socket_path
20
+ @socket_uri = config.detached_agent_socket_uri
15
21
  end
16
22
 
17
- def stop!
18
- @drb_server.stop_service
19
- DRb.stop_service
23
+ def started?
24
+ !!@started_at
20
25
  end
21
26
 
22
- class << self
23
- def start!
24
- Aikido::Zen.config.logger.debug("Starting DRb Server...")
25
- max_attempts = 10
26
- @server = new
27
-
28
- attempts = 0
29
- until @server.alive?
30
- Aikido::Zen.config.logger.info("DRb Server still not alive. #{max_attempts - attempts} attempts remaining")
31
- sleep 0.1
32
- attempts += 1
33
- raise Aikido::Zen::DetachedAgentError.new("Impossible to start the dRB server (socket=#{Aikido::Zen.config.detached_agent_socket_path})") \
34
- if attempts == max_attempts
35
- end
36
-
37
- @server
27
+ def start!
28
+ @config.logger.info("Starting DRb Server...")
29
+
30
+ # Try to ensure that the DRb service can start if the DRb service did
31
+ # not stop cleanly.
32
+ begin
33
+ # Check whether the Unix domain socket is in use by another process.
34
+ UNIXSocket.new(@socket_path).close
35
+ rescue Errno::ECONNREFUSED
36
+ @config.logger.debug("Removing residual Unix domain socket...")
37
+
38
+ # Remove the residual Unix domain socket.
39
+ FileUtils.rm_f(@socket_path)
40
+ rescue
41
+ # empty
42
+ end
43
+
44
+ @front = FrontObject.new
45
+
46
+ # If the Unix domain socket is in use by another process and/or the
47
+ # residual Unix domain socket could not be removed DRb will raise an
48
+ # appropriate error.
49
+ @drb_server = DRb.start_service(@socket_uri, @front)
50
+
51
+ # Only show DRb output in debug mode.
52
+ @drb_server.verbose = @config.logger.debug?
53
+
54
+ # Ensure that the DRb server is alive.
55
+ max_attempts = 10
56
+ attempts = 0
57
+ until @drb_server.alive?
58
+ @config.logger.info("DRb Server still not alive. #{max_attempts - attempts} attempts remaining")
59
+ sleep 0.1
60
+ attempts += 1
61
+ raise Aikido::Zen::DetachedAgentError.new("Impossible to start the dRB server (socket=#{Aikido::Zen.config.detached_agent_socket_path})") \
62
+ if attempts == max_attempts
38
63
  end
64
+
65
+ @started_at = Time.now.utc
66
+
67
+ at_exit { stop! if started? }
68
+ end
69
+
70
+ def stop!
71
+ @config.logger.info("Stopping DRb Server...")
72
+ @started_at = nil
73
+
74
+ @drb_server.stop_service if @drb_server.alive?
75
+ DRb.stop_service
39
76
  end
40
77
  end
41
78
  end
@@ -27,7 +27,7 @@ module Aikido::Zen
27
27
  {
28
28
  payload: value.to_s,
29
29
  source: SOURCE_SERIALIZATIONS[source],
30
- pathToPayload: path.to_s
30
+ path: path.to_s
31
31
  }
32
32
  end
33
33
 
@@ -17,8 +17,9 @@ module Aikido::Zen
17
17
  # @see Aikido::Zen.track_user
18
18
  attr_accessor :actor
19
19
 
20
- def initialize(delegate, framework:, router:)
20
+ def initialize(delegate, config = Aikido::Zen.config, framework:, router:)
21
21
  super(delegate)
22
+ @config = config
22
23
  @framework = framework
23
24
  @router = router
24
25
  @body_read = false
@@ -40,6 +41,24 @@ module Aikido::Zen
40
41
  @schema ||= Aikido::Zen::Request::Schema.build
41
42
  end
42
43
 
44
+ # @api private
45
+ #
46
+ # @return [String] the IP address of the client making the request.
47
+ def client_ip
48
+ return @client_ip if @client_ip
49
+
50
+ if @config.client_ip_header
51
+ value = env[@config.client_ip_header]
52
+ if Resolv::AddressRegex.match?(value)
53
+ @client_ip = value
54
+ else
55
+ @config.logger.warn("Invalid IP address in custom client IP header `#{@config.client_ip_header}`: `#{value}`")
56
+ end
57
+ end
58
+
59
+ @client_ip ||= respond_to?(:remote_ip) ? remote_ip : ip
60
+ end
61
+
43
62
  # Map the CGI-style env Hash into "pretty-looking" headers, preserving the
44
63
  # values as-is. For example, HTTP_ACCEPT turns into "Accept", CONTENT_TYPE
45
64
  # turns into "Content-Type", and HTTP_X_FORWARDED_FOR turns into
@@ -87,7 +106,7 @@ module Aikido::Zen
87
106
  {
88
107
  method: request_method.downcase,
89
108
  url: url,
90
- ipAddress: ip,
109
+ ipAddress: client_ip,
91
110
  userAgent: user_agent,
92
111
  headers: normalized_headers.reject { |_, val| val.to_s.empty? },
93
112
  body: truncated_body,
@@ -109,6 +109,8 @@ module Aikido::Zen
109
109
  #
110
110
  # @return [void]
111
111
  def presafe_sink_before(method_name, &block)
112
+ raise ArgumentError, "block required" unless block
113
+
112
114
  original = instance_method(method_name)
113
115
 
114
116
  define_method(method_name) do |*args, **kwargs, &blk|
@@ -131,6 +133,8 @@ module Aikido::Zen
131
133
  #
132
134
  # @note the block is executed within `safe` to handle errors safely; the original method is executed outside of `safe` to preserve the original behavior
133
135
  def sink_before(method_name, &block)
136
+ raise ArgumentError, "block required" unless block
137
+
134
138
  presafe_sink_before(method_name) do |*args, **kwargs|
135
139
  DSL.safe do
136
140
  instance_exec(*args, **kwargs, &block)
@@ -149,6 +153,8 @@ module Aikido::Zen
149
153
  #
150
154
  # @return [void]
151
155
  def presafe_sink_after(method_name, &block)
156
+ raise ArgumentError, "block required" unless block
157
+
152
158
  original = instance_method(method_name)
153
159
 
154
160
  define_method(method_name) do |*args, **kwargs, &blk|
@@ -173,6 +179,8 @@ module Aikido::Zen
173
179
  #
174
180
  # @note the block is executed within `safe` to handle errors safely; the original method is executed outside of `safe` to preserve the original behavior
175
181
  def sink_after(method_name, &block)
182
+ raise ArgumentError, "block required" unless block
183
+
176
184
  presafe_sink_after(method_name) do |result, *args, **kwargs|
177
185
  DSL.safe do
178
186
  instance_exec(result, *args, **kwargs, &block)
@@ -191,6 +199,8 @@ module Aikido::Zen
191
199
  #
192
200
  # @return [void]
193
201
  def presafe_sink_around(method_name, &block)
202
+ raise ArgumentError, "block required" unless block
203
+
194
204
  original = instance_method(method_name)
195
205
 
196
206
  define_method(method_name) do |*args, **kwargs, &blk|
@@ -219,6 +229,8 @@ module Aikido::Zen
219
229
  # @note the block is executed within `safe` to handle errors safely; the original method is executed within `presafe` to preserve the original behavior
220
230
  # @note if the block does not call `original_call`, the original method is called automatically after the block is executed
221
231
  def sink_around(method_name, &block)
232
+ raise ArgumentError, "block required" unless block
233
+
222
234
  presafe_sink_around(method_name) do |presafe_original_call, *args, **kwargs|
223
235
  original_called = false
224
236
  original_call = proc do
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Aikido
4
4
  module Zen
5
- VERSION = "1.0.1.beta.5"
5
+ VERSION = "1.0.2.beta.2"
6
6
 
7
7
  # The version of libzen_internals that we build against.
8
8
  LIBZEN_VERSION = "0.1.39"
data/lib/aikido/zen.rb CHANGED
@@ -165,6 +165,11 @@ module Aikido
165
165
  end
166
166
  end
167
167
 
168
+ # Align with other Zen implementations, while keeping internal consistency.
169
+ class << self
170
+ alias_method :set_user, :track_user
171
+ end
172
+
168
173
  # Marks that the Zen middleware was installed properly
169
174
  # @return void
170
175
  def self.middleware_installed!
@@ -209,7 +214,7 @@ module Aikido
209
214
  end
210
215
 
211
216
  def self.detached_agent_server
212
- @detached_agent_server ||= DetachedAgent::Server.start!
217
+ @detached_agent_server ||= DetachedAgent::Server.start
213
218
  end
214
219
 
215
220
  class << self
data/tasklib/libzen.rake CHANGED
@@ -76,6 +76,7 @@ LIBZENS = [
76
76
  LibZen.new("arm64-darwin.dylib", "libzen_internals_aarch64-apple-darwin.dylib"),
77
77
  LibZen.new("arm64-linux.so", "libzen_internals_aarch64-unknown-linux-gnu.so"),
78
78
  LibZen.new("arm64-linux-musl.so", "libzen_internals_aarch64-unknown-linux-musl.so"),
79
+ LibZen.new("aarch64-linux.so", "libzen_internals_aarch64-unknown-linux-gnu.so"),
79
80
  LibZen.new("x86_64-darwin.dylib", "libzen_internals_x86_64-apple-darwin.dylib"),
80
81
  LibZen.new("x86_64-linux.so", "libzen_internals_x86_64-unknown-linux-gnu.so"),
81
82
  LibZen.new("x86_64-linux-musl.so", "libzen_internals_x86_64-unknown-linux-musl.so"),
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.0.1.beta.5
4
+ version: 1.0.2.beta.2
5
5
  platform: x86_64-darwin
6
6
  authors:
7
7
  - Aikido Security
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-19 00:00:00.000000000 Z
11
+ date: 2025-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -74,6 +74,7 @@ files:
74
74
  - benchmarks/rails7.1_sql_injection.js
75
75
  - docs/banner.svg
76
76
  - docs/config.md
77
+ - docs/proxy.md
77
78
  - docs/rails.md
78
79
  - lib/aikido-zen.rb
79
80
  - lib/aikido/zen.rb