beskar 0.0.2 → 0.2.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +274 -0
  3. data/README.md +412 -204
  4. data/app/channels/concerns/beskar/channels/session_security.rb +46 -0
  5. data/app/controllers/beskar/administrative_actions_controller.rb +16 -0
  6. data/app/controllers/beskar/application_controller.rb +214 -0
  7. data/app/controllers/beskar/banned_ips_controller.rb +255 -0
  8. data/app/controllers/beskar/dashboard_controller.rb +62 -0
  9. data/app/controllers/beskar/security_events_controller.rb +164 -0
  10. data/app/controllers/concerns/beskar/controllers/audit_export.rb +54 -0
  11. data/app/controllers/concerns/beskar/controllers/security_tracking.rb +76 -48
  12. data/app/controllers/concerns/beskar/controllers/session_security.rb +29 -0
  13. data/app/jobs/beskar/notification_job.rb +33 -0
  14. data/app/mailers/beskar/security_mailer.rb +59 -0
  15. data/app/models/beskar/administrative_action.rb +41 -0
  16. data/app/models/beskar/banned_ip.rb +105 -105
  17. data/app/models/beskar/security_event.rb +51 -4
  18. data/app/models/beskar/security_state.rb +58 -0
  19. data/app/services/beskar/banned_ip_manager.rb +88 -0
  20. data/app/views/beskar/administrative_actions/index.html.erb +33 -0
  21. data/app/views/beskar/administrative_actions/show.html.erb +21 -0
  22. data/app/views/beskar/banned_ips/edit.html.erb +195 -0
  23. data/app/views/beskar/banned_ips/index.html.erb +319 -0
  24. data/app/views/beskar/banned_ips/new.html.erb +190 -0
  25. data/app/views/beskar/banned_ips/review.html.erb +24 -0
  26. data/app/views/beskar/banned_ips/show.html.erb +304 -0
  27. data/app/views/beskar/dashboard/index.html.erb +280 -0
  28. data/app/views/beskar/security_events/index.html.erb +302 -0
  29. data/app/views/beskar/security_events/show.html.erb +293 -0
  30. data/app/views/beskar/shared/_export_form.html.erb +10 -0
  31. data/app/views/layouts/beskar/_behavior.html.erb +121 -0
  32. data/app/views/layouts/beskar/application.html.erb +581 -6
  33. data/config/routes.rb +30 -0
  34. data/db/migrate/20251016000001_create_beskar_security_events.rb +3 -3
  35. data/db/migrate/20260910000001_create_beskar_security_states.rb +14 -0
  36. data/db/migrate/20260911000001_create_beskar_administrative_actions.rb +22 -0
  37. data/db/migrate/20260911000002_expand_administrative_action_targets.rb +6 -0
  38. data/docs/README.md +73 -0
  39. data/docs/archive/project-documentation.md +659 -0
  40. data/docs/audits/project-review.md +437 -0
  41. data/docs/audits/repair-status.md +216 -0
  42. data/docs/guides/audit-and-waf.md +175 -0
  43. data/docs/guides/audit-lifecycle.md +172 -0
  44. data/docs/guides/authentication.md +213 -0
  45. data/docs/guides/configuration.md +182 -0
  46. data/docs/guides/dashboard-and-search.md +251 -0
  47. data/docs/guides/notifications-and-recovery.md +157 -0
  48. data/docs/guides/risk-scoring.md +116 -0
  49. data/docs/operations/monitor-only-mode.md +85 -0
  50. data/docs/operations/security-hardening.md +167 -0
  51. data/docs/operations/state-storage.md +144 -0
  52. data/docs/research/rust-performance-assessment.md +69 -0
  53. data/lib/beskar/configuration.rb +105 -20
  54. data/lib/beskar/configuration_validator.rb +188 -0
  55. data/lib/beskar/devise_authentication.rb +24 -0
  56. data/lib/beskar/engine.rb +21 -88
  57. data/lib/beskar/logger.rb +288 -0
  58. data/lib/beskar/middleware/request_analyzer.rb +133 -99
  59. data/lib/beskar/models/security_trackable_authenticable.rb +76 -97
  60. data/lib/beskar/models/security_trackable_devise.rb +34 -25
  61. data/lib/beskar/models/security_trackable_generic.rb +171 -214
  62. data/lib/beskar/risk_level.rb +22 -0
  63. data/lib/beskar/services/account_locker.rb +90 -81
  64. data/lib/beskar/services/administrative_audit.rb +36 -0
  65. data/lib/beskar/services/administrative_bans.rb +104 -0
  66. data/lib/beskar/services/audit_data.rb +72 -0
  67. data/lib/beskar/services/authentication.rb +31 -0
  68. data/lib/beskar/services/authentication_attempt.rb +141 -0
  69. data/lib/beskar/services/ban_expiry.rb +28 -0
  70. data/lib/beskar/services/device_detector.rb +32 -41
  71. data/lib/beskar/services/event_search.rb +58 -0
  72. data/lib/beskar/services/geolocation_service.rb +83 -114
  73. data/lib/beskar/services/ip_whitelist.rb +31 -40
  74. data/lib/beskar/services/location_assessment.rb +109 -0
  75. data/lib/beskar/services/native_account_lock.rb +82 -0
  76. data/lib/beskar/services/notifications.rb +46 -0
  77. data/lib/beskar/services/rate_limiter.rb +99 -125
  78. data/lib/beskar/services/request_context.rb +64 -0
  79. data/lib/beskar/services/risk_assessment.rb +58 -0
  80. data/lib/beskar/services/session_revocation.rb +62 -0
  81. data/lib/beskar/services/waf.rb +311 -198
  82. data/lib/beskar/services/waf_request.rb +60 -0
  83. data/lib/beskar/version.rb +1 -1
  84. data/lib/beskar/warden_authentication.rb +53 -0
  85. data/lib/beskar.rb +54 -4
  86. data/lib/generators/beskar/install/install_generator.rb +158 -0
  87. data/lib/generators/beskar/install/templates/initializer.rb.tt +261 -0
  88. data/lib/tasks/beskar_tasks.rake +25 -20
  89. metadata +93 -12
  90. data/lib/beskar/templates/beskar_initializer.rb +0 -107
data/README.md CHANGED
@@ -1,23 +1,79 @@
1
1
  # Beskar
2
2
 
3
- **Beskar** is a comprehensive, Rails-native security engine designed to provide multi-layered, proactive protection for modern web applications. It defends against common threats, bot activity, and account takeovers without requiring external dependencies, integrating seamlessly into your application as a natural extension of the framework.
3
+ Implementation repairs are tracked in [Repair status](docs/audits/repair-status.md). Read [Security hardening and rollout](docs/operations/security-hardening.md) before upgrading: session revocation, explicit admin permissions, export auditing, sealed configuration, and safer availability defaults change integration requirements. The original findings remain in [Original project review](docs/audits/project-review.md).
4
+
5
+ Account deletion retains security events unchanged. Dashboard ban changes now require
6
+ a trusted actor and reason, recorded in a new administrative-history table. See
7
+ [Audit lifecycle](docs/guides/audit-lifecycle.md) for configuration and upgrade requirements.
8
+
9
+ **Beskar** is a Rails-native security engine for authentication admission limits, risk-based account locking, IP bans, scanner-path detection, and an administrative audit dashboard. It requires a shared writer database for coordinated state and explicit host authentication integration. Its heuristic signals do not replace application authorization, input validation, or recovery delivery.
10
+
11
+ ## Documentation
12
+
13
+ The [documentation index](docs/README.md) organizes current guides, operational
14
+ contracts, audit findings, research, and archived reference material. Start with
15
+ [configuration](docs/guides/configuration.md) and
16
+ [authentication](docs/guides/authentication.md) for integration, or the
17
+ [rollout checklist](docs/operations/security-hardening.md#rollout) for an upgrade.
18
+
19
+ ## Screenshots
20
+
21
+ ![Dashboard](https://humadroid-static-assets.s3.amazonaws.com/beskar/beskar-dashboard.png)
22
+
23
+ | Security Events | Banned IPs |
24
+ |:---------------:|:----------:|
25
+ | ![Security Events](https://humadroid-static-assets.s3.amazonaws.com/beskar/beskar-security-event.png) | ![Banned IPs](https://humadroid-static-assets.s3.amazonaws.com/beskar/beskar-banned-ips.png) |
26
+
27
+ ## Table of Contents
28
+
29
+ - [Documentation](#documentation)
30
+ - [Features](#features)
31
+ - [Installation](#installation)
32
+ - [Quick Start](#quick-start)
33
+ - [Dashboard Authentication (REQUIRED)](#dashboard-authentication-required)
34
+ - [Add to Your User Model](#add-to-your-user-model)
35
+ - [Configuration](#configuration)
36
+ - [Usage](#usage)
37
+ - [Risk-Based Account Locking](#risk-based-account-locking-with-devise-lockable)
38
+ - [Rate Limiting](#rate-limiting)
39
+ - [IP Whitelisting](#ip-whitelisting)
40
+ - [Web Application Firewall (WAF)](#web-application-firewall-waf)
41
+ - [IP Blocking and Banning](#ip-blocking-and-banning)
42
+ - [Security Events](#security-events)
43
+ - [Middleware Integration](#middleware-integration)
44
+ - [WAF Pattern Reference](#waf-pattern-reference)
45
+ - [Security Best Practices](#security-best-practices)
46
+ - [Troubleshooting](#troubleshooting)
47
+ - [Development](#development)
48
+ - [Contributing](#contributing)
49
+ - [License](#license)
4
50
 
5
51
  ## Features
6
52
 
7
- - **Devise Integration:** Seamless integration with Devise authentication for automatic login tracking and security analysis.
8
- - **Risk-Based Account Locking:** Automatically locks accounts when authentication risk scores exceed configurable thresholds, preventing compromised account access.
9
- - **Smart Rate Limiting:** Distributed rate limiting using Rails.cache with IP-based and account-based throttling with exponential backoff.
10
- - **Brute Force Detection:** Advanced pattern recognition to detect single account attacks vs credential stuffing attempts, with automatic IP banning.
11
- - **IP Whitelisting:** Allow trusted IPs (office networks, partners, security scanners) to bypass blocking while maintaining full audit logs. Supports individual IPs and CIDR notation.
12
- - **Persistent IP Blocking:** Hybrid cache + database blocking system that survives application restarts. Auto-bans IPs after authentication abuse or excessive rate limiting violations.
13
- - **Web Application Firewall (WAF):** Real-time detection and blocking of vulnerability scanning attempts across 7 attack categories (WordPress, PHP admin panels, config files, path traversal, framework debug, CMS detection, common exploits). Includes escalating ban durations and monitor-only mode.
14
- - **Security Event Tracking:** Comprehensive logging of authentication events with risk scoring and metadata extraction.
53
+ For current reporting bands, search semantics, native-user presentation, UTC ban
54
+ forms, script CSP/native-navigation behavior, and dashboard/export routes, see
55
+ [Dashboard and search](docs/guides/dashboard-and-search.md).
56
+ No versioned administration API is implemented.
57
+
58
+ See [Configuration](docs/guides/configuration.md) for startup validation, supported
59
+ strategies/providers, and opt-in host background analysis. Opt-in lock/reset email
60
+ delivery and its host recovery requirements are documented in
61
+ [Notifications and recovery](docs/guides/notifications-and-recovery.md).
62
+
63
+ - **Devise Integration:** Admission, risk assessment, optional audit tracking and durable session/remember-cookie revocation on protected models.
64
+ - **Risk-Based Account Locking:** Opt-in rules lock supported accounts at configured thresholds. Scores are heuristics, not proof of account takeover.
65
+ - **Rate Limiting:** Database-coordinated IP/account and opt-in global admission limits with enforced backoff deadlines. Supports any Rails.cache backend. See [authentication integration](docs/guides/authentication.md) for required host guards.
66
+ - **Authentication Pattern Helpers:** Bounded account/IP failure-history helpers; automatic background analysis requires an explicitly configured host job.
67
+ - **IP Whitelisting:** Trusted IPs/CIDRs bypass automatic blocking while configured observations remain enabled; optional audit delivery is not guaranteed.
68
+ - **Persistent IP Blocking:** Database-authoritative blocking across application restarts. WAF rules and opt-in request-wide quota-abuse escalation can create automatic bans.
69
+ - **Web Application Firewall (WAF):** Bounded, decoded-path scanner signatures and narrowly scoped Rails exception signals, with cumulative scores, escalating bans, monitor-only mode, and method/path/category exclusions. This is not a general SQL injection or XSS filter.
70
+ - **Security Event Tracking:** Filtered authentication/WAF evidence and required administrative history. Events survive account deletion; ordinary instance rewrites/deletes are rejected.
15
71
  - **IP Geolocation:** MaxMind GeoLite2-City database integration for country/city location, coordinates, timezone, and enhanced risk scoring (configurable, database not included due to licensing).
16
- - **Geographic Anomaly Detection:** Haversine-based impossible travel detection and location-based risk assessment.
17
- - **Advanced Bot Detection:** Multi-layered defense using JavaScript challenges and invisible honeypots to filter out malicious bots while allowing legitimate ones.
72
+ - **Geographic Anomaly Detection:** Timestamped, admitted-login history and Haversine-based travel heuristics with explicit evidence. Mock locations do not trigger geographic risk; see [risk scoring](docs/guides/risk-scoring.md).
73
+ - **User-Agent Heuristics:** Browser and bot-like User-Agent signals contribute to authentication risk. Headers are spoofable; JavaScript challenges and honeypots are not implemented.
18
74
  - **Modular Architecture:** Devise-specific code is isolated in separate services for maintainability and extensibility.
19
- - **Rails-Native Architecture:** Built as a mountable `Rails::Engine`, it leverages `ActiveJob` and `Rails.cache` for high performance and low overhead.
20
- - **Real-Time Dashboard (Coming Soon):** A mountable dashboard to visualize security events and monitor threats as they happen.
75
+ - **Rails-Native Architecture:** Built as a mountable `Rails::Engine`, with Active Record-backed security state and optional caching for enrichment.
76
+ - **Security Dashboard:** A mountable web interface for monitoring security events, managing IP bans, and viewing statistics. Features configurable authentication, real-time filtering, and export capabilities. See [Dashboard Authentication](#dashboard-authentication-required) section below.
21
77
 
22
78
  ## Installation
23
79
 
@@ -52,22 +108,118 @@ bin/rails db:migrate
52
108
 
53
109
  ### Quick Start
54
110
 
111
+ **1. Configure Dashboard Authentication (Required)**
112
+
113
+ Before using Beskar, you must configure authentication for the dashboard. See the [Dashboard Authentication](#dashboard-authentication-required) section below for details and examples.
114
+
115
+ **2. Enable WAF Monitoring**
116
+
55
117
  By default, Beskar enables the **Web Application Firewall (WAF) in monitor-only mode**. This means:
56
118
  - ✅ Vulnerability scans are detected and logged
57
- - ✅ Security events are created for analysis
119
+ - ✅ Security events are created for analysis
58
120
  - ⚠️ No requests are blocked yet (safe to enable in production)
59
121
 
60
122
  After monitoring for 24-48 hours, review the logs and disable monitor-only mode to enable active blocking:
61
123
 
62
124
  ```ruby
63
125
  # config/initializers/beskar.rb
64
- config.waf = {
65
- enabled: true,
66
- monitor_only: false, # Change this to enable blocking
126
+ Beskar.configure do |config|
127
+ config.monitor_only = true # Change this to false to enable blocking
128
+ config.waf[:enabled] = true
67
129
  # ... rest of configuration
68
- }
130
+ end
131
+ ```
132
+
133
+ ### Dashboard Authentication (REQUIRED)
134
+
135
+ **⚠️ IMPORTANT: Dashboard authentication must be configured for all environments.**
136
+
137
+ The Beskar dashboard requires authentication to prevent unauthorized access. You must configure how users authenticate to access the dashboard by setting up the `authenticate_admin` callback:
138
+
139
+ ```ruby
140
+ # config/initializers/beskar.rb
141
+ Beskar.configure do |config|
142
+ # REQUIRED: Configure dashboard authentication
143
+ # The block is executed in the controller context and receives the request object.
144
+ # You have access to all controller methods (cookies, session, etc.) and helpers.
145
+ config.authenticate_admin = ->(request) do
146
+ # Return truthy to allow access, falsey to deny
147
+
148
+ # Example 1: Devise with admin role (recommended for production)
149
+ user = request.env['warden']&.authenticate(scope: :user)
150
+ user&.admin?
151
+ end
152
+ # REQUIRED: grant individual capabilities using your host permission store.
153
+ config.authorize_admin = ->(request, permission) do
154
+ user = request.env['warden']&.user(scope: :user)
155
+ user&.admin? && user.beskar_permissions.include?(permission.to_s)
156
+ end
157
+ # Adapt beskar_permissions to your host: read, manage_bans, export, read_audit.
158
+ # REQUIRED for dashboard writes and exports: identify the authenticated operator.
159
+ config.audit_actor = ->(request) do
160
+ user = request.env['warden']&.user(scope: :user)
161
+ "User:#{user.id}" if user&.admin?
162
+ end
163
+ end
69
164
  ```
70
165
 
166
+ **Why this is required:** Previous versions allowed unauthenticated access in development/test environments, which could lead to production security issues. Now, authentication must be explicitly configured for all environments to prevent accidental exposure.
167
+
168
+ `audit_actor` is separate from authorization. Without it, authenticated reads work
169
+ but ban mutations return 503. Every mutation also requires a nonblank `audit_reason`
170
+ (at most 1,000 characters), supplied by the dashboard forms. Apply the history
171
+ migration and adapt other authentication strategies to return a trusted opaque
172
+ operator ID; never use request parameters or credentials as that identity. See
173
+ [administrative history](docs/guides/audit-lifecycle.md).
174
+
175
+ **Other Authentication Strategies:**
176
+
177
+ ```ruby
178
+ # Token-based authentication
179
+ config.authenticate_admin = ->(request) do
180
+ token = ENV['BESKAR_ADMIN_TOKEN']
181
+ token.present? && Beskar::Services::RequestContext.secure_match?(request.headers['Authorization'], "Bearer #{token}")
182
+ end
183
+
184
+ # HTTP Basic Auth (uses controller method)
185
+ config.authenticate_admin = ->(request) do
186
+ authenticate_or_request_with_http_basic do |username, password|
187
+ Beskar::Services::RequestContext.secure_match?(username, ENV['BESKAR_USERNAME']) &&
188
+ Beskar::Services::RequestContext.secure_match?(password, ENV['BESKAR_PASSWORD'])
189
+ end
190
+ end
191
+
192
+ # Cookie-based authentication (uses controller cookies)
193
+ config.authenticate_admin = ->(request) do
194
+ Beskar::Services::RequestContext.secure_match?(cookies.signed[:admin_token], ENV['BESKAR_ADMIN_TOKEN'])
195
+ end
196
+
197
+ # Development/Testing bypass (use with caution!)
198
+ config.authenticate_admin = ->(request) do
199
+ Rails.env.development? || Rails.env.test?
200
+ end
201
+ ```
202
+
203
+ **Accessing the Dashboard:**
204
+
205
+ After configuring authentication, mount the engine in your routes:
206
+
207
+ ```ruby
208
+ # config/routes.rb
209
+ Rails.application.routes.draw do
210
+ mount Beskar::Engine => "/beskar"
211
+ end
212
+ ```
213
+
214
+ Then visit `http://localhost:3000/beskar` to access the dashboard.
215
+
216
+ **Dashboard Features:**
217
+ - 📊 Security event monitoring with filtering and search
218
+ - 🚫 IP ban management (view, extend, unban)
219
+ - 📈 Statistics and risk distribution analysis
220
+ - 📥 Export capabilities (CSV/JSON)
221
+ - 🔒 CSRF protection and secure by default
222
+
71
223
  ### Add to Your User Model
72
224
 
73
225
  Include the `SecurityTrackable` concern in your Devise user model:
@@ -75,8 +227,8 @@ Include the `SecurityTrackable` concern in your Devise user model:
75
227
  ```ruby
76
228
  # app/models/user.rb
77
229
  class User < ApplicationRecord
78
- include Beskar::SecurityTrackable
79
-
230
+ include Beskar::Models::SecurityTrackable
231
+
80
232
  devise :database_authenticatable, :registerable,
81
233
  :recoverable, :rememberable, :validatable
82
234
  # ... other Devise modules
@@ -85,18 +237,28 @@ end
85
237
 
86
238
  ## Configuration
87
239
 
88
- You can configure Beskar in the initializer file created by the installer:
240
+ You can configure Beskar in the initializer file created by the installer.
241
+
242
+ > **Note:** Dashboard authentication setup is covered in the [Dashboard Authentication](#dashboard-authentication-required) section above.
89
243
 
90
244
  ```ruby
91
245
  # config/initializers/beskar.rb
92
246
  Beskar.configure do |config|
247
+ # === Dashboard Authentication (REQUIRED) ===
248
+ # See "Dashboard Authentication" section above for examples and details
249
+ config.authenticate_admin = ->(request) do
250
+ user = request.env['warden']&.authenticate(scope: :user)
251
+ user&.admin?
252
+ end
253
+
93
254
  # === Security Tracking ===
94
255
  # Controls what security events are tracked and analyzed
95
256
  config.security_tracking = {
96
257
  enabled: true, # Master switch - disables all tracking when false
97
258
  track_successful_logins: true, # Track successful authentication events
98
259
  track_failed_logins: true, # Track failed authentication attempts
99
- auto_analyze_patterns: true # Enable automatic pattern analysis for threats
260
+ auto_analyze_patterns: false, # Opt in only with a host-owned Active Job
261
+ analysis_job: nil # Example: "SecurityReviewJob"; see docs/guides/configuration.md
100
262
  }
101
263
 
102
264
  # === Rate Limiting ===
@@ -112,6 +274,7 @@ Beskar.configure do |config|
112
274
  exponential_backoff: true
113
275
  },
114
276
  global_attempts: {
277
+ enabled: false, # Opt-in: attackers can exhaust a shared login budget
115
278
  limit: 100, # System-wide limit
116
279
  period: 1.minute,
117
280
  exponential_backoff: false
@@ -119,36 +282,37 @@ Beskar.configure do |config|
119
282
  }
120
283
 
121
284
  # === IP Whitelisting ===
122
- # Allow trusted IPs to bypass all blocking (bans, rate limits, WAF)
123
- # while still logging all activity for audit purposes
124
- config.ip_whitelist = [
125
- "192.168.1.100", # Single IP address
126
- "10.0.0.0/24", # CIDR notation - entire subnet
127
- "172.16.0.0/16", # Larger CIDR range
128
- "2001:db8::/32" # IPv6 support
129
- ]
285
+ # See "IP Whitelisting" section below for detailed examples
286
+ config.ip_whitelist = [] # Add trusted IPs here (supports CIDR notation)
130
287
 
131
288
  # === Web Application Firewall (WAF) ===
132
- # Real-time detection and blocking of vulnerability scanning attempts
133
- config.waf = {
134
- enabled: true, # Master switch for WAF
135
- auto_block: true, # Automatically ban IPs after threshold
136
- block_threshold: 3, # Number of violations before blocking
137
- violation_window: 1.hour, # Time window for counting violations
138
- block_durations: [1.hour, 6.hours, 24.hours, 7.days], # Escalating ban durations
139
- permanent_block_after: 5, # Permanent ban after N violations
140
- create_security_events: true, # Log WAF violations to SecurityEvent table
141
- monitor_only: false # If true, log violations but never block
142
- }
289
+ # See "Web Application Firewall" section below for production examples
290
+ # Defaults shown here - use [:key] syntax to preserve other defaults
291
+ config.waf[:enabled] = true # Master switch for WAF
292
+ # config.waf[:auto_block] = true # Default: true
293
+ # config.waf[:score_threshold] = 150 # Default: 150 (cumulative risk score before blocking)
294
+ # config.waf[:violation_window] = 6.hours # Default: 6 hours (max time to track violations)
295
+ # config.waf[:block_durations] = [1.hour, 6.hours, 24.hours, 7.days] # Escalating bans
296
+ # config.waf[:permanent_block_after] = 500 # Permanent after cumulative score reaches 500
297
+ # config.waf[:create_security_events] = true # Log to SecurityEvent table
298
+ # config.waf[:record_not_found_exclusions] = [] # Regex patterns for false positives
299
+ # config.waf[:decay_enabled] = true # Enable exponential decay of violation scores
300
+ # config.waf[:decay_rates] = { # Decay rates by severity (half-life in minutes)
301
+ # critical: 360, # 6 hour half-life
302
+ # high: 120, # 2 hour half-life
303
+ # medium: 45, # 45 minute half-life
304
+ # low: 15 # 15 minute half-life
305
+ # }
306
+ # config.waf[:max_violations_tracked] = 50 # Maximum violations to track per IP
143
307
 
144
308
  # === Risk-Based Account Locking ===
145
309
  # Automatically lock accounts when authentication risk score exceeds threshold
146
310
  config.risk_based_locking = {
147
311
  enabled: false, # Master switch for risk-based locking
148
312
  risk_threshold: 75, # Lock account if risk score >= this value (0-100)
149
- lock_strategy: :devise_lockable, # Strategy: :devise_lockable, :custom, :none
150
- auto_unlock_time: 1.hour, # Time until automatic unlock (if supported)
151
- notify_user: true, # Send notification on lock (future feature)
313
+ lock_strategy: :devise_lockable, # Strategy: :devise_lockable, :rails_auth, :none
314
+ auto_unlock_time: 1.hour, # Native locks only; Devise owns unlock_in
315
+ notify_user: false, # Opt-in email; configure notifications first
152
316
  log_lock_events: true # Create security event for locks
153
317
  }
154
318
 
@@ -163,32 +327,14 @@ Beskar.configure do |config|
163
327
  }
164
328
  end
165
329
 
166
- # Security Tracking Configuration Details
167
- The security tracking system respects all configuration settings:
168
-
169
- - **`enabled: false`** - Completely disables all security event tracking
170
- - **`track_successful_logins: false`** - Stops tracking successful login events
171
- - **`track_failed_logins: false`** - Stops tracking failed login attempts
172
- - **`auto_analyze_patterns: false`** - Disables automatic threat pattern analysis
173
-
174
- When tracking is disabled via configuration, no `SecurityEvent` records are created and no background analysis jobs are queued.
175
330
  ```
176
331
 
177
332
  ## Usage
178
333
 
179
- ### Basic Setup
334
+ For current admission, account-lock, and recovery behavior—including the required
335
+ Rails-native controller/session-reader upgrade—see [Authentication](docs/guides/authentication.md).
180
336
 
181
- Once installed and configured, Beskar works automatically with Devise. Add the `SecurityTrackable` module to your User model:
182
-
183
- ```ruby
184
- class User < ApplicationRecord
185
- devise :database_authenticatable, :registerable,
186
- :recoverable, :rememberable, :validatable
187
-
188
- # Add Beskar security tracking
189
- include Beskar::Models::SecurityTrackable
190
- end
191
- ```
337
+ > **Note:** If you haven't already, see the [Add to Your User Model](#add-to-your-user-model) section in Quick Start for setting up `SecurityTrackable`.
192
338
 
193
339
  ### Risk-Based Account Locking (with Devise Lockable)
194
340
 
@@ -203,7 +349,7 @@ class User < ApplicationRecord
203
349
  devise :database_authenticatable, :registerable,
204
350
  :recoverable, :rememberable, :validatable,
205
351
  :lockable # Add this for risk-based locking
206
-
352
+
207
353
  include Beskar::Models::SecurityTrackable
208
354
  end
209
355
  ```
@@ -228,8 +374,8 @@ Beskar.configure do |config|
228
374
  enabled: true, # Enable the feature
229
375
  risk_threshold: 75, # Lock when risk >= 75
230
376
  lock_strategy: :devise_lockable, # Use Devise's lockable module
231
- auto_unlock_time: 1.hour, # Automatic unlock after 1 hour
232
- notify_user: true, # Log notification intent
377
+ auto_unlock_time: 1.hour, # Native locks only; configure Devise's unlock_in separately
378
+ notify_user: false, # Opt-in email; see docs/guides/notifications-and-recovery.md
233
379
  log_lock_events: true # Create security events
234
380
  }
235
381
  end
@@ -237,37 +383,24 @@ end
237
383
 
238
384
  **How it works:**
239
385
 
240
- - After each successful authentication, Beskar calculates a risk score (0-100) based on:
241
- - Geographic anomalies (impossible travel, country changes)
242
- - Device fingerprints (suspicious user agents, bot signatures)
243
- - Login patterns (velocity, time of day, recent failures)
244
- - IP reputation and geolocation risk
245
-
246
- - **Adaptive Learning:** The system learns from user behavior:
247
- - After 2+ successful logins from an IP, that location becomes "established"
248
- - If a user unlocks and logs in successfully, that pattern is trusted
249
- - Risk scores are reduced to 30% for established patterns (capped at 25)
250
- - This prevents repeated locks after users validate their login context
386
+ - Each authentication assessment records a score (0100), named factors, and evidence: timestamped geographic observations, unverified User-Agent claims, application-local mobile hours, and recent account failures.
387
+ - Repeated IP use and unlock events do not establish verified trust. The former automatic risk discount and geographic bypass have been removed.
251
388
 
252
389
  - If the risk score meets or exceeds the configured threshold, the account is automatically locked
253
- - The user session is terminated immediately to prevent access
254
- - A security event is logged with the lock reason and risk details (always logged for audit trail)
255
- - The account remains locked until manually unlocked or the auto-unlock time expires (if supported)
256
-
257
- **Example Adaptive Flow:**
258
- 1. User travels to new location → High risk (85) → Account locked
259
- 2. User unlocks account → Validates legitimacy
260
- 3. User logs in from same location → Pattern established → Risk reduced to 25 → Login succeeds ✅
261
- 4. Future logins from this location → Normal risk → No more locks
390
+ - Confirmed locks reject the current attempt and revoke prior Devise sessions/remember cookies. Unlock does not resurrect them. `immediate_signout` defaults true; legacy false no longer bypasses a lock. Unrelated accounts remain signed in.
391
+ - Optional audit events record lock details; missing audit rows do not change enforcement.
392
+ - Devise controls its own unlock policy; Rails-native locks use Beskar's `auto_unlock_time` and persistent session guards.
262
393
 
263
- See `ADAPTIVE_LEARNING.md` for detailed documentation.
394
+ See [Risk scoring](docs/guides/risk-scoring.md) for factor weights, bounded history, provider
395
+ behavior, and rollout limitations. Scores are heuristics, not calibrated
396
+ probabilities; validate the corrected inputs in monitor mode before enforcement.
264
397
 
265
398
  **Lock Reasons:**
266
399
 
267
400
  The system identifies specific reasons for locking:
268
401
  - `:impossible_travel` - Login from location requiring impossible travel speed
269
402
  - `:suspicious_device` - Bot signature or suspicious user agent detected
270
- - `:geographic_anomaly` - Country change or high-risk location
403
+ - `:geographic_anomaly` - Changed known country
271
404
  - `:high_risk_authentication` - General high-risk authentication pattern
272
405
 
273
406
  **Manual Lock/Unlock Operations:**
@@ -351,7 +484,7 @@ attack_type = rate_limiter.attack_pattern_type
351
484
  case attack_type
352
485
  when :brute_force_single_account
353
486
  # Single IP attacking one account
354
- when :distributed_single_account
487
+ when :distributed_single_account
355
488
  # Multiple IPs attacking one account
356
489
  when :single_ip_multiple_accounts
357
490
  # One IP attacking multiple accounts (credential stuffing)
@@ -388,62 +521,162 @@ if Beskar::Services::IpWhitelist.whitelisted?(request.ip)
388
521
  # IP is trusted - allow but log activity
389
522
  end
390
523
 
391
- # Clear whitelist cache after config changes
524
+ # Optional compatibility method; configuration changes are detected automatically.
392
525
  Beskar::Services::IpWhitelist.clear_cache!
393
526
  ```
394
527
 
395
528
  ### Web Application Firewall (WAF)
396
529
 
397
- Beskar's WAF detects and blocks vulnerability scanning attempts across 7 attack categories:
530
+ Beskar's WAF uses a **score-based blocking system with exponential decay** for scanner-path signatures and selected Rails exceptions. See [the matching and privacy contract](docs/guides/audit-and-waf.md) for canonicalization, exclusions, and limitations.
398
531
 
399
532
  **Attack Categories Detected:**
400
- 1. **WordPress Scans** (High Severity) - `/wp-admin`, `/wp-login.php`, `/xmlrpc.php`
401
- 2. **PHP Admin Panels** (High Severity) - `/phpmyadmin`, `/admin.php`, `/phpinfo.php`
402
- 3. **Config Files** (Critical Severity) - `/.env`, `/.git`, `/database.yml`
403
- 4. **Path Traversal** (Critical Severity) - `/../../../etc/passwd`, URL encoded variants
404
- 5. **Framework Debug** (Medium Severity) - `/rails/info/routes`, `/__debug__`, `/telescope`
405
- 6. **CMS Detection** (Medium Severity) - `/joomla`, `/drupal`, `/magento`
406
- 7. **Common Exploits** (Critical Severity) - `/shell.php`, `/c99.php`, `/webshell`
533
+ 1. **WordPress Scans** (High: 80 points) - `/wp-admin`, `/wp-login.php`, `/wp-content/*.php`, `/xmlrpc.php`
534
+ 2. **WordPress Static Files** (Low: 30 points) - `/wp-content/*.css`, `/wp-content/*.js`, `/wp-content/*.jpg` (broken links, not attacks)
535
+ 3. **PHP Admin Panels** (High: 80 points) - `/phpmyadmin`, `/admin.php`, `/phpinfo.php`
536
+ 4. **Config Files** (Critical: 95 points) - `/.env`, `/.git`, `/database.yml`
537
+ 5. **Path Traversal** (Critical: 95 points) - `/../../../etc/passwd`, URL encoded variants
538
+ 6. **Framework Debug** (Medium: 60 points) - `/rails/info/routes`, `/__debug__`, `/telescope`
539
+ 7. **CMS Detection** (Medium: 60 points) - `/joomla`, `/drupal`, `/magento`
540
+ 8. **Common Exploits** (Critical: 95 points) - `/shell.php`, `/c99.php`, `/webshell`
541
+ 9. **Rails Format Paths** (Medium: 60 points) - Selected resource/extension signatures such as `/users/1.exe`, plus exact executable `format` query values
542
+ 10. **Record Scanning Paths** (Low: 30 points) - Selected large-ID and scanner-name path signatures
543
+ 11. **Rails Exceptions** - `UnknownFormat` and `InvalidType` (60), `RecordNotFound` (30), and `IpSpoofAttackError` (95). Ordinary Rails exceptions require independent path/format evidence by default; IP-spoof exceptions require safe resolved attribution in middleware. Exceptions alone can be scored by explicitly opting into `exception_detection: :all`.
544
+
545
+ **How Score-Based Blocking Works:**
546
+
547
+ Instead of counting violations (1, 2, 3...), Beskar tracks a **cumulative risk score** that decays over time:
548
+
549
+ - Each middleware pass records at most one violation, using the highest matched path severity (Critical=95, High=80, Medium=60, Low=30); a downstream exception does not add a second charge
550
+ - Violations **decay exponentially** based on severity (critical threats persist longer)
551
+ - IP is blocked when cumulative score reaches threshold (default: 150 points)
552
+ - Lower-severity violations decay faster; ordinary 404s do not add points by default, but signature-matching legitimate paths still can
553
+
554
+ **Example Scenarios:**
555
+ ```ruby
556
+ # Scenario 1: Ordinary missing records, no matching scanner signature
557
+ # exception_detection: :suspicious (default)
558
+ 10 × RecordNotFound = no WAF points
559
+ # Opting into :all changes this: ten rapid failures can cross the ban threshold.
560
+
561
+ # Scenario 2: Attacker scanning config files
562
+ 2 × /.env access close together (95 points each) ≈ 190 points
563
+ → Exceeds threshold (150) → Ban when enforcement and auto-block are enabled
564
+ → One request alone is below the default threshold
565
+ → Critical severity has a 6-hour half-life within the configured retention window
566
+
567
+ # Scenario 3: Mixed attack pattern
568
+ 1 × WordPress scan (80) + 1 × Config access (95) = 175
569
+ → Exceeds threshold → Ban triggered
570
+ → Different decay rates for each violation type
571
+ ```
407
572
 
408
- **Configuration Examples:**
573
+ **Configuration Profiles:**
409
574
 
410
575
  ```ruby
411
- # Production - Aggressive protection
576
+ # 🔥 STRICT - High-security environment (financial, healthcare)
412
577
  Beskar.configure do |config|
413
- config.waf = {
414
- enabled: true,
415
- auto_block: true,
416
- block_threshold: 2, # Block after just 2 violations
417
- violation_window: 30.minutes,
418
- block_durations: [6.hours, 24.hours, 7.days, 30.days],
419
- permanent_block_after: 4,
420
- create_security_events: true
578
+ config.waf[:enabled] = true
579
+ config.waf[:auto_block] = true
580
+ config.waf[:score_threshold] = 100 # Lower threshold = faster blocking
581
+ config.waf[:violation_window] = 12.hours # Longer memory
582
+ config.waf[:permanent_block_after] = 300 # Permanent ban at 300 cumulative score
583
+ config.waf[:block_durations] = [6.hours, 24.hours, 7.days, 30.days]
584
+
585
+ # Slower decay = violations persist longer
586
+ config.waf[:decay_rates] = {
587
+ critical: 720, # 12 hour half-life (very persistent)
588
+ high: 360, # 6 hour half-life
589
+ medium: 120, # 2 hour half-life
590
+ low: 30 # 30 minute half-life
421
591
  }
592
+
593
+ # Exclude legitimate 404-prone paths
594
+ config.waf[:record_not_found_exclusions] = [
595
+ %r{/posts/.*}, %r{/articles/\d+}, %r{/public/.*}
596
+ ]
422
597
  end
423
598
 
424
- # Development - Monitor only
599
+ # ⚖️ BALANCED - Default production (recommended for most apps)
425
600
  Beskar.configure do |config|
426
- config.waf = {
427
- enabled: true,
428
- monitor_only: true, # Log but never block
429
- create_security_events: true
601
+ config.waf[:enabled] = true
602
+ config.waf[:auto_block] = true
603
+ config.waf[:score_threshold] = 150 # Default threshold
604
+ config.waf[:violation_window] = 6.hours # Standard window
605
+ config.waf[:permanent_block_after] = 500 # Permanent at 500 cumulative
606
+ config.waf[:decay_enabled] = true
607
+ # Uses default decay rates (critical: 360, high: 120, medium: 45, low: 15)
608
+
609
+ config.waf[:record_not_found_exclusions] = [
610
+ %r{/posts/.*}, %r{/products/[\\w-]+}
611
+ ]
612
+ end
613
+
614
+ # 🧪 PERMISSIVE - High-traffic public site with many 404s
615
+ Beskar.configure do |config|
616
+ config.waf[:enabled] = true
617
+ config.waf[:auto_block] = true
618
+ config.waf[:score_threshold] = 200 # Higher tolerance
619
+ config.waf[:violation_window] = 3.hours # Shorter memory
620
+ config.waf[:permanent_block_after] = 800 # Rare permanent bans
621
+
622
+ # Faster decay = violations forgotten quickly
623
+ config.waf[:decay_rates] = {
624
+ critical: 180, # 3 hour half-life
625
+ high: 60, # 1 hour half-life
626
+ medium: 20, # 20 minute half-life
627
+ low: 5 # 5 minute half-life (very forgiving)
430
628
  }
431
-
432
- config.ip_whitelist = ["127.0.0.1", "::1"] # Whitelist localhost
629
+
630
+ # Extensive exclusions for public content
631
+ config.waf[:record_not_found_exclusions] = [
632
+ %r{/posts/.*}, %r{/articles/.*}, %r{/tags/.*},
633
+ %r{/search/.*}, %r{/public/.*}, %r{/assets/.*}
634
+ ]
635
+ end
636
+
637
+ # 🔍 MONITOR ONLY - Testing/staging (recommended before going live)
638
+ Beskar.configure do |config|
639
+ config.monitor_only = true # Log violations but NEVER block
640
+ config.waf[:enabled] = true
641
+ config.waf[:create_security_events] = true
642
+ config.ip_whitelist = ["127.0.0.1", "::1"] # Whitelist localhost
433
643
  end
434
644
  ```
435
645
 
436
646
  **Blocking Behavior:**
437
- - **First violation**: Logged, violation count incremented
438
- - **Threshold reached** (default 3): IP automatically banned for 1 hour
439
- - **Repeat violations**: Ban duration escalates: 1h 6h → 24h → 7d → **permanent**
440
- - **Permanent block**: After 5 violations (configurable), IP is permanently banned
441
- - **Monitor mode**: Logs all violations but never blocks (useful for tuning)
647
+
648
+ With **default settings** (score_threshold: 150):
649
+ - **Violations accumulate**: Each violation adds points based on severity
650
+ - **Score threshold reached**: IP automatically banned when cumulative score 150
651
+ - **Exponential decay**: Violations lose impact over time based on severity
652
+ - **Ban duration escalates**: Based on total score accumulated:
653
+ - 150-300 points → 1 hour ban
654
+ - 300-450 points → 6 hour ban
655
+ - 450-600 points → 24 hour ban
656
+ - 600+ points → 7 day ban
657
+ - 500+ cumulative score → **permanent ban**
658
+
659
+ **Key Advantages:**
660
+ - **Fewer false positives**: Low-severity violations (404s) decay quickly
661
+ - **Faster response to serious threats**: Critical violations persist longer
662
+ - **Adaptive blocking**: Mixed attack patterns properly weighted
663
+ - **Monitor mode compatible**: Set `config.monitor_only = true` to log without blocking
664
+
665
+ > **Production Tip:** Start with monitor mode for 24-48 hours to observe your traffic patterns, then adjust thresholds and exclusions before enabling blocking.
442
666
 
443
667
  **Check WAF status:**
444
668
  ```ruby
445
- # Check if WAF detected threats
669
+ # Get current risk score (with decay applied)
670
+ current_score = Beskar::Services::Waf.get_current_score(ip_address)
671
+ # => 145.3 (below threshold, not blocked)
672
+
673
+ # Get number of violations tracked
446
674
  violation_count = Beskar::Services::Waf.get_violation_count(ip_address)
675
+ # => 3 (number of violations being tracked)
676
+
677
+ # Get detailed violation records
678
+ violations = Beskar::Services::Waf.get_violations(ip_address)
679
+ # => [{timestamp: ..., score: 95, severity: :critical, category: :config_files}, ...]
447
680
 
448
681
  # Reset violations (admin action)
449
682
  Beskar::Services::Waf.reset_violations(ip_address)
@@ -459,14 +692,19 @@ end
459
692
 
460
693
  ### IP Blocking and Banning
461
694
 
462
- Beskar uses a hybrid cache + database blocking system that persists across application restarts.
695
+ Beskar uses indexed database ban checks. All workers must share the same authoritative database. Cache eviction, cache outages, and stale cache values cannot change ban enforcement.
463
696
 
464
- **Automatic IP Banning:**
697
+ **Automatic IP Banning Thresholds:**
465
698
 
466
- IPs are automatically banned for:
467
- 1. **Authentication abuse** - 10+ failed login attempts in 1 hour
468
- 2. **Rate limit violations** - 5+ rate limit violations in 1 hour
469
- 3. **WAF violations** - 3+ vulnerability scan attempts (configurable)
699
+ | Trigger | Threshold | Time Window | Ban Duration | Configurable |
700
+ |---------|-----------|-------------|--------------|--------------|
701
+ | **Authentication attempt limit** | Configured IP limit (default 10) | Configured period (default 1 hour) | HTTP 429 until the actual retry deadline | Via rate_limiting config |
702
+ | **Rate Limit Violations** | 5 denied requests | Fixed 1 hour window | Adds 1 hour to a temporary ban | Fixed middleware policy |
703
+ | **WAF Violations** | Cumulative score (default 150) | Configured window with optional decay | Score-based durations or permanent | Via waf configuration |
704
+
705
+ Explicit extensions without a duration escalate to 6h, 24h, 7d, then permanent.
706
+ Extensions with a duration add that duration; already permanent bans remain permanent.
707
+ Monitor mode does not create automatic bans.
470
708
 
471
709
  **Manual IP Management:**
472
710
 
@@ -520,16 +758,11 @@ Beskar::BannedIp.where(reason: 'rate_limit_abuse')
520
758
  removed_count = Beskar::BannedIp.cleanup_expired!
521
759
  ```
522
760
 
523
- **Preload cache on startup:**
761
+ **State cleanup:**
524
762
 
525
- The cache is automatically preloaded when your app starts, but you can manually trigger it:
526
-
527
- ```ruby
528
- # In config/initializers/beskar.rb (optional - happens automatically)
529
- Rails.application.config.after_initialize do
530
- Beskar::BannedIp.preload_cache!
531
- end
532
- ```
763
+ Schedule `bin/rails beskar:cleanup_security_state` to reclaim expired coordination
764
+ rows. Audit-event retention is separate. Ban cache preloading is no longer required;
765
+ `Beskar::BannedIp.preload_cache!` remains a compatibility no-op.
533
766
 
534
767
  ### Security Events and Monitoring
535
768
 
@@ -574,10 +807,10 @@ class SecurityCleanupJob < ApplicationJob
574
807
  # Remove expired bans from database
575
808
  removed = Beskar::BannedIp.cleanup_expired!
576
809
  Rails.logger.info "Cleaned up #{removed} expired IP bans"
577
-
810
+
578
811
  # Archive old security events (optional)
579
812
  Beskar::SecurityEvent.where('created_at < ?', 90.days.ago).delete_all
580
-
813
+
581
814
  # Generate security report (example)
582
815
  report = {
583
816
  active_bans: Beskar::BannedIp.active.count,
@@ -587,7 +820,7 @@ class SecurityCleanupJob < ApplicationJob
587
820
  created_at: 24.hours.ago..Time.current
588
821
  ).count
589
822
  }
590
-
823
+
591
824
  # Send to monitoring service
592
825
  Rails.logger.info "Security Report: #{report}"
593
826
  end
@@ -611,10 +844,7 @@ Every request passes through these security checks in order:
611
844
  **Features:**
612
845
  - **Early exit** - Banned IPs are blocked immediately for performance
613
846
  - **Whitelist bypass** - Trusted IPs bypass all blocking but activity is logged
614
- - **Auto-blocking** - Automatic IP banning after:
615
- - 10+ failed authentication attempts (authentication abuse)
616
- - 5+ rate limit violations in 1 hour (rate limit abuse)
617
- - 3+ WAF violations (configurable, vulnerability scanning)
847
+ - **Auto-blocking** - See [Automatic IP Banning Thresholds](#ip-blocking-and-banning) section for details
618
848
  - **Custom error pages** - Returns helpful 403/429 error responses
619
849
  - **Response headers** - Adds `X-Beskar-Blocked` and `X-Beskar-Rate-Limited` headers
620
850
  - **Graceful degradation** - Continues working if cache is unavailable
@@ -637,19 +867,27 @@ Security events are logged to the `beskar_security_events` table for analysis an
637
867
 
638
868
  | Category | Severity | Example Patterns | Risk Score |
639
869
  |----------|----------|------------------|------------|
640
- | WordPress Scans | High | `/wp-admin`, `/wp-login.php`, `/xmlrpc.php` | 80 |
870
+ | WordPress Scans | High | `/wp-admin`, `/wp-login.php`, `/wp-content/*.php` | 80 |
871
+ | WordPress Static Files | Low | `/wp-content/*.css`, `/wp-content/*.jpg` | 30 |
641
872
  | PHP Admin Panels | High | `/phpmyadmin`, `/admin.php`, `/phpinfo.php` | 80 |
642
873
  | Config Files | **Critical** | `/.env`, `/.git`, `/database.yml`, `/config.php` | **95** |
643
874
  | Path Traversal | **Critical** | `/../../../etc/passwd`, `%2e%2e/` | **95** |
644
875
  | Framework Debug | Medium | `/rails/info/routes`, `/__debug__`, `/telescope` | 60 |
645
876
  | CMS Detection | Medium | `/joomla`, `/drupal`, `/magento` | 60 |
646
877
  | Common Exploits | **Critical** | `/shell.php`, `/c99.php`, `/webshell` | **95** |
878
+ | Rails Format Paths | Medium | `/users/1.exe`, `/reports?format=exe` | 60 |
879
+ | Record Scanning Paths | Low | `/account/999999` | 30 |
880
+ | IP Spoofing Exception | **Critical** | Conflicting IP headers | **95** |
881
+ | UnknownFormat / InvalidType Exceptions | Medium | Requires path/format evidence by default | 60 |
882
+ | RecordNotFound Exception | Low | Requires path/format evidence by default | 30 |
883
+
884
+ **Pattern matching:**
647
885
 
648
- **Pattern matching is:**
649
- - Case-insensitive
650
- - Works on full path including query strings
651
- - Detects URL-encoded variants
652
- - Can match multiple patterns per request
886
+ - Uses an at-most-8-KiB path with up to two percent-decoding passes; backslashes become slashes and dot segments are retained.
887
+ - Uses case-insensitive matching for most signatures, with explicit root/segment boundaries.
888
+ - Ignores arbitrary query text and request bodies; only the exact `format` query key is examined.
889
+ - Can match several rules but charges once per middleware pass; ordinary `.well-known` endpoints are not signatures.
890
+ - Supports explicit method/path/category exclusions. `exception_detection` defaults to `:suspicious`; `:all` opts into broad exception scoring and `:none` disables exception scoring.
653
891
 
654
892
  ## Security Best Practices
655
893
 
@@ -658,21 +896,17 @@ Security events are logged to the `beskar_security_events` table for analysis an
658
896
  When first enabling WAF, use monitor-only mode to tune thresholds:
659
897
 
660
898
  ```ruby
661
- config.waf = {
662
- enabled: true,
663
- monitor_only: true, # Log but don't block
664
- create_security_events: true
665
- }
899
+ config.monitor_only = true # Log but don't block
900
+ config.waf[:enabled] = true
901
+ config.waf[:create_security_events] = true
666
902
  ```
667
903
 
668
904
  After reviewing logs for false positives, enable blocking:
669
905
 
670
906
  ```ruby
671
- config.waf = {
672
- enabled: true,
673
- monitor_only: false,
674
- auto_block: true
675
- }
907
+ config.monitor_only = false
908
+ config.waf[:enabled] = true
909
+ config.waf[:auto_block] = true
676
910
  ```
677
911
 
678
912
  ### 2. Whitelist Carefully
@@ -757,10 +991,10 @@ end
757
991
 
758
992
  ### Issue: Legitimate users being blocked
759
993
 
760
- **Solution:** Add their IP to whitelist or reduce WAF `block_threshold`:
994
+ **Solution:** Review matching rules in monitor mode, add narrow exclusions, or raise the cumulative score threshold:
761
995
 
762
996
  ```ruby
763
- config.waf[:block_threshold] = 5 # Increase from default 3
997
+ config.waf[:score_threshold] = 250 # Increase from default 150; not a violation count
764
998
  ```
765
999
 
766
1000
  Or whitelist specific IPs:
@@ -773,7 +1007,7 @@ config.ip_whitelist = ["user.ip.address.here"]
773
1007
  **Solution:** Enable monitor-only mode and review patterns:
774
1008
 
775
1009
  ```ruby
776
- config.waf[:monitor_only] = true
1010
+ config.monitor_only = true # This is a global setting, not WAF-specific
777
1011
 
778
1012
  # Review what's being flagged
779
1013
  Beskar::SecurityEvent.where(event_type: 'waf_violation').last(20).each do |event|
@@ -795,50 +1029,25 @@ Beskar::BannedIp.cleanup_expired!
795
1029
 
796
1030
  ### Issue: Performance concerns
797
1031
 
798
- **Solution:** Beskar uses cache-first architecture. Ensure cache is configured:
799
-
800
- ```ruby
801
- # config/environments/production.rb
802
- config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
803
- ```
804
-
805
- Check cache health:
806
- ```ruby
807
- Rails.cache.read("test_key") # Should work
808
- Beskar::BannedIp.preload_cache! # Reload from database if needed
809
- ```
1032
+ **Solution:** Measure database query latency, connection-pool contention, and global
1033
+ counter throughput. Redis is not required and cannot replace the authoritative
1034
+ database. See [state-storage trade-offs](docs/operations/state-storage.md).
810
1035
 
811
1036
  ## Migration from Previous Versions
812
1037
 
813
- If upgrading from a version without WAF/IP blocking features:
814
-
815
- ```bash
816
- # Run new migrations
817
- rails db:migrate
818
-
819
- # Preload cache with existing bans (if any)
820
- rails runner "Beskar::BannedIp.preload_cache!"
821
-
822
- # Test in development first
823
- RAILS_ENV=development rails server
824
-
825
- # Review logs for any issues
826
- tail -f log/development.log | grep Beskar
827
- ```
1038
+ Copy engine migrations with `bin/rails beskar:install:migrations`, then run
1039
+ `bin/rails db:migrate` before starting new workers. Drain old cache-based workers:
1040
+ mixed versions do not share counters. Existing cache counters are not imported.
1041
+ Review legacy bans and schedule state cleanup as described in
1042
+ [the upgrade notes](docs/operations/state-storage.md#upgrade).
828
1043
 
829
1044
  ## Performance Characteristics
830
1045
 
831
- - **Whitelist check**: O(n) where n = whitelist size, cached, < 1ms
832
- - **Banned IP check**: O(1) cache lookup, < 1ms
833
- - **Rate limit check**: O(1) cache lookup, < 1ms
834
- - **WAF analysis**: O(m) where m = number of patterns, < 5ms
835
- - **Total middleware overhead**: Typically < 10ms per request
836
-
837
- **Scalability:**
838
- - Handles 1000s of requests/second
839
- - Cache-first architecture minimizes database queries
840
- - Efficient pattern matching with compiled regexes
841
- - Parallel test execution: 352 tests run in < 3 seconds
1046
+ Ordinary requests read indexed ban state. Request-wide IP quota checks are opt-in. Authentication
1047
+ attempts update applicable counters transactionally; WAF violations update per-IP
1048
+ history. The global counter is disabled by default; enabling it serializes authentication accounting. No fixed
1049
+ requests-per-second or latency guarantee is asserted; benchmark the deployed
1050
+ database and workload.
842
1051
 
843
1052
  ## Development
844
1053
 
@@ -853,7 +1062,7 @@ $ bin/rails test
853
1062
 
854
1063
  ## Contributing
855
1064
 
856
- Bug reports and pull requests are welcome on GitHub at [https://github.com/prograis/beskar](https://github.com/prograils/beskar).
1065
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/prograis/beskar](https://github.com/prograils/beskar).
857
1066
 
858
1067
  ## License
859
1068
 
@@ -862,4 +1071,3 @@ The gem is available as open source under the terms of the [MIT License](https:/
862
1071
  ## Code of Conduct
863
1072
 
864
1073
  Just be nice to each other.
865
-