aikido-zen 1.0.2.beta.2-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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.aikido +6 -0
  3. data/.ruby-version +1 -0
  4. data/.simplecov +26 -0
  5. data/.standard.yml +3 -0
  6. data/LICENSE +674 -0
  7. data/README.md +146 -0
  8. data/Rakefile +67 -0
  9. data/benchmarks/README.md +23 -0
  10. data/benchmarks/rails7.1_sql_injection.js +70 -0
  11. data/docs/banner.svg +202 -0
  12. data/docs/config.md +125 -0
  13. data/docs/proxy.md +10 -0
  14. data/docs/rails.md +114 -0
  15. data/lib/aikido/zen/actor.rb +116 -0
  16. data/lib/aikido/zen/agent/heartbeats_manager.rb +66 -0
  17. data/lib/aikido/zen/agent.rb +179 -0
  18. data/lib/aikido/zen/api_client.rb +145 -0
  19. data/lib/aikido/zen/attack.rb +207 -0
  20. data/lib/aikido/zen/background_worker.rb +52 -0
  21. data/lib/aikido/zen/capped_collections.rb +68 -0
  22. data/lib/aikido/zen/collector/hosts.rb +15 -0
  23. data/lib/aikido/zen/collector/routes.rb +66 -0
  24. data/lib/aikido/zen/collector/sink_stats.rb +95 -0
  25. data/lib/aikido/zen/collector/stats.rb +111 -0
  26. data/lib/aikido/zen/collector/users.rb +30 -0
  27. data/lib/aikido/zen/collector.rb +144 -0
  28. data/lib/aikido/zen/config.rb +282 -0
  29. data/lib/aikido/zen/context/rack_request.rb +24 -0
  30. data/lib/aikido/zen/context/rails_request.rb +44 -0
  31. data/lib/aikido/zen/context.rb +112 -0
  32. data/lib/aikido/zen/detached_agent/agent.rb +78 -0
  33. data/lib/aikido/zen/detached_agent/front_object.rb +37 -0
  34. data/lib/aikido/zen/detached_agent/server.rb +78 -0
  35. data/lib/aikido/zen/detached_agent.rb +2 -0
  36. data/lib/aikido/zen/errors.rb +107 -0
  37. data/lib/aikido/zen/event.rb +71 -0
  38. data/lib/aikido/zen/internals.rb +103 -0
  39. data/lib/aikido/zen/libzen-v0.1.39-aarch64-linux.so +0 -0
  40. data/lib/aikido/zen/middleware/check_allowed_addresses.rb +26 -0
  41. data/lib/aikido/zen/middleware/middleware.rb +11 -0
  42. data/lib/aikido/zen/middleware/rack_throttler.rb +48 -0
  43. data/lib/aikido/zen/middleware/request_tracker.rb +192 -0
  44. data/lib/aikido/zen/middleware/set_context.rb +26 -0
  45. data/lib/aikido/zen/outbound_connection.rb +45 -0
  46. data/lib/aikido/zen/outbound_connection_monitor.rb +23 -0
  47. data/lib/aikido/zen/package.rb +22 -0
  48. data/lib/aikido/zen/payload.rb +50 -0
  49. data/lib/aikido/zen/rails_engine.rb +56 -0
  50. data/lib/aikido/zen/rate_limiter/breaker.rb +61 -0
  51. data/lib/aikido/zen/rate_limiter/bucket.rb +76 -0
  52. data/lib/aikido/zen/rate_limiter/result.rb +31 -0
  53. data/lib/aikido/zen/rate_limiter.rb +50 -0
  54. data/lib/aikido/zen/request/heuristic_router.rb +115 -0
  55. data/lib/aikido/zen/request/rails_router.rb +77 -0
  56. data/lib/aikido/zen/request/schema/auth_discovery.rb +86 -0
  57. data/lib/aikido/zen/request/schema/auth_schemas.rb +54 -0
  58. data/lib/aikido/zen/request/schema/builder.rb +121 -0
  59. data/lib/aikido/zen/request/schema/definition.rb +107 -0
  60. data/lib/aikido/zen/request/schema/empty_schema.rb +28 -0
  61. data/lib/aikido/zen/request/schema.rb +87 -0
  62. data/lib/aikido/zen/request.rb +122 -0
  63. data/lib/aikido/zen/route.rb +39 -0
  64. data/lib/aikido/zen/runtime_settings/endpoints.rb +49 -0
  65. data/lib/aikido/zen/runtime_settings/ip_set.rb +36 -0
  66. data/lib/aikido/zen/runtime_settings/protection_settings.rb +62 -0
  67. data/lib/aikido/zen/runtime_settings/rate_limit_settings.rb +47 -0
  68. data/lib/aikido/zen/runtime_settings.rb +65 -0
  69. data/lib/aikido/zen/scan.rb +75 -0
  70. data/lib/aikido/zen/scanners/path_traversal/helpers.rb +65 -0
  71. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +63 -0
  72. data/lib/aikido/zen/scanners/shell_injection/helpers.rb +159 -0
  73. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +64 -0
  74. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +93 -0
  75. data/lib/aikido/zen/scanners/ssrf/dns_lookups.rb +27 -0
  76. data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +97 -0
  77. data/lib/aikido/zen/scanners/ssrf_scanner.rb +265 -0
  78. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +49 -0
  79. data/lib/aikido/zen/scanners.rb +7 -0
  80. data/lib/aikido/zen/sink.rb +118 -0
  81. data/lib/aikido/zen/sinks/action_controller.rb +83 -0
  82. data/lib/aikido/zen/sinks/async_http.rb +80 -0
  83. data/lib/aikido/zen/sinks/curb.rb +113 -0
  84. data/lib/aikido/zen/sinks/em_http.rb +83 -0
  85. data/lib/aikido/zen/sinks/excon.rb +118 -0
  86. data/lib/aikido/zen/sinks/file.rb +112 -0
  87. data/lib/aikido/zen/sinks/http.rb +93 -0
  88. data/lib/aikido/zen/sinks/httpclient.rb +95 -0
  89. data/lib/aikido/zen/sinks/httpx.rb +78 -0
  90. data/lib/aikido/zen/sinks/kernel.rb +33 -0
  91. data/lib/aikido/zen/sinks/mysql2.rb +31 -0
  92. data/lib/aikido/zen/sinks/net_http.rb +101 -0
  93. data/lib/aikido/zen/sinks/patron.rb +103 -0
  94. data/lib/aikido/zen/sinks/pg.rb +72 -0
  95. data/lib/aikido/zen/sinks/resolv.rb +62 -0
  96. data/lib/aikido/zen/sinks/socket.rb +78 -0
  97. data/lib/aikido/zen/sinks/sqlite3.rb +46 -0
  98. data/lib/aikido/zen/sinks/trilogy.rb +31 -0
  99. data/lib/aikido/zen/sinks/typhoeus.rb +78 -0
  100. data/lib/aikido/zen/sinks.rb +36 -0
  101. data/lib/aikido/zen/sinks_dsl.rb +250 -0
  102. data/lib/aikido/zen/synchronizable.rb +24 -0
  103. data/lib/aikido/zen/system_info.rb +84 -0
  104. data/lib/aikido/zen/version.rb +10 -0
  105. data/lib/aikido/zen/worker.rb +87 -0
  106. data/lib/aikido/zen.rb +246 -0
  107. data/lib/aikido-zen.rb +3 -0
  108. data/placeholder/.gitignore +4 -0
  109. data/placeholder/README.md +11 -0
  110. data/placeholder/Rakefile +75 -0
  111. data/placeholder/lib/placeholder.rb.template +3 -0
  112. data/placeholder/placeholder.gemspec.template +20 -0
  113. data/tasklib/bench.rake +94 -0
  114. data/tasklib/libzen.rake +133 -0
  115. data/tasklib/wrk.rb +88 -0
  116. metadata +205 -0
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ module Sinks
5
+ module Trilogy
6
+ SINK = Sinks.add("trilogy", scanners: [Scanners::SQLInjectionScanner])
7
+
8
+ module Helpers
9
+ def self.scan(query, operation)
10
+ SINK.scan(query: query, dialect: :mysql, operation: operation)
11
+ end
12
+ end
13
+
14
+ def self.load_sinks!
15
+ if Aikido::Zen.satisfy "trilogy", ">= 2.0"
16
+ require "trilogy"
17
+
18
+ ::Trilogy.class_eval do
19
+ extend Sinks::DSL
20
+
21
+ sink_before :query do |query|
22
+ Helpers.scan(query, "query")
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ Aikido::Zen::Sinks::Trilogy.load_sinks!
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../sink"
4
+ require_relative "../outbound_connection_monitor"
5
+
6
+ module Aikido::Zen
7
+ module Sinks
8
+ module Typhoeus
9
+ SINK = Sinks.add("typhoeus", scanners: [
10
+ Aikido::Zen::Scanners::SSRFScanner,
11
+ Aikido::Zen::OutboundConnectionMonitor
12
+ ])
13
+
14
+ before_callback = ->(request) {
15
+ wrapped_request = Aikido::Zen::Scanners::SSRFScanner::Request.new(
16
+ verb: request.options[:method],
17
+ uri: URI(request.url),
18
+ headers: request.options[:headers]
19
+ )
20
+
21
+ # Store the request information so the DNS sinks can pick it up.
22
+ if (context = Aikido::Zen.current_context)
23
+ prev_request = context["ssrf.request"]
24
+ context["ssrf.request"] = wrapped_request
25
+ end
26
+
27
+ SINK.scan(
28
+ connection: Aikido::Zen::OutboundConnection.from_uri(URI(request.base_url)),
29
+ request: wrapped_request,
30
+ operation: "request"
31
+ )
32
+
33
+ request.on_headers do |response|
34
+ context["ssrf.request"] = prev_request if context
35
+
36
+ Aikido::Zen::Scanners::SSRFScanner.track_redirects(
37
+ request: wrapped_request,
38
+ response: Aikido::Zen::Scanners::SSRFScanner::Response.new(
39
+ status: response.code,
40
+ headers: response.headers.to_h
41
+ )
42
+ )
43
+ end
44
+
45
+ # When Typhoeus is configured with followlocation: true, the redirect
46
+ # following happens between the on_headers and the on_complete callback,
47
+ # so we need this one to detect if the request resulted in an automatic
48
+ # redirect that was followed.
49
+ request.on_complete do |response|
50
+ break if response.effective_url == request.url
51
+
52
+ last_effective_request = Aikido::Zen::Scanners::SSRFScanner::Request.new(
53
+ verb: request.options[:method],
54
+ uri: URI(response.effective_url),
55
+ headers: request.options[:headers]
56
+ )
57
+ context["ssrf.request"] = last_effective_request if context
58
+
59
+ # In this case, we can't actually stop the request from happening, but
60
+ # we can scan again (now that we know another request happened), to
61
+ # stop the response from being exposed to the user. This downgrades
62
+ # the SSRF into a blind SSRF, which is better than doing nothing.
63
+ SINK.scan(
64
+ connection: Aikido::Zen::OutboundConnection.from_uri(URI(response.effective_url)),
65
+ request: last_effective_request,
66
+ operation: "request"
67
+ )
68
+ ensure
69
+ context["ssrf.request"] = nil if context
70
+ end
71
+
72
+ true
73
+ }
74
+
75
+ ::Typhoeus.before.prepend(before_callback)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Code coverage is disabled in this file because it is environment-specific and
4
+ # not intended to be tested directly.
5
+ # :nocov:
6
+
7
+ require_relative "sink"
8
+ require_relative "sinks_dsl"
9
+
10
+ require_relative "sinks/action_controller" if defined?(::ActionController)
11
+
12
+ require_relative "sinks/kernel"
13
+
14
+ require_relative "sinks/file"
15
+ require_relative "sinks/socket"
16
+ require_relative "sinks/resolv"
17
+ require_relative "sinks/net_http"
18
+
19
+ # http.rb aims to support and is tested against Ruby 3.0+:
20
+ # https://github.com/httprb/http?tab=readme-ov-file#supported-ruby-versions
21
+ require_relative "sinks/http" if RUBY_VERSION >= "3.0"
22
+
23
+ require_relative "sinks/httpx"
24
+ require_relative "sinks/httpclient"
25
+ require_relative "sinks/excon"
26
+ require_relative "sinks/curb"
27
+ require_relative "sinks/patron"
28
+ require_relative "sinks/typhoeus" if defined?(::Typhoeus)
29
+ require_relative "sinks/async_http"
30
+ require_relative "sinks/em_http"
31
+ require_relative "sinks/mysql2"
32
+ require_relative "sinks/pg"
33
+ require_relative "sinks/sqlite3"
34
+ require_relative "sinks/trilogy"
35
+
36
+ # :nocov:
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ module Sinks
5
+ module DSL
6
+ extend self
7
+
8
+ # In the context of `Aikido::Zen::Sinks::DSL`, the terms safe and presafe
9
+ # are defined as follows:
10
+ #
11
+ # safe: the desired state for a sink, particularly with respect to rescue.
12
+ #
13
+ # A sink is considered safe when unintended errors in the sink are handled,
14
+ # and-so are prevented from disrupting the operation of the original method
15
+ # (by raising unintended errors).
16
+ #
17
+ # presafe: the default state of a sink, particularly with respect to rescue.
18
+ #
19
+ # A sink is in the presafe state before and while unintended errors in the
20
+ # sink are not handled.
21
+ #
22
+ # Sink methods (like all methods) are in the presafe state when defined and
23
+ # become safe when unexpected errors cannot cause harm. The `safe` method
24
+ # is used to establish a safe state for the duration of the block executed.
25
+ # It is sometimes useful to be able to reestablish the presafe safe while
26
+ # inside a `safe` block; the `presafe` method allows this.
27
+ #
28
+ # Methods that contain the term presafe in their name should be used with
29
+ # appropriate care and understanding.
30
+ #
31
+ # IMPORTANT: All sinks should be safe!
32
+ #
33
+ # While this DSL proves useful for defining safe sink methods that follow
34
+ # common patterns, there are exceptions. It is always possible to define
35
+ # sink methods without using this DSL, but this should only be done when
36
+ # absolutely necessary. The sink methods defined using this DSL are safe,
37
+ # unless explicitly declared presafe.
38
+ #
39
+ # IMPORTANT: No sinks should be presafe in production!
40
+ #
41
+ # We are all responsible for ensuring that the sinks we implement are safe
42
+ # for production use. This DSL is only here to assist, by taking care of
43
+ # delicate edge cases and reducing the space for errors.
44
+ #
45
+ # When writing sink methods manually, some principles should be considered,
46
+ # to ensure safety for production:
47
+ #
48
+ # 1. Sink methods should ensure that the original method is always called,
49
+ # passing all parameters (positional, keyword, and block) exactly as they
50
+ # were passed to the original method, and return the result returned by
51
+ # the original method exactly as it was returned by the original method.
52
+ # (Unless intervention is required.)
53
+ #
54
+ # 2. Sink methods should not predict the signature of the original method,
55
+ # and-so restrict it from varying. The original method implementation is
56
+ # the sole and ultimate reference for its own behavior. We are observers
57
+ # (unless intervention is required).
58
+ #
59
+ # 3. Unexpected errors that are encountered in sink methods should not be
60
+ # capable of interfering with or preventing the normal operation of the
61
+ # original method. This includes but is not limited to exceptions that
62
+ # that may be raised when the sink method is called. Safe sink methods
63
+ # should return control to their caller (unless intervention is required).
64
+ #
65
+ # These are the guidelines adhered to by the `Aikido::Zen::Sinks::DSL`.
66
+
67
+ # The error with an original error as its cause to re-raise in `safe`.
68
+ class PresafeError < StandardError
69
+ end
70
+
71
+ # Safely execute the given block
72
+ #
73
+ # All standard errors are suppressed except `Aikido::Zen::UnderAttackError`s.
74
+ # This ensures that unexpected errors do not interrupt the execution of the
75
+ # original method, while all detected attacks are raised.
76
+ #
77
+ # When an error is wrapped in `PresafeError` the original error is reraised.
78
+ #
79
+ # @yield the block to execute
80
+ def safe
81
+ yield
82
+ rescue Aikido::Zen::UnderAttackError
83
+ raise
84
+ rescue PresafeError => err
85
+ raise err.cause
86
+ rescue => err
87
+ Aikido::Zen.config.logger.debug("[safe] #{err.class}: #{err.message}")
88
+ end
89
+
90
+ # Presafely execute the given block
91
+ #
92
+ # Safely wrap standard errors in `PresafeError` so that the original error is
93
+ # reraised when rescued in `safe`.
94
+ #
95
+ # @yield the block to execute
96
+ def presafe
97
+ yield
98
+ rescue => err
99
+ raise PresafeError, cause: err
100
+ end
101
+
102
+ # Define a method `method_name` that presafely executes the given block before
103
+ # the original method.
104
+ #
105
+ # @param method_name [Symbol, String] the name of the method to define
106
+ # @yield the block to execute before the original method
107
+ # @yieldparam args [Array] the positional arguments passed to the original method
108
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
109
+ #
110
+ # @return [void]
111
+ def presafe_sink_before(method_name, &block)
112
+ raise ArgumentError, "block required" unless block
113
+
114
+ original = instance_method(method_name)
115
+
116
+ define_method(method_name) do |*args, **kwargs, &blk|
117
+ instance_exec(*args, **kwargs, &block)
118
+ original.bind_call(self, *args, **kwargs, &blk)
119
+ end
120
+ rescue NameError
121
+ Aikido::Zen.config.logger.warn("cannot wrap method `#{method_name}' for class `#{self}'")
122
+ end
123
+
124
+ # Define a method `method_name` that safely executes the given block before
125
+ # the original method.
126
+ #
127
+ # @param method_name [Symbol, String] the name of the method to define
128
+ # @yield the block to execute before the original method
129
+ # @yieldparam args [Array] the positional arguments passed to the original method
130
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
131
+ #
132
+ # @return [void]
133
+ #
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
135
+ def sink_before(method_name, &block)
136
+ raise ArgumentError, "block required" unless block
137
+
138
+ presafe_sink_before(method_name) do |*args, **kwargs|
139
+ DSL.safe do
140
+ instance_exec(*args, **kwargs, &block)
141
+ end
142
+ end
143
+ end
144
+
145
+ # Define a method `method_name` that presafely executes the given block after
146
+ # the original method.
147
+ #
148
+ # @param method_name [Symbol, String] the name of the method to define
149
+ # @yield the block to execute after the original method
150
+ # @yieldparam result [Object] the result returned by the original method
151
+ # @yieldparam args [Array] the positional arguments passed to the original method
152
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
153
+ #
154
+ # @return [void]
155
+ def presafe_sink_after(method_name, &block)
156
+ raise ArgumentError, "block required" unless block
157
+
158
+ original = instance_method(method_name)
159
+
160
+ define_method(method_name) do |*args, **kwargs, &blk|
161
+ result = original.bind_call(self, *args, **kwargs, &blk)
162
+ instance_exec(result, *args, **kwargs, &block)
163
+ result
164
+ end
165
+ rescue NameError
166
+ Aikido::Zen.config.logger.warn("cannot wrap method `#{method_name}' for class `#{self}'")
167
+ end
168
+
169
+ # Define a method `method_name` that safely executes the given block after
170
+ # the original method.
171
+ #
172
+ # @param method_name [Symbol, String] the name of the method to define
173
+ # @yield the block to execute after the original method
174
+ # @yieldparam result [Object] the result returned by the original method
175
+ # @yieldparam args [Array] the positional arguments passed to the original method
176
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
177
+ #
178
+ # @return [void]
179
+ #
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
181
+ def sink_after(method_name, &block)
182
+ raise ArgumentError, "block required" unless block
183
+
184
+ presafe_sink_after(method_name) do |result, *args, **kwargs|
185
+ DSL.safe do
186
+ instance_exec(result, *args, **kwargs, &block)
187
+ end
188
+ end
189
+ end
190
+
191
+ # Define a method `method_name` that presafely executes the given block around
192
+ # the original method.
193
+ #
194
+ # @param method_name [Symbol, String] the name of the method to define
195
+ # @yield the block to execute around the original method
196
+ # @yieldparam original_call [Proc] the proc that calls the original method
197
+ # @yieldparam args [Array] the positional arguments passed to the original method
198
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
199
+ #
200
+ # @return [void]
201
+ def presafe_sink_around(method_name, &block)
202
+ raise ArgumentError, "block required" unless block
203
+
204
+ original = instance_method(method_name)
205
+
206
+ define_method(method_name) do |*args, **kwargs, &blk|
207
+ result = nil
208
+ original_call = proc do
209
+ result = original.bind_call(self, *args, **kwargs, &blk)
210
+ end
211
+ instance_exec(original_call, *args, **kwargs, &block)
212
+ result
213
+ end
214
+ rescue NameError
215
+ Aikido::Zen.config.logger.warn("cannot wrap method `#{method_name}' for class `#{self}'")
216
+ end
217
+
218
+ # Define a method `method_name` that safely executes the given block around
219
+ # the original method.
220
+ #
221
+ # @param method_name [Symbol, String] the name of the method to define
222
+ # @yield the block to execute around the original method
223
+ # @yieldparam original_call [Proc] the proc that calls the original method
224
+ # @yieldparam args [Array] the positional arguments passed to the original method
225
+ # @yieldparam kwargs [Hash] the keyword arguments passed to the original method
226
+ #
227
+ # @return [void]
228
+ #
229
+ # @note the block is executed within `safe` to handle errors safely; the original method is executed within `presafe` to preserve the original behavior
230
+ # @note if the block does not call `original_call`, the original method is called automatically after the block is executed
231
+ def sink_around(method_name, &block)
232
+ raise ArgumentError, "block required" unless block
233
+
234
+ presafe_sink_around(method_name) do |presafe_original_call, *args, **kwargs|
235
+ original_called = false
236
+ original_call = proc do
237
+ original_called = true
238
+ DSL.presafe do
239
+ presafe_original_call.call
240
+ end
241
+ end
242
+ DSL.safe do
243
+ instance_exec(original_call, *args, **kwargs, &block)
244
+ end
245
+ presafe_original_call.call unless original_called
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido::Zen
4
+ # @!visibility private
5
+ #
6
+ # Provides the synchronization part of Concurrent's LockableObject, but allows
7
+ # objects to take keyword arguments as well.
8
+ #
9
+ # NOTE: This is meant to be prepennded.
10
+ module Synchronizable
11
+ def initialize(*, **)
12
+ @__lock__ = ::Mutex.new
13
+ super
14
+ end
15
+
16
+ def synchronize
17
+ if @__lock__.owned?
18
+ yield
19
+ else
20
+ @__lock__.synchronize { yield }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "rubygems"
5
+
6
+ require_relative "package"
7
+
8
+ module Aikido::Zen
9
+ # Provides information about the currently running Agent.
10
+ class SystemInfo
11
+ def initialize(config = Aikido::Zen.config)
12
+ @config = config
13
+ end
14
+
15
+ def attacks_block_requests?
16
+ !!@config.blocking_mode
17
+ end
18
+
19
+ def attacks_are_only_reported?
20
+ !attacks_block_requests?
21
+ end
22
+
23
+ def library_name
24
+ "firewall-ruby"
25
+ end
26
+
27
+ def library_version
28
+ VERSION
29
+ end
30
+
31
+ def platform_version
32
+ RUBY_VERSION
33
+ end
34
+
35
+ def hostname
36
+ @hostname ||= Socket.gethostname
37
+ end
38
+
39
+ # @return [Array<Aikido::Zen::Package>] a list of loaded rubygems that are
40
+ # supported by Aikido (i.e. we have a Sink that scans the package for
41
+ # vulnerabilities and protects you).
42
+ def packages
43
+ @packages ||= Gem.loaded_specs
44
+ .map { |_, spec| Package.new(spec.name, spec.version) }
45
+ .select(&:supported?)
46
+ end
47
+
48
+ # @return [String] the first non-loopback IPv4 address that we can use
49
+ # to identify this host. If the machine is solely identified by IPv6
50
+ # addresses, then this will instead return an IPv6 address.
51
+ def ip_address
52
+ @ip_address ||= Socket.ip_address_list
53
+ .reject { |ip| ip.ipv4_loopback? || ip.ipv6_loopback? || ip.unix? }
54
+ .min_by { |ip| ip.ipv4? ? 0 : 1 }
55
+ .ip_address
56
+ end
57
+
58
+ def os_name
59
+ Gem::Platform.local.os
60
+ end
61
+
62
+ def os_version
63
+ Gem::Platform.local.version || "unknown"
64
+ end
65
+
66
+ def as_json
67
+ {
68
+ dryMode: attacks_are_only_reported?,
69
+ library: library_name,
70
+ version: library_version,
71
+ hostname: hostname,
72
+ ipAddress: ip_address,
73
+ platform: {version: platform_version},
74
+ os: {name: os_name, version: os_version},
75
+ packages: packages.reduce({}) { |all, package| all.update(package.as_json) },
76
+ incompatiblePackages: {},
77
+ stack: [],
78
+ serverless: false,
79
+ nodeEnv: "",
80
+ preventedPrototypePollution: false
81
+ }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aikido
4
+ module Zen
5
+ VERSION = "1.0.2.beta.2"
6
+
7
+ # The version of libzen_internals that we build against.
8
+ LIBZEN_VERSION = "0.1.39"
9
+ end
10
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+
5
+ module Aikido::Zen
6
+ # @api private
7
+ #
8
+ # The worker manages the background thread in which Zen communicates with the
9
+ # Aikido server.
10
+ class Worker
11
+ # @return [Concurrent::ExecutorService]
12
+ attr_reader :executor
13
+
14
+ # @!visibility private
15
+ attr_reader :timers, :deferrals
16
+
17
+ def initialize(config: Aikido::Zen.config)
18
+ @config = config
19
+ @timers = []
20
+ @deferrals = []
21
+ @executor = Concurrent::SingleThreadExecutor.new
22
+ end
23
+
24
+ # Queue a block to be run asynchronously in the background thread.
25
+ #
26
+ # @return [void]
27
+ def perform(&block)
28
+ executor.post do
29
+ yield
30
+ rescue Exception => err # rubocop:disable Lint/RescueException
31
+ @config.logger.error "Error in background worker: #{err.inspect}"
32
+ end
33
+ end
34
+
35
+ # Queue a block to be run asynchronously after a delay.
36
+ #
37
+ # @param interval [Integer] amount of seconds to wait.
38
+ # @return [void]
39
+ def delay(interval, &task)
40
+ Concurrent::ScheduledTask
41
+ .execute(interval, executor: executor) { perform(&task) }
42
+ .tap { |deferral| @deferrals << deferral }
43
+ end
44
+
45
+ # Queue a block to run repeatedly on a timer on the background thread. The
46
+ # timer will consider how long the block takes to run to schedule the next
47
+ # run. For example, if you schedule a block to run every 10 seconds, and the
48
+ # block itself takes 2 seconds, the second iteration will be run 8 seconds
49
+ # after the first one.
50
+ #
51
+ # If the block takes longer than the given interval, the second iteration
52
+ # will be run immediately.
53
+ #
54
+ # @param interval [Integer] amount of seconds to wait between runs.
55
+ # @param run_now [Boolean] whether to run the block immediately, or wait for
56
+ # +interval+ seconds before the first run. Defaults to +true+.
57
+ # @return [void]
58
+ def every(interval, run_now: true, &task)
59
+ Concurrent::TimerTask
60
+ .execute(
61
+ run_now: run_now,
62
+ executor: executor,
63
+ interval_type: :fixed_rate,
64
+ execution_interval: interval
65
+ ) {
66
+ perform(&task)
67
+ }
68
+ .tap { |timer| @timers << timer }
69
+ end
70
+
71
+ # Safely clean up and kill the thread, giving time to kill any ongoing tasks
72
+ # on the queue.
73
+ #
74
+ # @return [void]
75
+ def shutdown
76
+ @deferrals.each { |task| task.cancel if task.pending? }
77
+ @timers.each { |task| task.shutdown }
78
+ @executor.shutdown
79
+ @executor.wait_for_termination(30)
80
+ end
81
+
82
+ def restart
83
+ shutdown
84
+ @executor = Concurrent::SingleThreadExecutor.new
85
+ end
86
+ end
87
+ end