beskar 0.0.1 → 0.1.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -0
  3. data/README.md +987 -21
  4. data/app/controllers/beskar/application_controller.rb +170 -0
  5. data/app/controllers/beskar/banned_ips_controller.rb +280 -0
  6. data/app/controllers/beskar/dashboard_controller.rb +70 -0
  7. data/app/controllers/beskar/security_events_controller.rb +182 -0
  8. data/app/controllers/concerns/beskar/controllers/security_tracking.rb +70 -0
  9. data/app/models/beskar/banned_ip.rb +193 -0
  10. data/app/models/beskar/security_event.rb +64 -0
  11. data/app/services/beskar/banned_ip_manager.rb +78 -0
  12. data/app/views/beskar/banned_ips/edit.html.erb +259 -0
  13. data/app/views/beskar/banned_ips/index.html.erb +361 -0
  14. data/app/views/beskar/banned_ips/new.html.erb +310 -0
  15. data/app/views/beskar/banned_ips/show.html.erb +310 -0
  16. data/app/views/beskar/dashboard/index.html.erb +280 -0
  17. data/app/views/beskar/security_events/index.html.erb +309 -0
  18. data/app/views/beskar/security_events/show.html.erb +307 -0
  19. data/app/views/layouts/beskar/application.html.erb +647 -5
  20. data/config/locales/en.yml +10 -0
  21. data/config/routes.rb +41 -0
  22. data/db/migrate/20251016000001_create_beskar_security_events.rb +25 -0
  23. data/db/migrate/20251016000002_create_beskar_banned_ips.rb +23 -0
  24. data/lib/beskar/configuration.rb +214 -0
  25. data/lib/beskar/engine.rb +105 -0
  26. data/lib/beskar/logger.rb +293 -0
  27. data/lib/beskar/middleware/request_analyzer.rb +305 -0
  28. data/lib/beskar/middleware.rb +4 -0
  29. data/lib/beskar/models/security_trackable.rb +25 -0
  30. data/lib/beskar/models/security_trackable_authenticable.rb +167 -0
  31. data/lib/beskar/models/security_trackable_devise.rb +82 -0
  32. data/lib/beskar/models/security_trackable_generic.rb +355 -0
  33. data/lib/beskar/services/account_locker.rb +263 -0
  34. data/lib/beskar/services/device_detector.rb +250 -0
  35. data/lib/beskar/services/geolocation_service.rb +392 -0
  36. data/lib/beskar/services/ip_whitelist.rb +113 -0
  37. data/lib/beskar/services/rate_limiter.rb +257 -0
  38. data/lib/beskar/services/waf.rb +551 -0
  39. data/lib/beskar/version.rb +1 -1
  40. data/lib/beskar.rb +32 -1
  41. data/lib/generators/beskar/install/install_generator.rb +158 -0
  42. data/lib/generators/beskar/install/templates/initializer.rb.tt +177 -0
  43. data/lib/tasks/beskar_tasks.rake +121 -4
  44. metadata +138 -5
@@ -0,0 +1,551 @@
1
+ module Beskar
2
+ module Services
3
+ class Waf
4
+ # Common vulnerability scan patterns
5
+ VULNERABILITY_PATTERNS = {
6
+ rails_exceptions: {
7
+ patterns: [
8
+ %r{/(?:users?|posts?|articles?|comments?|api/v\d+/\w+)/\d+\.(?:exe|bat|cmd|com|scr|vbs|jar|app|deb|rpm)$}i, # Rails resources with executable extensions
9
+ %r{/(?:users?|posts?|articles?|comments?|api/v\d+/\w+)\.(?:asp|aspx|jsp|do|action|cgi|pl|py|rb)$}i, # Rails routes with server-side script extensions
10
+ %r{\?format=(?:exe|bat|cmd|com|scr|vbs|jar|asp|aspx|jsp|php)$}i, # Suspicious format in query params
11
+ ],
12
+ severity: :medium,
13
+ description: "Potential Rails exception triggering attempt"
14
+ },
15
+ ip_spoofing: {
16
+ patterns: [
17
+ %r{X-Forwarded-For.*X-Forwarded-For}i, # Multiple X-Forwarded-For headers in path (suspicious)
18
+ %r{Client-IP.*X-Forwarded-For}i, # Conflicting IP headers in path
19
+ ],
20
+ severity: :high,
21
+ description: "Potential IP spoofing attempt"
22
+ },
23
+ record_scanning: {
24
+ patterns: [
25
+ %r{/(?:user|admin|account|profile|order|payment|invoice|document|file|download)/\d{6,}}i, # Large IDs that likely don't exist
26
+ %r{/(?:user|admin|account|profile)/(?:test|admin|root|administrator|superuser)}i, # Common test usernames
27
+ %r{/api/v\d+/(?:users?|accounts?|orders?|payments?)/(?:999999|123456|0|null|undefined)}i, # Obviously fake API IDs
28
+ ],
29
+ severity: :low,
30
+ description: "Potential record enumeration/scanning"
31
+ },
32
+ wordpress: {
33
+ patterns: [
34
+ %r{/wp-admin}i,
35
+ %r{/wp-login\.php}i,
36
+ %r{/wp-content/.*\.php}i, # PHP files in wp-content are suspicious
37
+ %r{/wp-includes}i,
38
+ %r{/xmlrpc\.php}i,
39
+ %r{/wp-config\.php}i,
40
+ %r{/wp-config\.bak}i,
41
+ %r{/wordpress}i
42
+ ],
43
+ severity: :high,
44
+ description: "WordPress vulnerability scan"
45
+ },
46
+ wordpress_static: {
47
+ patterns: [
48
+ %r{/wp-content/.*\.(?:css|js|jpe?g|png|gif|svg|webp|ico|woff2?|ttf|eot|map)$}i, # Static files in wp-content
49
+ %r{/wp-content/(?:uploads|themes|plugins)/[^.]*$}i, # Directory listing attempts
50
+ ],
51
+ severity: :low,
52
+ description: "WordPress static file probe"
53
+ },
54
+ php_admin: {
55
+ patterns: [
56
+ %r{/phpmyadmin}i,
57
+ %r{/pma}i,
58
+ %r{/admin\.php}i,
59
+ %r{/administrator}i,
60
+ %r{/admin/config\.php}i,
61
+ %r{/phpinfo\.php}i
62
+ ],
63
+ severity: :high,
64
+ description: "PHP admin panel scan"
65
+ },
66
+ config_files: {
67
+ patterns: [
68
+ %r{/\.env},
69
+ %r{/\.git},
70
+ %r{/config\.php}i,
71
+ %r{/configuration\.php}i,
72
+ %r{/settings\.php}i,
73
+ %r{/database\.yml},
74
+ %r{/credentials\.yml}i
75
+ ],
76
+ severity: :critical,
77
+ description: "Configuration file access attempt"
78
+ },
79
+ path_traversal: {
80
+ patterns: [
81
+ %r{/etc/passwd},
82
+ %r{/etc/shadow},
83
+ %r{/etc/hosts},
84
+ %r{\.\./},
85
+ %r{\.\.\\},
86
+ %r{%2e%2e/}i,
87
+ %r{%252e%252e/}i
88
+ ],
89
+ severity: :critical,
90
+ description: "Path traversal attempt"
91
+ },
92
+ framework_debug: {
93
+ patterns: [
94
+ %r{/rails/info/routes},
95
+ %r{/__debug__},
96
+ %r{/debug},
97
+ %r{/telescope},
98
+ %r{/_profiler},
99
+ %r{/\.well-known}
100
+ ],
101
+ severity: :medium,
102
+ description: "Framework debug endpoint scan"
103
+ },
104
+ cms_scan: {
105
+ patterns: [
106
+ %r{/joomla}i,
107
+ %r{/drupal}i,
108
+ %r{/magento}i,
109
+ %r{/prestashop}i,
110
+ %r{/typo3}i
111
+ ],
112
+ severity: :medium,
113
+ description: "CMS detection scan"
114
+ },
115
+ common_exploits: {
116
+ patterns: [
117
+ %r{/shell\.php}i,
118
+ %r{/cmd\.php}i,
119
+ %r{/backdoor}i,
120
+ %r{/c99\.php}i,
121
+ %r{/r57\.php}i,
122
+ %r{/webshell}i
123
+ ],
124
+ severity: :critical,
125
+ description: "Common exploit file access"
126
+ }
127
+ }.freeze
128
+
129
+ # Configuration for RecordNotFound exclusion patterns
130
+ # These patterns will not trigger WAF violations
131
+ RECORD_NOT_FOUND_EXCLUSIONS = [
132
+ # Add default exclusions here if needed
133
+ # %r{/posts/.*}, # Example: exclude all posts paths
134
+ ].freeze
135
+
136
+ class << self
137
+ # Analyze a request for vulnerability scanning patterns
138
+ def analyze_request(request)
139
+ path = request.fullpath || request.path
140
+ return nil if path.blank?
141
+
142
+ detected_patterns = []
143
+
144
+ VULNERABILITY_PATTERNS.each do |category, config|
145
+ config[:patterns].each do |pattern|
146
+ if path.match?(pattern)
147
+ detected_patterns << {
148
+ category: category,
149
+ pattern: pattern.source,
150
+ severity: config[:severity],
151
+ description: config[:description],
152
+ matched_path: path
153
+ }
154
+ end
155
+ end
156
+ end
157
+
158
+ if detected_patterns.any?
159
+ {
160
+ threat_detected: true,
161
+ patterns: detected_patterns,
162
+ highest_severity: calculate_highest_severity(detected_patterns),
163
+ ip_address: request.ip,
164
+ user_agent: request.user_agent,
165
+ timestamp: Time.current
166
+ }
167
+ else
168
+ nil
169
+ end
170
+ end
171
+
172
+ # Analyze Rails exceptions as potential security threats
173
+ def analyze_exception(exception, request)
174
+ case exception
175
+ when ActionController::UnknownFormat
176
+ {
177
+ threat_detected: true,
178
+ patterns: [{
179
+ category: :unknown_format,
180
+ pattern: "ActionController::UnknownFormat",
181
+ severity: :medium,
182
+ description: "Unknown format requested - potential scanner",
183
+ matched_path: request.fullpath
184
+ }],
185
+ highest_severity: :medium,
186
+ ip_address: request.ip,
187
+ user_agent: request.user_agent,
188
+ timestamp: Time.current,
189
+ exception_class: exception.class.name,
190
+ exception_message: exception.message
191
+ }
192
+ when ActionDispatch::RemoteIp::IpSpoofAttackError
193
+ {
194
+ threat_detected: true,
195
+ patterns: [{
196
+ category: :ip_spoof,
197
+ pattern: "ActionDispatch::RemoteIp::IpSpoofAttackError",
198
+ severity: :critical,
199
+ description: "IP spoofing attack detected",
200
+ matched_path: request.fullpath
201
+ }],
202
+ highest_severity: :critical,
203
+ ip_address: request.ip,
204
+ user_agent: request.user_agent,
205
+ timestamp: Time.current,
206
+ exception_class: exception.class.name,
207
+ exception_message: exception.message
208
+ }
209
+ when ActiveRecord::RecordNotFound
210
+ # Check if this path should be excluded from WAF
211
+ if should_exclude_record_not_found?(request.fullpath)
212
+ return nil
213
+ end
214
+
215
+ {
216
+ threat_detected: true,
217
+ patterns: [{
218
+ category: :record_not_found,
219
+ pattern: "ActiveRecord::RecordNotFound",
220
+ severity: :low,
221
+ description: "Record not found - potential enumeration scan",
222
+ matched_path: request.fullpath
223
+ }],
224
+ highest_severity: :low,
225
+ ip_address: request.ip,
226
+ user_agent: request.user_agent,
227
+ timestamp: Time.current,
228
+ exception_class: exception.class.name,
229
+ exception_message: exception.message
230
+ }
231
+ when ActionDispatch::Http::MimeNegotiation::InvalidType
232
+ {
233
+ threat_detected: true,
234
+ patterns: [{
235
+ category: :invalid_mime_type,
236
+ pattern: "ActionDispatch::Http::MimeNegotiation::InvalidType",
237
+ severity: :medium,
238
+ description: "Invalid MIME type requested - potential scanner",
239
+ matched_path: request.fullpath
240
+ }],
241
+ highest_severity: :medium,
242
+ ip_address: request.ip,
243
+ user_agent: request.user_agent,
244
+ timestamp: Time.current,
245
+ exception_class: exception.class.name,
246
+ exception_message: exception.message
247
+ }
248
+ else
249
+ nil
250
+ end
251
+ end
252
+
253
+ # Check if a RecordNotFound exception should be excluded from WAF
254
+ def should_exclude_record_not_found?(path)
255
+ return false if path.blank?
256
+
257
+ # Check configured exclusions
258
+ exclusions = waf_config[:record_not_found_exclusions] || []
259
+ all_exclusions = RECORD_NOT_FOUND_EXCLUSIONS + exclusions
260
+
261
+ all_exclusions.any? { |pattern| path.match?(pattern) }
262
+ end
263
+
264
+ # Check if request should be blocked based on violation history
265
+ def should_block?(ip_address)
266
+ config = waf_config
267
+ return false unless config[:enabled]
268
+ return false unless config[:auto_block]
269
+
270
+ # Get current risk score (with decay applied)
271
+ current_score = get_current_score(ip_address)
272
+ threshold = config[:score_threshold] || 150
273
+
274
+ current_score >= threshold
275
+ end
276
+
277
+ # Record a WAF violation
278
+ def record_violation(ip_address, analysis_result, whitelisted: false)
279
+ config = waf_config
280
+ return unless config[:enabled]
281
+
282
+ Beskar::Logger.debug("[WAF] Recording violation for IP: #{ip_address}, whitelisted: #{whitelisted}", component: :WAF)
283
+
284
+ # Get current violations and add new one
285
+ cache_key = "beskar:waf_violations:#{ip_address}"
286
+ violations = Rails.cache.read(cache_key) || []
287
+
288
+ # Calculate risk score for this violation
289
+ risk_score = severity_to_risk_score(analysis_result[:highest_severity])
290
+
291
+ # Create new violation record
292
+ new_violation = {
293
+ timestamp: Time.current.to_i,
294
+ score: risk_score,
295
+ severity: analysis_result[:highest_severity],
296
+ category: analysis_result[:patterns].first[:category],
297
+ description: analysis_result[:patterns].first[:description],
298
+ path: analysis_result[:patterns].first[:matched_path]
299
+ }
300
+
301
+ violations << new_violation
302
+
303
+ # Prune old violations (outside violation window and max tracked)
304
+ violations = prune_violations(violations, config)
305
+
306
+ # Calculate current score with decay
307
+ current_score = calculate_current_score(violations, config)
308
+
309
+ Beskar::Logger.debug("[WAF] Violation recorded for #{ip_address}: score=#{risk_score}, current_total=#{current_score.round(2)}, violations_count=#{violations.size}", component: :WAF)
310
+
311
+ # Store violations with TTL from config
312
+ ttl = config[:violation_window] || 6.hours
313
+ Rails.cache.write(cache_key, violations, expires_in: ttl)
314
+
315
+ # Log the violation
316
+ log_violation(ip_address, analysis_result, current_score, violations.size)
317
+
318
+ # Create security event if configured
319
+ if config[:create_security_events]
320
+ create_security_event(ip_address, analysis_result, current_score)
321
+ end
322
+
323
+ # Check if we should auto-block (skip if whitelisted)
324
+ threshold = config[:score_threshold] || 150
325
+
326
+ Beskar::Logger.debug("[WAF] Auto-block check: whitelisted=#{whitelisted}, auto_block=#{config[:auto_block]}, score=#{current_score.round(2)}, threshold=#{threshold}", component: :WAF)
327
+
328
+ if !whitelisted && config[:auto_block] && current_score >= threshold
329
+ Beskar::Logger.info("[WAF] Score threshold reached for #{ip_address}, creating ban record", component: :WAF)
330
+ # Always create the ban record (even in monitor-only mode)
331
+ auto_block_ip(ip_address, analysis_result, current_score)
332
+
333
+ # But also log monitor-only message if in monitor mode
334
+ if Beskar.configuration.monitor_only?
335
+ log_monitor_only_action(ip_address, analysis_result, current_score, threshold)
336
+ end
337
+ else
338
+ Beskar::Logger.debug("[WAF] Not auto-blocking: conditions not met", component: :WAF)
339
+ end
340
+
341
+ current_score
342
+ end
343
+
344
+ # Get current risk score for an IP (with decay applied)
345
+ def get_current_score(ip_address)
346
+ violations = get_violations(ip_address)
347
+ calculate_current_score(violations, waf_config)
348
+ end
349
+
350
+ # Get violations for an IP
351
+ def get_violations(ip_address)
352
+ cache_key = "beskar:waf_violations:#{ip_address}"
353
+ Rails.cache.read(cache_key) || []
354
+ end
355
+
356
+ # Get violation count for an IP (number of violations tracked)
357
+ def get_violation_count(ip_address)
358
+ get_violations(ip_address).size
359
+ end
360
+
361
+ # Reset violations for an IP
362
+ def reset_violations(ip_address)
363
+ cache_key = "beskar:waf_violations:#{ip_address}"
364
+ Rails.cache.delete(cache_key)
365
+ end
366
+
367
+ private
368
+
369
+ # Calculate current cumulative score with decay applied
370
+ def calculate_current_score(violations, config)
371
+ return 0.0 if violations.empty?
372
+ return violations.sum { |v| v[:score] } unless config[:decay_enabled]
373
+
374
+ now = Time.current.to_i
375
+ decay_rates = config[:decay_rates] || {}
376
+
377
+ violations.sum do |v|
378
+ age_seconds = now - v[:timestamp]
379
+ age_minutes = age_seconds / 60.0
380
+
381
+ # Get half-life for this severity (in minutes)
382
+ half_life = decay_rates[v[:severity]] || 60
383
+
384
+ # Exponential decay: score * (1/2)^(age/half_life)
385
+ # Equivalent to: score * e^(-ln(2) * age / half_life)
386
+ decay_factor = Math.exp(-Math.log(2) * age_minutes / half_life)
387
+
388
+ v[:score] * decay_factor
389
+ end
390
+ end
391
+
392
+ # Prune violations that are outside the window or exceed max tracked
393
+ def prune_violations(violations, config)
394
+ now = Time.current.to_i
395
+ window_seconds = (config[:violation_window] || 6.hours).to_i
396
+ max_tracked = config[:max_violations_tracked] || 50
397
+
398
+ # Remove violations outside the time window
399
+ recent = violations.select do |v|
400
+ (now - v[:timestamp]) <= window_seconds
401
+ end
402
+
403
+ # Keep only the most recent violations if we exceed max_tracked
404
+ if recent.size > max_tracked
405
+ recent.sort_by { |v| -v[:timestamp] }.first(max_tracked)
406
+ else
407
+ recent
408
+ end
409
+ end
410
+
411
+ # Get WAF configuration
412
+ def waf_config
413
+ Beskar.configuration.waf || {}
414
+ end
415
+
416
+ # Calculate highest severity from detected patterns
417
+ def calculate_highest_severity(patterns)
418
+ severities = patterns.map { |p| p[:severity] }
419
+ return :critical if severities.include?(:critical)
420
+ return :high if severities.include?(:high)
421
+ return :medium if severities.include?(:medium)
422
+ :low
423
+ end
424
+
425
+ # Log WAF violation
426
+ def log_violation(ip_address, analysis_result, current_score, violation_count)
427
+ severity_emoji = {
428
+ critical: "🚨",
429
+ high: "⚠️",
430
+ medium: "⚡",
431
+ low: "ℹ️"
432
+ }
433
+
434
+ emoji = severity_emoji[analysis_result[:highest_severity]] || "🔍"
435
+ config = waf_config
436
+ monitor_mode_notice = Beskar.configuration.monitor_only? ? " [MONITOR-ONLY MODE]" : ""
437
+
438
+ Beskar::Logger.warn("#{emoji} Vulnerability scan detected#{monitor_mode_notice} " \
439
+ "(score: #{current_score.round(2)}, violations: #{violation_count}) - " \
440
+ "IP: #{ip_address}, " \
441
+ "Severity: #{analysis_result[:highest_severity]}, " \
442
+ "Patterns: #{analysis_result[:patterns].map { |p| p[:description] }.join(', ')}, " \
443
+ "Path: #{analysis_result[:patterns].first[:matched_path]}", component: :WAF)
444
+ end
445
+
446
+ # Log what would happen in monitor-only mode (but don't actually block)
447
+ def log_monitor_only_action(ip_address, analysis_result, current_score, threshold)
448
+ config = waf_config
449
+ duration = calculate_block_duration(current_score, config)
450
+
451
+ Beskar::Logger.warn("🔍 MONITOR-ONLY: IP #{ip_address} WOULD BE BLOCKED " \
452
+ "(score threshold reached: #{current_score.round(2)}/#{threshold}) - " \
453
+ "Duration would be: #{duration ? "#{duration / 3600.0} hours" : 'PERMANENT'}, " \
454
+ "Severity: #{analysis_result[:highest_severity]}, " \
455
+ "Patterns: #{analysis_result[:patterns].map { |p| p[:description] }.join(', ')}. " \
456
+ "To enable blocking, set config.monitor_only = false", component: :WAF)
457
+ end
458
+
459
+ # Create security event for WAF violation
460
+ def create_security_event(ip_address, analysis_result, current_score)
461
+ config = waf_config
462
+ violation_count = get_violation_count(ip_address)
463
+ threshold = config[:score_threshold] || 150
464
+ would_be_blocked = current_score >= threshold
465
+
466
+ Beskar::SecurityEvent.create!(
467
+ event_type: 'waf_violation',
468
+ ip_address: ip_address,
469
+ user_agent: analysis_result[:user_agent],
470
+ risk_score: severity_to_risk_score(analysis_result[:highest_severity]),
471
+ metadata: {
472
+ waf_analysis: analysis_result,
473
+ patterns_matched: analysis_result[:patterns].map { |p| p[:description] },
474
+ severity: analysis_result[:highest_severity],
475
+ monitor_only_mode: Beskar.configuration.monitor_only?,
476
+ would_be_blocked: would_be_blocked,
477
+ violation_count: violation_count,
478
+ current_score: current_score.round(2),
479
+ score_threshold: threshold
480
+ }
481
+ )
482
+ rescue => e
483
+ Beskar::Logger.error("Failed to create security event: #{e.message}", component: :WAF)
484
+ end
485
+
486
+ # Auto-block an IP after threshold violations
487
+ def auto_block_ip(ip_address, analysis_result, current_score)
488
+ config = waf_config
489
+ duration = calculate_block_duration(current_score, config)
490
+ violation_count = get_violation_count(ip_address)
491
+
492
+ Beskar::Logger.debug("[WAF] Attempting to ban IP #{ip_address} with duration: #{duration.inspect}", component: :WAF)
493
+
494
+ begin
495
+ banned_ip = Beskar::BannedIp.ban!(
496
+ ip_address,
497
+ reason: 'waf_violation',
498
+ duration: duration,
499
+ permanent: duration.nil?,
500
+ details: "WAF score: #{current_score.round(2)} (#{violation_count} violations) - #{analysis_result[:patterns].map { |p| p[:description] }.join(', ')}",
501
+ metadata: {
502
+ violation_count: violation_count,
503
+ risk_score: current_score.round(2),
504
+ patterns: analysis_result[:patterns],
505
+ highest_severity: analysis_result[:highest_severity],
506
+ blocked_at: Time.current
507
+ }
508
+ )
509
+
510
+ Beskar::Logger.warn("🔒 Auto-blocked IP #{ip_address} " \
511
+ "with score #{current_score.round(2)} (#{violation_count} violations) " \
512
+ "(duration: #{duration ? "#{duration / 3600} hours" : 'permanent'}), " \
513
+ "Ban ID: #{banned_ip.id}", component: :WAF)
514
+ rescue => e
515
+ Beskar::Logger.error("[WAF] Failed to create ban for #{ip_address}: #{e.class} - #{e.message}", component: :WAF)
516
+ raise
517
+ end
518
+ end
519
+
520
+ # Calculate block duration based on cumulative score
521
+ def calculate_block_duration(current_score, config)
522
+ permanent_threshold = config[:permanent_block_after]
523
+ return nil if permanent_threshold && current_score >= permanent_threshold
524
+
525
+ # Default escalating durations: 1h, 6h, 24h, 7d
526
+ base_durations = config[:block_durations] || [1.hour, 6.hours, 24.hours, 7.days]
527
+ score_threshold = config[:score_threshold] || 150
528
+
529
+ # Calculate how many times over the threshold we are
530
+ # 150-300 = 1x = 1 hour
531
+ # 300-450 = 2x = 6 hours
532
+ # 450-600 = 3x = 24 hours
533
+ # 600+ = 4x = 7 days
534
+ multiplier = ((current_score / score_threshold).floor - 1).clamp(0, base_durations.length - 1)
535
+ base_durations[multiplier]
536
+ end
537
+
538
+ # Convert severity level to risk score
539
+ def severity_to_risk_score(severity)
540
+ case severity
541
+ when :critical then 95
542
+ when :high then 80
543
+ when :medium then 60
544
+ when :low then 30
545
+ else 50
546
+ end
547
+ end
548
+ end
549
+ end
550
+ end
551
+ end
@@ -1,3 +1,3 @@
1
1
  module Beskar
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/beskar.rb CHANGED
@@ -1,6 +1,37 @@
1
1
  require "beskar/version"
2
+ require "beskar/configuration"
3
+ require "beskar/logger"
4
+ require "beskar/middleware"
5
+ require "beskar/middleware/request_analyzer"
6
+ require "beskar/models/security_trackable_generic"
7
+ require "beskar/models/security_trackable_devise"
8
+ require "beskar/models/security_trackable_authenticable"
9
+ require "beskar/models/security_trackable"
10
+ require "beskar/services/rate_limiter"
11
+ require "beskar/services/device_detector"
12
+ require "beskar/services/geolocation_service"
13
+ require "beskar/services/account_locker"
14
+ require "beskar/services/ip_whitelist"
15
+ require "beskar/services/waf"
2
16
  require "beskar/engine"
3
17
 
4
18
  module Beskar
5
- # Your code goes here...
19
+ class << self
20
+ attr_accessor :configuration
21
+ end
22
+
23
+ def self.configure
24
+ self.configuration ||= Configuration.new
25
+ yield(configuration)
26
+ end
27
+
28
+ # Convenience method to access the rate limiter
29
+ def self.rate_limiter
30
+ Services::RateLimiter
31
+ end
32
+
33
+ # Check if a request should be rate limited
34
+ def self.rate_limited?(request, user = nil)
35
+ Services::RateLimiter.is_rate_limited?(request, user)
36
+ end
6
37
  end