aikido-zen 1.0.1.beta.5-x86_64-mingw-64 → 1.0.2.beta.2-x86_64-mingw-64
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/proxy.md +10 -0
- data/docs/rails.md +50 -6
- data/lib/aikido/zen/config.rb +9 -6
- data/lib/aikido/zen/detached_agent/agent.rb +1 -1
- data/lib/aikido/zen/detached_agent/server.rb +63 -26
- data/lib/aikido/zen/payload.rb +1 -1
- data/lib/aikido/zen/request.rb +21 -2
- data/lib/aikido/zen/sinks_dsl.rb +12 -0
- data/lib/aikido/zen/version.rb +1 -1
- data/lib/aikido/zen.rb +6 -1
- data/tasklib/libzen.rake +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df08748f71489b839ce1f792902314b6584d7262c306e9db836bcc977549223f
|
4
|
+
data.tar.gz: bb19e98304fbfc5b4676e9472dba44ed68517921c2bce17e79a8136d4d2da800
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 120676b9f0f20fd1876ef61e6e74b8c62366707ecce112427f930f9b2753cbd17af5190d3fd037708c0dc80fb8c280755502b8fafbb6f08340b815dce1af56be
|
7
|
+
data.tar.gz: e78d3459416dc19e6cd5e0a9e6a0f2545a5101ec92975d64273ec455601697fc717dd3248648274dce7031d58fe80a0ad05d8ca45331723ddcf4800f38303b26
|
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
|
-
```
|
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](
|
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
|
-
```
|
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
|
-
```
|
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
|
```
|
data/lib/aikido/zen/config.rb
CHANGED
@@ -61,10 +61,10 @@ module Aikido::Zen
|
|
61
61
|
# @return [Logger]
|
62
62
|
attr_reader :logger
|
63
63
|
|
64
|
-
# @return [
|
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
|
-
|
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
|
226
|
-
@detached_agent_socket_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.
|
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
|
-
@
|
7
|
-
@drb_server = DRb.start_service(config.detached_agent_socket_path, @detached_agent_front)
|
15
|
+
@started_at = nil
|
8
16
|
|
9
|
-
|
10
|
-
@drb_server.verbose = config.logger.debug?
|
11
|
-
end
|
17
|
+
@config = config
|
12
18
|
|
13
|
-
|
14
|
-
@
|
19
|
+
@socket_path = config.detached_agent_socket_path
|
20
|
+
@socket_uri = config.detached_agent_socket_uri
|
15
21
|
end
|
16
22
|
|
17
|
-
def
|
18
|
-
|
19
|
-
DRb.stop_service
|
23
|
+
def started?
|
24
|
+
!!@started_at
|
20
25
|
end
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
data/lib/aikido/zen/payload.rb
CHANGED
data/lib/aikido/zen/request.rb
CHANGED
@@ -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:
|
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,
|
data/lib/aikido/zen/sinks_dsl.rb
CHANGED
@@ -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
|
data/lib/aikido/zen/version.rb
CHANGED
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.
|
4
|
+
version: 1.0.2.beta.2
|
5
5
|
platform: x86_64-mingw-64
|
6
6
|
authors:
|
7
7
|
- Aikido Security
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
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
|