aikido-zen 1.0.1.beta.4-arm64-linux → 1.0.2-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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +6 -0
  3. data/README.md +2 -0
  4. data/benchmarks/README.md +0 -1
  5. data/benchmarks/rails7.1_benchmark.js +1 -0
  6. data/benchmarks/rails7.1_sql_injection.js +52 -20
  7. data/docs/config.md +9 -1
  8. data/docs/proxy.md +10 -0
  9. data/docs/rails.md +55 -13
  10. data/docs/troubleshooting.md +62 -0
  11. data/lib/aikido/zen/actor.rb +34 -4
  12. data/lib/aikido/zen/agent/heartbeats_manager.rb +5 -5
  13. data/lib/aikido/zen/agent.rb +19 -17
  14. data/lib/aikido/zen/attack.rb +19 -9
  15. data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
  16. data/lib/aikido/zen/attack_wave.rb +88 -0
  17. data/lib/aikido/zen/cache.rb +91 -0
  18. data/lib/aikido/zen/capped_collections.rb +22 -4
  19. data/lib/aikido/zen/collector/event.rb +238 -0
  20. data/lib/aikido/zen/collector/hosts.rb +16 -1
  21. data/lib/aikido/zen/collector/routes.rb +13 -8
  22. data/lib/aikido/zen/collector/stats.rb +33 -22
  23. data/lib/aikido/zen/collector/users.rb +5 -3
  24. data/lib/aikido/zen/collector.rb +107 -28
  25. data/lib/aikido/zen/config.rb +54 -21
  26. data/lib/aikido/zen/context/rack_request.rb +3 -0
  27. data/lib/aikido/zen/context/rails_request.rb +3 -0
  28. data/lib/aikido/zen/context.rb +42 -9
  29. data/lib/aikido/zen/detached_agent/agent.rb +28 -27
  30. data/lib/aikido/zen/detached_agent/front_object.rb +10 -6
  31. data/lib/aikido/zen/detached_agent/server.rb +63 -26
  32. data/lib/aikido/zen/event.rb +47 -2
  33. data/lib/aikido/zen/helpers.rb +24 -0
  34. data/lib/aikido/zen/internals.rb +23 -3
  35. data/lib/aikido/zen/libzen-v0.1.48-arm64-linux.so +0 -0
  36. data/lib/aikido/zen/middleware/{check_allowed_addresses.rb → allowed_address_checker.rb} +1 -1
  37. data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
  38. data/lib/aikido/zen/middleware/{set_context.rb → context_setter.rb} +1 -1
  39. data/lib/aikido/zen/middleware/fork_detector.rb +23 -0
  40. data/lib/aikido/zen/middleware/rack_throttler.rb +3 -1
  41. data/lib/aikido/zen/middleware/request_tracker.rb +9 -4
  42. data/lib/aikido/zen/outbound_connection.rb +18 -1
  43. data/lib/aikido/zen/payload.rb +1 -1
  44. data/lib/aikido/zen/rails_engine.rb +5 -8
  45. data/lib/aikido/zen/request/rails_router.rb +17 -2
  46. data/lib/aikido/zen/request.rb +21 -36
  47. data/lib/aikido/zen/route.rb +57 -0
  48. data/lib/aikido/zen/runtime_settings/endpoints.rb +37 -8
  49. data/lib/aikido/zen/runtime_settings.rb +6 -5
  50. data/lib/aikido/zen/scanners/path_traversal/helpers.rb +10 -7
  51. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +5 -4
  52. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +3 -2
  53. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +3 -2
  54. data/lib/aikido/zen/scanners/ssrf_scanner.rb +2 -1
  55. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +8 -2
  56. data/lib/aikido/zen/sink.rb +1 -1
  57. data/lib/aikido/zen/sinks/action_controller.rb +3 -1
  58. data/lib/aikido/zen/sinks/async_http.rb +40 -42
  59. data/lib/aikido/zen/sinks/curb.rb +56 -58
  60. data/lib/aikido/zen/sinks/em_http.rb +27 -29
  61. data/lib/aikido/zen/sinks/excon.rb +62 -65
  62. data/lib/aikido/zen/sinks/file.rb +108 -71
  63. data/lib/aikido/zen/sinks/http.rb +26 -28
  64. data/lib/aikido/zen/sinks/httpclient.rb +27 -29
  65. data/lib/aikido/zen/sinks/httpx.rb +27 -29
  66. data/lib/aikido/zen/sinks/kernel.rb +11 -12
  67. data/lib/aikido/zen/sinks/mysql2.rb +10 -12
  68. data/lib/aikido/zen/sinks/net_http.rb +25 -27
  69. data/lib/aikido/zen/sinks/patron.rb +56 -58
  70. data/lib/aikido/zen/sinks/pg.rb +23 -25
  71. data/lib/aikido/zen/sinks/resolv.rb +21 -21
  72. data/lib/aikido/zen/sinks/socket.rb +17 -12
  73. data/lib/aikido/zen/sinks/sqlite3.rb +18 -21
  74. data/lib/aikido/zen/sinks/trilogy.rb +10 -12
  75. data/lib/aikido/zen/sinks.rb +1 -4
  76. data/lib/aikido/zen/sinks_dsl.rb +39 -15
  77. data/lib/aikido/zen/system_info.rb +1 -5
  78. data/lib/aikido/zen/version.rb +2 -2
  79. data/lib/aikido/zen.rb +78 -16
  80. data/tasklib/bench.rake +1 -1
  81. data/tasklib/libzen.rake +1 -0
  82. metadata +15 -5
  83. data/lib/aikido/zen/libzen-v0.1.39-arm64-linux.so +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 895c4d9ef772a24b65d8d177ad9f9b3c85af107233469a22f9a8c0feed06ce5f
4
- data.tar.gz: 417a5e62590c306c5846b874dc9d08bdc621f80f87a109349048b785d553c209
3
+ metadata.gz: eeeb7f7c6a3fd9ebd51915d35129a49480f1dbf7ac74136b9095d2e09af79520
4
+ data.tar.gz: bd706cfe01f382af08e13bb92692f40a7296f40bd564fdfbd1159805ffc16001
5
5
  SHA512:
6
- metadata.gz: d06cd8b71dca523344187a250b452f7c024435fb21d7db331d6cc8c9f1adb3b4e70732ea5dfd985d89e95f499b250780335f85eb38d845de71762d1787bdc79b
7
- data.tar.gz: 6f82d9dda5c3e875049259295ac9896cde37fa7b6559ff9d66c9e1aa8b83260f10e4cec991bf14493fdfe399009eab67931366b6ecd1ecd855eefc9277ae27ea
6
+ metadata.gz: 6a19e292419b63fbba355622372182b7b78c417f8e63bb17cf81247848fc66d67705c7ac782faceb2bee209249ed417aed7f1533f566b9faba8eea20bd3f6ad8
7
+ data.tar.gz: f69cfeae83de24abb0665ca0d0f3f1ef4d655725ddae20e85bdb7caf1d0ebf5bf942bd4dc3209c6987643cf58320a11d6cf6088383a2a49ac1b01272d5d7cafc
data/.simplecov CHANGED
@@ -6,6 +6,12 @@
6
6
  return if RUBY_VERSION < "3.0"
7
7
  return if ENV["DISABLE_COVERAGE"] == "true"
8
8
 
9
+ # Output coverage as LCOV to support CodeCov
10
+ if ENV["COVERAGE_OUTPUT_LCOV"] == "true"
11
+ SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
12
+ SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
13
+ end
14
+
9
15
  SimpleCov.start do
10
16
  # Make sure SimpleCov waits until after the tests
11
17
  # are finished to generate the coverage reports.
data/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
7
7
  [![Unit tests](https://github.com/AikidoSec/firewall-ruby/actions/workflows/main.yml/badge.svg)](https://github.com/AikidoSec/firewall-ruby/actions/workflows/main.yml)
8
8
  [![Release](https://github.com/AikidoSec/firewall-ruby/actions/workflows/release.yml/badge.svg)](https://github.com/AikidoSec/firewall-ruby/actions/workflows/release.yml)
9
+ [![codecov](https://codecov.io/gh/AikidoSec/firewall-ruby/graph/badge.svg?token=X0MLST7S15)](https://codecov.io/gh/AikidoSec/firewall-ruby)
9
10
 
10
11
  Zen, your in-app firewall for peace of mind - at runtime.
11
12
 
@@ -22,6 +23,7 @@ Zen will autonomously protect your Ruby applications against:
22
23
  * 🛡️ [Command injection attacks](https://www.aikido.dev/blog/command-injection-in-2024-unpacked)
23
24
  * 🛡️ [Path traversal attacks](https://www.aikido.dev/blog/path-traversal-in-2024-the-year-unpacked)
24
25
  * 🛡️ [NoSQL injection attacks](https://www.aikido.dev/blog/web-application-security-vulnerabilities) (coming soon)
26
+ * 🛡️ [Attack waves](https://help.aikido.dev/zen-firewall/zen-features/attack-wave-protection)
25
27
 
26
28
  Zen operates autonomously on the same server as your Ruby app to:
27
29
 
data/benchmarks/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  # Benchmarking Zen for Ruby
2
2
 
3
-
4
3
  We use [WRK](https://github.com/wg/wrk) & [Grafana K6](https://k6.io) for these.
5
4
 
6
5
  WRK benchmarks are only requesting a URL (`/benchmark`). In case you want to add more
@@ -0,0 +1 @@
1
+ rails7.1_sql_injection.js
@@ -1,5 +1,5 @@
1
- import http from 'k6/http';
2
- import {Trend} from 'k6/metrics';
1
+ import http from "k6/http";
2
+ import {Trend} from "k6/metrics";
3
3
 
4
4
  const HTTP = {
5
5
  withZen: {
@@ -10,7 +10,7 @@ const HTTP = {
10
10
  get: (path, ...args) => http.get("http://localhost:3002" + path, ...args),
11
11
  post: (path, ...args) => http.post("http://localhost:3002" + path, ...args)
12
12
  }
13
- }
13
+ };
14
14
 
15
15
  function test(name, fn) {
16
16
  const withZen = fn(HTTP.withZen);
@@ -36,35 +36,67 @@ function buildTestTrends(prefix) {
36
36
 
37
37
  const tests = {
38
38
  test_post_page_with_json_body: buildTestTrends("test_post_page_with_json_body"),
39
- test_get_page_without_attack: buildTestTrends("test_get_page_without_attack"),
40
- test_get_page_with_sql_injection: buildTestTrends("test_get_page_with_sql_injection")
41
- }
39
+ test_get_page_without_attack: buildTestTrends("test_get_page_without_attack")
40
+ };
41
+
42
42
  export const options = {
43
43
  vus: 1, // Number of virtual users
44
- iterations: 200,
44
+ duration: "60s",
45
45
  thresholds: {
46
- http_req_failed: ['rate==0'], // we are marking the attacks as expected, so we should have no errors
46
+ http_req_failed: ["rate==0"], // We are marking the attacks as expected, so we should have no errors.
47
47
  test_post_page_with_json_body_delta: ["med<10"],
48
- test_get_page_without_attack_delta: ["med<10"],
49
- test_get_page_with_sql_injection_delta: ["med<10"],
48
+ test_get_page_without_attack_delta: ["med<10"]
50
49
  }
51
50
  };
52
51
 
52
+ const headers = {
53
+ Authorization:
54
+ "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.Bw8sSk3kdnT9d803kqqE_LZJzY1PzMl5cbmuanQKxrI",
55
+ Accept:
56
+ "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
57
+ "Accept-Language": "en-US,en;q=0.9",
58
+ Dnt: "1",
59
+ Priority: "u=0, i",
60
+ "Sec-Ch-Ua":
61
+ '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
62
+ "Sec-Ch-Ua-Arch": '"arm"',
63
+ "Sec-Ch-Ua-Bitness": '"64"',
64
+ "Sec-Ch-Ua-Full-Version-List":
65
+ '"Not)A;Brand";v="99.0.0.0", "Google Chrome";v="127.0.6533.72", "Chromium";v="127.0.6533.72"',
66
+ "Sec-Ch-Ua-Mobile": "?0",
67
+ "Sec-Ch-Ua-Model": '""',
68
+ "Sec-Ch-Ua-Platform": '"macOS"',
69
+ "Sec-Ch-Ua-Platform-Version": '"14.5.0"',
70
+ "Sec-Ch-Ua-Wow64": "?0",
71
+ "Sec-Fetch-Dest": "document",
72
+ "Sec-Fetch-Mode": "navigate",
73
+ "Sec-Fetch-Site": "cross-site",
74
+ "Sec-Fetch-User": "?1",
75
+ "Sec-Gpc": "1",
76
+ "Upgrade-Insecure-Requests": "1",
77
+ "User-Agent":
78
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
79
+ };
80
+
53
81
  const expectAttack = http.expectedStatuses(200, 500);
54
82
 
55
83
  export default function () {
56
84
  test("test_post_page_with_json_body",
57
- (http) => http.post("/cats", JSON.stringify({cat: {name: "Féline Dion"}}), {
58
- headers: {
59
- "Content-Type": "application/json",
60
- "Accept": "application/json"
61
- }
62
- })
85
+ (http) => http.post("/cats",
86
+ JSON.stringify({cat: {name: "Féline Dion"}}),
87
+ {
88
+ headers: {
89
+ ...headers,
90
+ "Content-Type": "application/json",
91
+ "Accept": "application/json"
92
+ }
93
+ })
63
94
  )
64
95
 
65
- test("test_get_page_without_attack", (http) => http.get("/cats"))
66
-
67
- test("test_get_page_with_sql_injection", (http) =>
68
- http.get("/cats/1'%20OR%20''='", { responseCallback: expectAttack })
96
+ test("test_get_page_without_attack",
97
+ (http) => http.get("/cats/count",
98
+ {
99
+ headers: headers
100
+ })
69
101
  )
70
102
  }
data/docs/config.md CHANGED
@@ -8,7 +8,7 @@ other Rack-based apps).
8
8
  ## Disable Zen
9
9
 
10
10
  In order to fully turn off Zen and prevent it from intercepting any requests or
11
- reporting back to the Aikido servers, set `AIKIDO_DISABLED=true` in your
11
+ reporting back to the Aikido servers, set `AIKIDO_DISABLE=true` in your
12
12
  environment, or set `Aikido::Zen.config.disabled = true`.
13
13
 
14
14
  (We recommend the ENV variable as you can normally change this easily without
@@ -34,6 +34,14 @@ set it via `Aikido::Zen.config.token = <token>`.
34
34
 
35
35
  **NOTE**: Never commit your token to the source code repository in plain text.
36
36
 
37
+ ## Hardened mode
38
+
39
+ Zen hardens methods, restricting dangerous undocumented behavior to improve
40
+ security and performance.
41
+
42
+ To disable method hardening, set `AIKIDO_HARDEN=false` in your environment,
43
+ or set `Aikido::Zen.config.harden = false`.
44
+
37
45
  ## Logger
38
46
 
39
47
  Zen logs to standard output by default. You can change this by changing the
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,27 +2,71 @@
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
- Rails.application.config.zen.api_timeouts = 20
63
+ Rails.application.config.zen.option = value
20
64
  ```
21
65
 
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,15 +74,15 @@ 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_..."
37
81
  ```
38
82
 
39
- You can just tell Zen to use it like so:
83
+ You can 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
  ```
@@ -58,13 +102,11 @@ way.
58
102
 
59
103
  ## Logging
60
104
 
61
- By default, Zen will use the Rails logger, prefixing messages with `[aikido]`.
62
- You can redirect the log to a separate stream by overriding the logger:
105
+ You can override the logger to integrate with your application logging strategy:
63
106
 
64
- ```
107
+ ```ruby
65
108
  # config/initializers/zen.rb
66
- Rails.application.config.zen.logger = Logger.new(...)
109
+ Rails.application.config.zen.logger = ::Rails.logger
67
110
  ```
68
111
 
69
- You should supply an instance of ruby's [Logger](https://github.com/ruby/logger)
70
- class.
112
+ Zen expects an instance of Ruby's [Logger](https://github.com/ruby/logger) class.
@@ -0,0 +1,62 @@
1
+ # Troubleshooting
2
+
3
+ If the Zen Firewall isn't working as expected, follow these steps in order to diagnose common issues.
4
+
5
+ ## Review installation steps
6
+
7
+ Double-check your setup against the [installation guide](../README.md#installation).
8
+
9
+ Make sure:
10
+ - Your runtime and framework are supported (see [Supported libraries and frameworks](../README.md#supported-libraries-and-frameworks)).
11
+ - The package installed successfully.
12
+ - Your framework-specific integration matches the example in the docs (for Rails, see the example in [docs/rails.md](../docs/rails.md)).
13
+
14
+ ## Check connection to Aikido
15
+
16
+ The firewall must be able to reach Aikido's API endpoints.
17
+
18
+ Test connectivity from the same environment where your app runs, following the instructions on this page: https://help.aikido.dev/zen-firewall/miscellaneous/outbound-network-connections-for-zen
19
+
20
+ ## Check logs for errors
21
+
22
+ Common places:
23
+ - Local dev: `cat log/development.log` or `tail -f log/development.log`
24
+ - Docker: `docker logs <your-app-container>`
25
+ - systemd: `journalctl -u <your-app-service> --since "1 hour ago"`
26
+
27
+ Tip: search logs for lines containing `Aikido` or `Zen`.
28
+
29
+ For example:
30
+
31
+ ```sh
32
+ grep -Ei 'aikido|zen' log/development.log
33
+ ```
34
+
35
+ ## Enable debug output temporarily
36
+
37
+ If you use Rails, set the log level to `info` or `debug` while you investigate.
38
+
39
+ ```ruby
40
+ # config/environments/development.rb or production.rb
41
+ config.log_level = :info # use :debug if needed
42
+ ```
43
+
44
+ If you have your own logger, make sure Zen logs go to stdout.
45
+
46
+ You can enable Zen debugging mode as follows.
47
+
48
+ ```ruby
49
+ # config/initializers/zen.rb
50
+ Rails.application.config.zen.debugging = true
51
+ ```
52
+
53
+ Or set `AIKIDO_DEBUG=true` in your environment.
54
+
55
+ ## Contact support
56
+
57
+ If you still can't resolve the problem:
58
+
59
+ - Use the in-app chat to reach our support team directly.
60
+ - Or create an issue on [GitHub](https://github.com/AikidoSec/firewall-ruby/issues) with details about your setup, framework, and logs.
61
+
62
+ Include as much context as possible; this helps us respond faster.
@@ -38,6 +38,16 @@ module Aikido::Zen
38
38
 
39
39
  # Represents someone connecting to the application and making requests.
40
40
  class Actor
41
+ def self.from_json(data)
42
+ new(
43
+ id: data[:id],
44
+ name: data[:name],
45
+ ip: data[:lastIpAddress],
46
+ first_seen_at: Time.at(data[:firstSeenAt] / 1000),
47
+ last_seen_at: Time.at(data[:lastSeenAt] / 1000)
48
+ )
49
+ end
50
+
41
51
  # @return [String] a unique identifier for this user.
42
52
  attr_reader :id
43
53
 
@@ -50,18 +60,20 @@ module Aikido::Zen
50
60
  # @param id [String]
51
61
  # @param name [String, nil]
52
62
  # @param ip [String, nil]
53
- # @param seen_at [Time]
63
+ # @param first_seen_at [Time]
64
+ # @param last_seen_at [Time]
54
65
  def initialize(
55
66
  id:,
56
67
  name: nil,
57
68
  ip: Aikido::Zen.current_context&.request&.ip,
58
- seen_at: Time.now.utc
69
+ first_seen_at: Time.now.utc,
70
+ last_seen_at: first_seen_at
59
71
  )
60
72
  @id = id
61
73
  @name = name
62
- @first_seen_at = seen_at
63
- @last_seen_at = Concurrent::AtomicReference.new(seen_at)
64
74
  @ip = Concurrent::AtomicReference.new(ip)
75
+ @first_seen_at = first_seen_at
76
+ @last_seen_at = Concurrent::AtomicReference.new(last_seen_at)
65
77
  end
66
78
 
67
79
  # @return [Time]
@@ -89,6 +101,24 @@ module Aikido::Zen
89
101
  @ip.try_update { |last_ip| [ip, last_ip].compact.first }
90
102
  end
91
103
 
104
+ # Merges the actor with another actor.
105
+ #
106
+ # @param other [Aikido::Zen::Actor]
107
+ # @return [Aikido::Zen::Actor]
108
+ def merge(other)
109
+ older = (first_seen_at < other.first_seen_at) ? self : other
110
+ newer = (last_seen_at > other.last_seen_at) ? self : other
111
+
112
+ self.class.new(
113
+ id: @id,
114
+ name: newer.name,
115
+ ip: newer.ip,
116
+ first_seen_at: older.first_seen_at,
117
+ last_seen_at: newer.last_seen_at
118
+ )
119
+ end
120
+ alias_method :|, :merge
121
+
92
122
  # @return [self]
93
123
  def to_aikido_actor
94
124
  self
@@ -20,7 +20,7 @@ module Aikido::Zen
20
20
  # @return [Boolean] whether the currently running heartbeat matches the
21
21
  # expected interval in the runtime settings.
22
22
  def stale_settings?
23
- running? && @timer.execution_interval != @settings.heartbeat_interval
23
+ running? && @timer.execution_interval != interval
24
24
  end
25
25
 
26
26
  # Sets up the the timer to run the given block at the appropriate interval.
@@ -30,11 +30,11 @@ module Aikido::Zen
30
30
  def start(&task)
31
31
  return if running?
32
32
 
33
- if @settings.heartbeat_interval&.nonzero?
34
- @config.logger.debug "Scheduling heartbeats every #{@settings.heartbeat_interval} seconds"
35
- @timer = @worker.every(@settings.heartbeat_interval, run_now: false, &task)
33
+ if interval&.nonzero?
34
+ @config.logger.debug "Scheduling heartbeats every #{interval} seconds"
35
+ @timer = @worker.every(interval, run_now: false, &task)
36
36
  else
37
- @config.logger.warn(format("Heartbeat could not be set up (interval: %p)", @settings.heartbeat_interval))
37
+ @config.logger.warn(format("Heartbeat could not be set up (interval: %p)", interval))
38
38
  end
39
39
  end
40
40
 
@@ -37,22 +37,22 @@ module Aikido::Zen
37
37
  end
38
38
 
39
39
  def start!
40
- @config.logger.info "Starting Aikido agent v#{Aikido::Zen::VERSION}"
40
+ @config.logger.info("Starting Aikido agent v#{Aikido::Zen::VERSION}")
41
41
 
42
42
  raise Aikido::ZenError, "Aikido Agent already started!" if started?
43
43
  @started_at = Time.now.utc
44
44
  @collector.start(at: @started_at)
45
45
 
46
- if @config.blocking_mode?
47
- @config.logger.info "Requests identified as attacks will be blocked"
46
+ if Aikido::Zen.blocking_mode?
47
+ @config.logger.info("Requests identified as attacks will be blocked")
48
48
  else
49
- @config.logger.warn "Non-blocking mode enabled! No requests will be blocked."
49
+ @config.logger.warn("Non-blocking mode enabled! No requests will be blocked.")
50
50
  end
51
51
 
52
52
  if @api_client.can_make_requests?
53
- @config.logger.info "API Token set! Reporting has been enabled."
53
+ @config.logger.info("API Token set! Reporting has been enabled.")
54
54
  else
55
- @config.logger.warn "No API Token set! Reporting has been disabled."
55
+ @config.logger.warn("No API Token set! Reporting has been disabled.")
56
56
  return
57
57
  end
58
58
 
@@ -60,15 +60,18 @@ module Aikido::Zen
60
60
 
61
61
  report(Events::Started.new(time: @started_at)) do |response|
62
62
  updated_settings! if Aikido::Zen.runtime_settings.update_from_json(response)
63
- @config.logger.info "Updated runtime settings."
63
+ @config.logger.info("Updated runtime settings.")
64
64
  rescue => err
65
65
  @config.logger.error(err.message)
66
66
  end
67
67
 
68
68
  poll_for_setting_updates
69
69
 
70
- @worker.delay(@config.initial_heartbeat_delay) do
71
- send_heartbeat if @collector.stats.any?
70
+ @config.initial_heartbeat_delays.each do |heartbeat_delay|
71
+ @worker.delay(heartbeat_delay) do
72
+ send_heartbeat
73
+ @config.logger.info("Executed initial heartbeat after #{heartbeat_delay} seconds")
74
+ end
72
75
  end
73
76
  end
74
77
 
@@ -77,7 +80,7 @@ module Aikido::Zen
77
80
  #
78
81
  # @return [void]
79
82
  def stop!
80
- @config.logger.info "Stopping Aikido agent"
83
+ @config.logger.info("Stopping Aikido agent")
81
84
  @started_at = nil
82
85
  @worker.shutdown
83
86
  end
@@ -103,7 +106,7 @@ module Aikido::Zen
103
106
  # @raise [Aikido::Zen::UnderAttackError] if the firewall is configured
104
107
  # to block requests.
105
108
  def handle_attack(attack)
106
- attack.will_be_blocked! if @config.blocking_mode?
109
+ attack.will_be_blocked! if Aikido::Zen.blocking_mode?
107
110
 
108
111
  @config.logger.error(
109
112
  format("Zen has %s a %s: %s", attack.blocked? ? "blocked" : "detected", attack.humanized_name, attack.as_json.to_json)
@@ -143,11 +146,10 @@ module Aikido::Zen
143
146
  def send_heartbeat(at: Time.now.utc)
144
147
  return unless @api_client.can_make_requests?
145
148
 
146
- @collector.flush_heartbeats.each do |heartbeat|
147
- report(heartbeat) do |response|
148
- updated_settings! if Aikido::Zen.runtime_settings.update_from_json(response)
149
- @config.logger.info "Updated runtime settings after heartbeat"
150
- end
149
+ heartbeat = @collector.flush
150
+ report(heartbeat) do |response|
151
+ updated_settings! if Aikido::Zen.runtime_settings.update_from_json(response)
152
+ @config.logger.info("Updated runtime settings after heartbeat")
151
153
  end
152
154
  end
153
155
 
@@ -162,7 +164,7 @@ module Aikido::Zen
162
164
  @worker.every(@config.polling_interval) do
163
165
  if @api_client.should_fetch_settings?
164
166
  updated_settings! if Aikido::Zen.runtime_settings.update_from_json(@api_client.fetch_settings)
165
- @config.logger.info "Updated runtime settings after polling"
167
+ @config.logger.info("Updated runtime settings after polling")
166
168
  end
167
169
  end
168
170
  end
@@ -10,10 +10,11 @@ module Aikido::Zen
10
10
  attr_reader :operation
11
11
  attr_accessor :sink
12
12
 
13
- def initialize(context:, sink:, operation:)
13
+ def initialize(context:, sink:, operation:, stack: nil)
14
14
  @context = context
15
15
  @operation = operation
16
16
  @sink = sink
17
+ @stack = stack
17
18
  @blocked = false
18
19
  end
19
20
 
@@ -46,8 +47,9 @@ module Aikido::Zen
46
47
  kind: kind,
47
48
  blocked: blocked?,
48
49
  metadata: metadata,
49
- operation: @operation
50
- }.merge(input.as_json)
50
+ operation: @operation,
51
+ stack: @stack
52
+ }.compact.merge(input.as_json)
51
53
  end
52
54
 
53
55
  def exception(*)
@@ -67,7 +69,9 @@ module Aikido::Zen
67
69
  end
68
70
 
69
71
  def metadata
70
- {filename: filepath}
72
+ {
73
+ filename: filepath
74
+ }
71
75
  end
72
76
 
73
77
  def humanized_name
@@ -133,7 +137,10 @@ module Aikido::Zen
133
137
  end
134
138
 
135
139
  def metadata
136
- {sql: @query}
140
+ {
141
+ sql: @query,
142
+ dialect: @dialect.name
143
+ }
137
144
  end
138
145
 
139
146
  def exception(*)
@@ -165,8 +172,8 @@ module Aikido::Zen
165
172
 
166
173
  def metadata
167
174
  {
168
- host: @request.uri.hostname,
169
- port: @request.uri.port
175
+ hostname: @request.uri.hostname,
176
+ port: @request.uri.port.to_s
170
177
  }
171
178
  end
172
179
  end
@@ -192,7 +199,7 @@ module Aikido::Zen
192
199
  end
193
200
 
194
201
  def kind
195
- "ssrf"
202
+ "stored_ssrf"
196
203
  end
197
204
 
198
205
  def input
@@ -200,7 +207,10 @@ module Aikido::Zen
200
207
  end
201
208
 
202
209
  def metadata
203
- {}
210
+ {
211
+ hostname: @hostname,
212
+ privateIP: @address
213
+ }
204
214
  end
205
215
  end
206
216
  end