rack-ai 0.3.0 → 0.4.0
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/CHANGELOG.md +18 -14
- data/lib/rack/ai/features/classification.rb +8 -2
- data/lib/rack/ai/features/moderation.rb +17 -3
- data/lib/rack/ai/features/rate_limiter.rb +145 -0
- data/lib/rack/ai/features/security_scanner.rb +326 -0
- data/lib/rack/ai/utils/logger.rb +6 -5
- data/lib/rack/ai/version.rb +1 -1
- data/lib/rack/ai.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a73e35063e7e68e0af7c74603df68d971a2da7837b06d66c1721abc183929604
|
4
|
+
data.tar.gz: 9cca59b691a9a532944175d18c0b2b61e2c7d2e32c9a199b032180d7fbaea836
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce2759602f6428f04102889998d096aa776a403f62c3751bc26c8a7ae47040e0f658e55823108135f930c34261339d9a40c9bd3d9341e6a7114ea2e7e2042d63
|
7
|
+
data.tar.gz: 9f77a8725714b6107fdf069f3b1fc1d4a1124cb37264ed45932929e4bcca221fee49dccbb053245571e664e60dee76761983fe763ded8606c3b6ec83f78dd50b
|
data/CHANGELOG.md
CHANGED
@@ -7,25 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.4.0] - 2025-01-11
|
11
|
+
|
10
12
|
### Added
|
11
|
-
-
|
12
|
-
-
|
13
|
-
- Enhanced logging system with
|
14
|
-
- Comprehensive
|
15
|
-
- Advanced error handling and edge case coverage
|
16
|
-
- Performance optimizations and memory management
|
13
|
+
- **New Security Scanner Feature**: Advanced security threat detection with pattern matching for SQL injection, XSS, path traversal, and command injection
|
14
|
+
- **Rate Limiter Feature**: Built-in rate limiting with client identification, configurable windows, and penalty periods
|
15
|
+
- **Enhanced Logger**: Improved logging system with proper method delegation and structured output
|
16
|
+
- **Comprehensive Test Coverage**: Added complete test suites for new features with 96 passing tests
|
17
17
|
|
18
18
|
### Changed
|
19
|
-
-
|
20
|
-
-
|
21
|
-
- Enhanced
|
22
|
-
- Better test coverage and mocking strategies
|
19
|
+
- **Improved Error Handling**: Enhanced configuration validation with safe method access patterns
|
20
|
+
- **Better Code Quality**: Fixed potential null pointer exceptions in classification and moderation features
|
21
|
+
- **Enhanced Security**: Added defensive programming patterns throughout the codebase
|
23
22
|
|
24
23
|
### Fixed
|
25
|
-
-
|
26
|
-
-
|
27
|
-
-
|
28
|
-
-
|
24
|
+
- Logger method delegation issues - now properly uses structured logging
|
25
|
+
- Configuration access safety in classification feature routing checks
|
26
|
+
- Moderation feature configuration validation for toxicity thresholds
|
27
|
+
- Syntax errors in security scanner regex patterns
|
28
|
+
|
29
|
+
### Security
|
30
|
+
- Advanced threat detection patterns for multiple attack vectors
|
31
|
+
- Improved rate limiting with client fingerprinting
|
32
|
+
- Enhanced input validation and sanitization
|
29
33
|
|
30
34
|
## [0.3.0] - 2024-01-16
|
31
35
|
|
@@ -48,9 +48,9 @@ module Rack
|
|
48
48
|
when :spam
|
49
49
|
:block
|
50
50
|
when :suspicious
|
51
|
-
|
51
|
+
smart_routing_enabled? ? :route : :allow
|
52
52
|
when :bot
|
53
|
-
|
53
|
+
smart_routing_enabled? ? :route : :allow
|
54
54
|
when :human
|
55
55
|
:allow
|
56
56
|
else
|
@@ -58,6 +58,12 @@ module Rack
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
+
def smart_routing_enabled?
|
62
|
+
@config.respond_to?(:routing) &&
|
63
|
+
@config.routing.respond_to?(:smart_routing_enabled) &&
|
64
|
+
@config.routing.smart_routing_enabled
|
65
|
+
end
|
66
|
+
|
61
67
|
def generate_request_id(env)
|
62
68
|
"#{Time.now.to_i}-#{env.object_id}"
|
63
69
|
end
|
@@ -17,7 +17,9 @@ module Rack
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def process_response?
|
20
|
-
@config.moderation
|
20
|
+
@config.respond_to?(:moderation) &&
|
21
|
+
@config.moderation.respond_to?(:check_response) &&
|
22
|
+
@config.moderation.check_response
|
21
23
|
end
|
22
24
|
|
23
25
|
def process_request(env)
|
@@ -27,11 +29,11 @@ module Rack
|
|
27
29
|
result = @provider.moderate_content(content.join(" "))
|
28
30
|
|
29
31
|
# Apply toxicity threshold
|
30
|
-
threshold =
|
32
|
+
threshold = get_toxicity_threshold
|
31
33
|
max_score = result[:category_scores]&.values&.max || 0.0
|
32
34
|
|
33
35
|
result[:action] = if result[:flagged] && max_score > threshold
|
34
|
-
|
36
|
+
should_block_on_violation? ? :block : :flag
|
35
37
|
else
|
36
38
|
:allow
|
37
39
|
end
|
@@ -87,6 +89,18 @@ module Rack
|
|
87
89
|
content.compact.reject(&:empty?)
|
88
90
|
end
|
89
91
|
|
92
|
+
def get_toxicity_threshold
|
93
|
+
return 0.7 unless @config.respond_to?(:moderation)
|
94
|
+
return 0.7 unless @config.moderation.respond_to?(:toxicity_threshold)
|
95
|
+
@config.moderation.toxicity_threshold
|
96
|
+
end
|
97
|
+
|
98
|
+
def should_block_on_violation?
|
99
|
+
return true unless @config.respond_to?(:moderation)
|
100
|
+
return true unless @config.moderation.respond_to?(:block_on_violation)
|
101
|
+
@config.moderation.block_on_violation
|
102
|
+
end
|
103
|
+
|
90
104
|
def extract_content_from_response(body)
|
91
105
|
return "" unless body.respond_to?(:each)
|
92
106
|
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module AI
|
7
|
+
module Features
|
8
|
+
class RateLimiter
|
9
|
+
attr_reader :name, :config
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@name = :rate_limiter
|
13
|
+
@config = config
|
14
|
+
@cache = {}
|
15
|
+
@cleanup_interval = 300 # 5 minutes
|
16
|
+
@last_cleanup = Time.now
|
17
|
+
end
|
18
|
+
|
19
|
+
def enabled?
|
20
|
+
@config.feature_enabled?(:rate_limiter)
|
21
|
+
end
|
22
|
+
|
23
|
+
def process_response?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_request(env)
|
28
|
+
return { processed: false, reason: "disabled" } unless enabled?
|
29
|
+
|
30
|
+
client_id = extract_client_id(env)
|
31
|
+
current_time = Time.now
|
32
|
+
|
33
|
+
cleanup_expired_entries if should_cleanup?
|
34
|
+
|
35
|
+
# Get or initialize client data
|
36
|
+
client_data = @cache[client_id] ||= {
|
37
|
+
requests: [],
|
38
|
+
blocked_until: nil
|
39
|
+
}
|
40
|
+
|
41
|
+
# Check if client is currently blocked
|
42
|
+
if client_data[:blocked_until] && current_time < client_data[:blocked_until]
|
43
|
+
return {
|
44
|
+
processed: true,
|
45
|
+
action: :block,
|
46
|
+
reason: "rate_limited",
|
47
|
+
blocked_until: client_data[:blocked_until],
|
48
|
+
feature: @name,
|
49
|
+
timestamp: current_time.iso8601
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Clean old requests
|
54
|
+
window_start = current_time - rate_limit_window
|
55
|
+
client_data[:requests].reject! { |req_time| req_time < window_start }
|
56
|
+
|
57
|
+
# Check rate limit
|
58
|
+
if client_data[:requests].size >= max_requests_per_window
|
59
|
+
# Block client for penalty duration
|
60
|
+
client_data[:blocked_until] = current_time + penalty_duration
|
61
|
+
|
62
|
+
return {
|
63
|
+
processed: true,
|
64
|
+
action: :block,
|
65
|
+
reason: "rate_limit_exceeded",
|
66
|
+
requests_count: client_data[:requests].size,
|
67
|
+
blocked_until: client_data[:blocked_until],
|
68
|
+
feature: @name,
|
69
|
+
timestamp: current_time.iso8601
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
# Record this request
|
74
|
+
client_data[:requests] << current_time
|
75
|
+
|
76
|
+
{
|
77
|
+
processed: true,
|
78
|
+
action: :allow,
|
79
|
+
requests_count: client_data[:requests].size,
|
80
|
+
remaining_requests: max_requests_per_window - client_data[:requests].size,
|
81
|
+
feature: @name,
|
82
|
+
timestamp: current_time.iso8601
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def extract_client_id(env)
|
89
|
+
# Try multiple identification methods
|
90
|
+
client_ip = env['HTTP_X_FORWARDED_FOR']&.split(',')&.first&.strip ||
|
91
|
+
env['HTTP_X_REAL_IP'] ||
|
92
|
+
env['REMOTE_ADDR'] ||
|
93
|
+
'unknown'
|
94
|
+
|
95
|
+
user_agent = env['HTTP_USER_AGENT'] || 'unknown'
|
96
|
+
|
97
|
+
# Create a hash of IP + User Agent for better identification
|
98
|
+
Digest::SHA256.hexdigest("#{client_ip}:#{user_agent}")[0..16]
|
99
|
+
end
|
100
|
+
|
101
|
+
def rate_limit_window
|
102
|
+
get_config_value(:rate_limit_window, 60) # 1 minute default
|
103
|
+
end
|
104
|
+
|
105
|
+
def max_requests_per_window
|
106
|
+
get_config_value(:max_requests_per_window, 100) # 100 requests per minute default
|
107
|
+
end
|
108
|
+
|
109
|
+
def penalty_duration
|
110
|
+
get_config_value(:penalty_duration, 300) # 5 minutes default
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_config_value(key, default)
|
114
|
+
return default unless @config.respond_to?(:rate_limiter)
|
115
|
+
return default unless @config.rate_limiter.respond_to?(key)
|
116
|
+
@config.rate_limiter.public_send(key)
|
117
|
+
end
|
118
|
+
|
119
|
+
def should_cleanup?
|
120
|
+
Time.now - @last_cleanup > @cleanup_interval
|
121
|
+
end
|
122
|
+
|
123
|
+
def cleanup_expired_entries
|
124
|
+
current_time = Time.now
|
125
|
+
window_start = current_time - rate_limit_window
|
126
|
+
|
127
|
+
@cache.each do |client_id, data|
|
128
|
+
# Remove expired requests
|
129
|
+
data[:requests].reject! { |req_time| req_time < window_start }
|
130
|
+
|
131
|
+
# Remove expired blocks
|
132
|
+
data[:blocked_until] = nil if data[:blocked_until] && current_time >= data[:blocked_until]
|
133
|
+
|
134
|
+
# Remove empty entries
|
135
|
+
if data[:requests].empty? && data[:blocked_until].nil?
|
136
|
+
@cache.delete(client_id)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
@last_cleanup = current_time
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module AI
|
5
|
+
module Features
|
6
|
+
class SecurityScanner
|
7
|
+
attr_reader :name, :config
|
8
|
+
|
9
|
+
def initialize(config)
|
10
|
+
@name = :security_scanner
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
def enabled?
|
15
|
+
@config.feature_enabled?(:security_scanner)
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_response?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def process_request(env)
|
23
|
+
return { processed: false, reason: "disabled" } unless enabled?
|
24
|
+
|
25
|
+
threats = []
|
26
|
+
risk_score = 0.0
|
27
|
+
|
28
|
+
# Check for various security threats
|
29
|
+
threats.concat(check_sql_injection(env))
|
30
|
+
threats.concat(check_xss_attempts(env))
|
31
|
+
threats.concat(check_path_traversal(env))
|
32
|
+
threats.concat(check_command_injection(env))
|
33
|
+
threats.concat(check_suspicious_headers(env))
|
34
|
+
threats.concat(check_malicious_user_agents(env))
|
35
|
+
|
36
|
+
# Calculate overall risk score
|
37
|
+
risk_score = calculate_risk_score(threats)
|
38
|
+
threat_level = determine_threat_level(risk_score)
|
39
|
+
|
40
|
+
{
|
41
|
+
processed: true,
|
42
|
+
action: determine_action(threat_level),
|
43
|
+
threats: threats,
|
44
|
+
risk_score: risk_score,
|
45
|
+
threat_level: threat_level,
|
46
|
+
feature: @name,
|
47
|
+
timestamp: Time.now.iso8601
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def check_sql_injection(env)
|
54
|
+
threats = []
|
55
|
+
patterns = [
|
56
|
+
/union\s+select/i,
|
57
|
+
/drop\s+table/i,
|
58
|
+
/insert\s+into/i,
|
59
|
+
/delete\s+from/i,
|
60
|
+
/update\s+.*set/i,
|
61
|
+
/exec\s*\(/i,
|
62
|
+
/script\s*>/i,
|
63
|
+
/'.*or.*'.*='/i,
|
64
|
+
/1=1/i,
|
65
|
+
/admin'--/i
|
66
|
+
]
|
67
|
+
|
68
|
+
check_patterns_in_request(env, patterns, 'sql_injection').each do |threat|
|
69
|
+
threats << threat
|
70
|
+
end
|
71
|
+
|
72
|
+
threats
|
73
|
+
end
|
74
|
+
|
75
|
+
def check_xss_attempts(env)
|
76
|
+
threats = []
|
77
|
+
patterns = [
|
78
|
+
/<script[^>]*>.*?<\/script>/i,
|
79
|
+
/javascript:/i,
|
80
|
+
/on\w+\s*=/i,
|
81
|
+
/<iframe[^>]*>/i,
|
82
|
+
/<object[^>]*>/i,
|
83
|
+
/<embed[^>]*>/i,
|
84
|
+
/eval\s*\(/i,
|
85
|
+
/alert\s*\(/i,
|
86
|
+
/document\.cookie/i
|
87
|
+
]
|
88
|
+
|
89
|
+
check_patterns_in_request(env, patterns, 'xss_attempt').each do |threat|
|
90
|
+
threats << threat
|
91
|
+
end
|
92
|
+
|
93
|
+
threats
|
94
|
+
end
|
95
|
+
|
96
|
+
def check_path_traversal(env)
|
97
|
+
threats = []
|
98
|
+
patterns = [
|
99
|
+
/\.\.\//,
|
100
|
+
/\.\.\\/,
|
101
|
+
/%2e%2e%2f/i,
|
102
|
+
/%2e%2e%5c/i,
|
103
|
+
/etc\/passwd/i,
|
104
|
+
/windows\/system32/i
|
105
|
+
]
|
106
|
+
|
107
|
+
check_patterns_in_request(env, patterns, 'path_traversal').each do |threat|
|
108
|
+
threats << threat
|
109
|
+
end
|
110
|
+
|
111
|
+
threats
|
112
|
+
end
|
113
|
+
|
114
|
+
def check_command_injection(env)
|
115
|
+
threats = []
|
116
|
+
patterns = [
|
117
|
+
/;\s*cat\s+/i,
|
118
|
+
/;\s*ls\s+/i,
|
119
|
+
/;\s*rm\s+/i,
|
120
|
+
/;\s*wget\s+/i,
|
121
|
+
/;\s*curl\s+/i,
|
122
|
+
/\|\s*nc\s+/i,
|
123
|
+
/&&\s*whoami/i,
|
124
|
+
/`.*`/,
|
125
|
+
/\$\(.*\)/
|
126
|
+
]
|
127
|
+
|
128
|
+
check_patterns_in_request(env, patterns, 'command_injection').each do |threat|
|
129
|
+
threats << threat
|
130
|
+
end
|
131
|
+
|
132
|
+
threats
|
133
|
+
end
|
134
|
+
|
135
|
+
def check_suspicious_headers(env)
|
136
|
+
threats = []
|
137
|
+
|
138
|
+
# Check for suspicious User-Agent patterns
|
139
|
+
user_agent = env['HTTP_USER_AGENT']
|
140
|
+
if user_agent
|
141
|
+
suspicious_agents = [
|
142
|
+
/sqlmap/i,
|
143
|
+
/nikto/i,
|
144
|
+
/nessus/i,
|
145
|
+
/burp/i,
|
146
|
+
/w3af/i,
|
147
|
+
/acunetix/i,
|
148
|
+
/netsparker/i
|
149
|
+
]
|
150
|
+
|
151
|
+
suspicious_agents.each do |pattern|
|
152
|
+
if user_agent.match?(pattern)
|
153
|
+
threats << {
|
154
|
+
type: 'suspicious_user_agent',
|
155
|
+
pattern: pattern.source,
|
156
|
+
value: user_agent,
|
157
|
+
severity: 'high'
|
158
|
+
}
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Check for suspicious headers
|
164
|
+
suspicious_headers = %w[
|
165
|
+
HTTP_X_FORWARDED_HOST
|
166
|
+
HTTP_X_ORIGINAL_URL
|
167
|
+
HTTP_X_REWRITE_URL
|
168
|
+
]
|
169
|
+
|
170
|
+
suspicious_headers.each do |header|
|
171
|
+
if env[header] && env[header].include?('..')
|
172
|
+
threats << {
|
173
|
+
type: 'suspicious_header',
|
174
|
+
header: header,
|
175
|
+
value: env[header],
|
176
|
+
severity: 'medium'
|
177
|
+
}
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
threats
|
182
|
+
end
|
183
|
+
|
184
|
+
def check_malicious_user_agents(env)
|
185
|
+
threats = []
|
186
|
+
user_agent = env['HTTP_USER_AGENT']
|
187
|
+
return threats unless user_agent
|
188
|
+
|
189
|
+
# Check for bot patterns that might be malicious
|
190
|
+
malicious_patterns = [
|
191
|
+
/python-requests/i,
|
192
|
+
/curl/i,
|
193
|
+
/wget/i,
|
194
|
+
/libwww/i,
|
195
|
+
/lwp-trivial/i,
|
196
|
+
/^$/ # Empty user agent
|
197
|
+
]
|
198
|
+
|
199
|
+
malicious_patterns.each do |pattern|
|
200
|
+
if user_agent.match?(pattern)
|
201
|
+
threats << {
|
202
|
+
type: 'potentially_malicious_bot',
|
203
|
+
pattern: pattern.source,
|
204
|
+
value: user_agent,
|
205
|
+
severity: 'low'
|
206
|
+
}
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
threats
|
211
|
+
end
|
212
|
+
|
213
|
+
def check_patterns_in_request(env, patterns, threat_type)
|
214
|
+
threats = []
|
215
|
+
|
216
|
+
# Check query string
|
217
|
+
if env['QUERY_STRING']
|
218
|
+
patterns.each do |pattern|
|
219
|
+
if env['QUERY_STRING'].match?(pattern)
|
220
|
+
threats << {
|
221
|
+
type: threat_type,
|
222
|
+
location: 'query_string',
|
223
|
+
pattern: pattern.source,
|
224
|
+
value: env['QUERY_STRING'],
|
225
|
+
severity: 'high'
|
226
|
+
}
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Check POST data
|
232
|
+
if %w[POST PUT PATCH].include?(env['REQUEST_METHOD']) && env['rack.input']
|
233
|
+
begin
|
234
|
+
body = env['rack.input'].read
|
235
|
+
env['rack.input'].rewind if env['rack.input'].respond_to?(:rewind)
|
236
|
+
|
237
|
+
patterns.each do |pattern|
|
238
|
+
if body.match?(pattern)
|
239
|
+
threats << {
|
240
|
+
type: threat_type,
|
241
|
+
location: 'request_body',
|
242
|
+
pattern: pattern.source,
|
243
|
+
value: body[0..200], # Truncate for logging
|
244
|
+
severity: 'high'
|
245
|
+
}
|
246
|
+
end
|
247
|
+
end
|
248
|
+
rescue => e
|
249
|
+
# Log error but don't fail
|
250
|
+
Utils::Logger.warn("Error reading request body for security scan", { error: e.message })
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Check headers
|
255
|
+
env.each do |key, value|
|
256
|
+
next unless key.start_with?('HTTP_')
|
257
|
+
next unless value.is_a?(String)
|
258
|
+
|
259
|
+
patterns.each do |pattern|
|
260
|
+
if value.match?(pattern)
|
261
|
+
threats << {
|
262
|
+
type: threat_type,
|
263
|
+
location: 'headers',
|
264
|
+
header: key,
|
265
|
+
pattern: pattern.source,
|
266
|
+
value: value,
|
267
|
+
severity: 'medium'
|
268
|
+
}
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
threats
|
274
|
+
end
|
275
|
+
|
276
|
+
def calculate_risk_score(threats)
|
277
|
+
return 0.0 if threats.empty?
|
278
|
+
|
279
|
+
score = 0.0
|
280
|
+
threats.each do |threat|
|
281
|
+
case threat[:severity]
|
282
|
+
when 'high'
|
283
|
+
score += 0.4
|
284
|
+
when 'medium'
|
285
|
+
score += 0.2
|
286
|
+
when 'low'
|
287
|
+
score += 0.1
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
[score, 1.0].min # Cap at 1.0
|
292
|
+
end
|
293
|
+
|
294
|
+
def determine_threat_level(risk_score)
|
295
|
+
case risk_score
|
296
|
+
when 0.0...0.2
|
297
|
+
:low
|
298
|
+
when 0.2...0.5
|
299
|
+
:medium
|
300
|
+
when 0.5...0.8
|
301
|
+
:high
|
302
|
+
else
|
303
|
+
:critical
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def determine_action(threat_level)
|
308
|
+
case threat_level
|
309
|
+
when :critical, :high
|
310
|
+
:block
|
311
|
+
when :medium
|
312
|
+
get_config_value(:medium_threat_action, :flag)
|
313
|
+
else
|
314
|
+
:allow
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def get_config_value(key, default)
|
319
|
+
return default unless @config.respond_to?(:security_scanner)
|
320
|
+
return default unless @config.security_scanner.respond_to?(key)
|
321
|
+
@config.security_scanner.public_send(key)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
data/lib/rack/ai/utils/logger.rb
CHANGED
@@ -13,23 +13,24 @@ module Rack
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def debug(message, metadata = {})
|
16
|
-
|
16
|
+
return unless ENV['RACK_AI_DEBUG']
|
17
|
+
log(:debug, message, metadata)
|
17
18
|
end
|
18
19
|
|
19
20
|
def info(message, metadata = {})
|
20
|
-
|
21
|
+
log(:info, message, metadata)
|
21
22
|
end
|
22
23
|
|
23
24
|
def warn(message, metadata = {})
|
24
|
-
|
25
|
+
log(:warn, message, metadata)
|
25
26
|
end
|
26
27
|
|
27
28
|
def error(message, metadata = {})
|
28
|
-
|
29
|
+
log(:error, message, metadata)
|
29
30
|
end
|
30
31
|
|
31
32
|
def fatal(message, metadata = {})
|
32
|
-
|
33
|
+
log(:fatal, message, metadata)
|
33
34
|
end
|
34
35
|
|
35
36
|
private
|
data/lib/rack/ai/version.rb
CHANGED
data/lib/rack/ai.rb
CHANGED
@@ -16,6 +16,8 @@ require_relative "ai/features/logging"
|
|
16
16
|
require_relative "ai/features/enhancement"
|
17
17
|
require_relative "ai/features/rate_limiting"
|
18
18
|
require_relative "ai/features/anomaly_detection"
|
19
|
+
require_relative "ai/features/rate_limiter"
|
20
|
+
require_relative "ai/features/security_scanner"
|
19
21
|
require_relative "ai/utils/logger"
|
20
22
|
require_relative "ai/utils/metrics"
|
21
23
|
require_relative "ai/utils/sanitizer"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-ai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ahmet KAHRAMAN
|
@@ -254,9 +254,11 @@ files:
|
|
254
254
|
- lib/rack/ai/features/enhancement.rb
|
255
255
|
- lib/rack/ai/features/logging.rb
|
256
256
|
- lib/rack/ai/features/moderation.rb
|
257
|
+
- lib/rack/ai/features/rate_limiter.rb
|
257
258
|
- lib/rack/ai/features/rate_limiting.rb
|
258
259
|
- lib/rack/ai/features/routing.rb
|
259
260
|
- lib/rack/ai/features/security.rb
|
261
|
+
- lib/rack/ai/features/security_scanner.rb
|
260
262
|
- lib/rack/ai/middleware.rb
|
261
263
|
- lib/rack/ai/providers/base.rb
|
262
264
|
- lib/rack/ai/providers/huggingface.rb
|