relintio-agent 0.1.2 → 0.1.3

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: d42d341367847d5102fcbf1cddc8ef608b4bbd1382d1612fd20d80943fc1ffcf
4
- data.tar.gz: 81572cfb40adf0335c1b8312de64355587558f92ae9ead2208376030dae89013
3
+ metadata.gz: 23398699d3740487e32d28ec7e95b17e52e0741a46aec0404d5c029999a91be8
4
+ data.tar.gz: 8c1a757870bb6690d96a433d6fc3bb46cf63102fc14815989805318cf47eea9f
5
5
  SHA512:
6
- metadata.gz: 10081c0eeb8ec80e8a9717b029b493883f4b5863c8dfe703d11f38870d72ee622b237be9b06c2412c28d59f5a7d6aee08f4ac15108836a20d20c4da88143da5c
7
- data.tar.gz: 558324f7c55ffc91e1fe9c2c21844e0c48e5e7c02829f839cadf2ae631897d5e00753d6d7051a828bd973e60e4a10dbd65e47627fbc883bb1eb55a9ae891a1d5
6
+ metadata.gz: 5a218ef36dddcdf36e1b193ca4c634778649521d0eccf08375b30eca6280108afad7eb33666a7fd0b8d945cebabee110c4b4ff2fd6473d5c46e6fd689f68d00f
7
+ data.tar.gz: 4b5af35ffe5f8d6a2e3289efc45e2287b82678ea224f1552f42d4c3ae33830a652f10961abab36ae7c558751d55b88050b2049b680290aac577c3ae0c692d377
data/README.md CHANGED
@@ -40,7 +40,8 @@ end
40
40
  # Initialize Relintio Agent
41
41
  agent = Relintio::Agent.new(
42
42
  license_key: "YOUR_LICENSE_KEY",
43
- api_url: "https://api.relintio.com/api"
43
+ domain: "example.com",
44
+ api_url: "https://relintio.com/api"
44
45
  )
45
46
  agent.start_sync
46
47
 
@@ -61,7 +62,8 @@ module YourApp
61
62
  # Initialize Relintio Agent
62
63
  config.after_initialize do
63
64
  $relintio_agent = Relintio::Agent.new(
64
- license_key: ENV['RELINTIO_LICENSE_KEY'] || 'YOUR_LICENSE_KEY'
65
+ license_key: ENV['RELINTIO_LICENSE_KEY'] || 'YOUR_LICENSE_KEY',
66
+ domain: ENV['RELINTIO_DOMAIN'] || 'example.com'
65
67
  )
66
68
  $relintio_agent.start_sync
67
69
  end
@@ -20,7 +20,7 @@ module Relintio
20
20
 
21
21
  if res[:action] == "block"
22
22
  return [403, { 'Content-Type' => 'text/html' }, [
23
- `<!DOCTYPE html><html><head><title>Access Denied</title><style>body{background:#000;color:#fff;font-family:sans-serif;padding:50px;text-align:center;}</style></head><body><h1>403 Forbidden</h1><p>Request blocked by Relintio WAF protection.</p></body></html>`
23
+ '<!DOCTYPE html><html><head><title>Access Denied</title><style>body{background:#000;color:#fff;font-family:sans-serif;padding:50px;text-align:center;}</style></head><body><h1>403 Forbidden</h1><p>Request blocked by Relintio WAF protection.</p></body></html>'
24
24
  ]]
25
25
  end
26
26
 
@@ -31,7 +31,7 @@ module Relintio
31
31
  'X-Relintio-Action' => 'challenge',
32
32
  'X-Relintio-Challenge-URL' => challenge_url
33
33
  }, [
34
- `<!DOCTYPE html><html><head><title>Security Challenge</title></head><body><script>window.location.href="#{challenge_url}";</script></body></html>`
34
+ '<!DOCTYPE html><html><head><title>Security Challenge</title></head><body><script>window.location.href="' + challenge_url + '";</script></body></html>'
35
35
  ]]
36
36
  end
37
37
 
@@ -5,47 +5,70 @@ require 'thread'
5
5
 
6
6
  module Relintio
7
7
  class Agent
8
+ AGENT_VERSION = '0.1.1'
9
+
8
10
  attr_reader :config, :rules
9
11
 
10
12
  def initialize(config = {})
11
13
  @config = {
12
14
  license_key: config[:license_key],
13
- api_url: config[:api_url] || "https://api.relintio.com/api",
15
+ api_url: config[:api_url] || "https://relintio.com/api",
16
+ domain: config[:domain] || '',
14
17
  sync_interval: config[:sync_interval] || 60
15
18
  }
16
19
  @rules = []
17
20
  @rules_mutex = Mutex.new
18
- @client = Net::HTTP
21
+ @running = true
22
+ @telemetry_queue = SizedQueue.new(1_000)
23
+ @telemetry_thread = Thread.new { telemetry_loop }
24
+ @telemetry_thread.report_on_exception = false
19
25
  end
20
26
 
21
27
  def start_sync
22
- Thread.new do
23
- loop do
28
+ return @sync_thread if @sync_thread&.alive?
29
+
30
+ @sync_thread = Thread.new do
31
+ while @running
24
32
  sync_rules
25
33
  sleep @config[:sync_interval]
26
34
  end
27
35
  end
36
+ @sync_thread.report_on_exception = false
37
+ @sync_thread
38
+ end
39
+
40
+ def stop
41
+ @running = false
42
+ @sync_thread&.wakeup if @sync_thread&.alive?
43
+ @telemetry_queue << nil
44
+ @sync_thread&.join(2)
45
+ @telemetry_thread&.join(2)
28
46
  end
29
47
 
30
48
  def sync_rules
31
49
  begin
32
- uri = URI.parse("#{@config[:api_url].chomp('/')}/rules/sync")
33
- req = Net::HTTP::Get.new(uri)
34
- req['Authorization'] = "Bearer #{@config[:license_key]}"
50
+ uri = URI.parse("#{@config[:api_url].chomp('/')}/agent/verify")
51
+ req = Net::HTTP::Post.new(uri)
35
52
  req['Content-Type'] = 'application/json'
36
-
37
- http = Net::HTTP.new(uri.host, uri.port)
38
- http.use_ssl = (uri.scheme == 'https')
39
- http.read_timeout = 10
40
-
41
- res = http.request(req)
53
+ req.body = {
54
+ license_key: @config[:license_key],
55
+ domain: @config[:domain],
56
+ protocol_version: 1,
57
+ agent_kind: 'ruby',
58
+ agent_version: AGENT_VERSION,
59
+ capabilities: %w[custom_rules telemetry]
60
+ }.to_json
61
+
62
+ res = http_client(uri).request(req)
42
63
  if res.code == '200'
64
+ return if res.body.bytesize > 1_048_576
65
+
43
66
  data = JSON.parse(res.body)
44
67
  @rules_mutex.synchronize do
45
68
  @rules = data['rules'] || []
46
69
  end
47
70
  end
48
- rescue => e
71
+ rescue StandardError
49
72
  # Fail-open: ignore errors during sync
50
73
  end
51
74
  end
@@ -91,35 +114,48 @@ module Relintio
91
114
  end
92
115
 
93
116
  def send_telemetry(request, result)
94
- Thread.new do
117
+ return unless @running
118
+
119
+ @telemetry_queue.push({
120
+ license_key: @config[:license_key],
121
+ ip: request.ip,
122
+ user_agent: request.user_agent || '',
123
+ path: request.path_info,
124
+ risk_score: [[result[:score].to_i, 0].max, 100].min,
125
+ action: result[:action].to_s.upcase,
126
+ reason_code: 'sdk_rule',
127
+ protocol_version: 1,
128
+ agent_kind: 'ruby',
129
+ agent_version: AGENT_VERSION
130
+ }, true)
131
+ rescue ThreadError
132
+ nil
133
+ end
134
+
135
+ private
136
+
137
+ def telemetry_loop
138
+ while (payload = @telemetry_queue.pop)
95
139
  begin
96
- uri = URI.parse("#{@config[:api_url].chomp('/')}/telemetry/log")
140
+ uri = URI.parse("#{@config[:api_url].chomp('/')}/agent/log")
97
141
  req = Net::HTTP::Post.new(uri)
98
- req['Authorization'] = "Bearer #{@config[:license_key]}"
99
142
  req['Content-Type'] = 'application/json'
100
-
101
- payload = {
102
- ip: request.ip,
103
- user_agent: request.user_agent || "",
104
- path: request.path_info,
105
- score: result[:score],
106
- action: result[:action],
107
- timestamp: Time.now.to_i
108
- }
109
-
110
143
  req.body = payload.to_json
111
-
112
- http = Net::HTTP.new(uri.host, uri.port)
113
- http.use_ssl = (uri.scheme == 'https')
114
- http.read_timeout = 10
115
- http.request(req)
116
- rescue
117
- # Fail-open
144
+ http_client(uri).request(req)
145
+ rescue StandardError
146
+ next
118
147
  end
119
148
  end
120
149
  end
121
150
 
122
- private
151
+ def http_client(uri)
152
+ Net::HTTP.new(uri.host, uri.port).tap do |http|
153
+ http.use_ssl = uri.scheme == 'https'
154
+ http.open_timeout = 5
155
+ http.read_timeout = 10
156
+ http.write_timeout = 10 if http.respond_to?(:write_timeout=)
157
+ end
158
+ end
123
159
 
124
160
  def match_value(val, pattern, condition)
125
161
  return false if val.nil? || pattern.nil?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relintio-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Relintio Operations
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-04 00:00:00.000000000 Z
11
+ date: 2026-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack