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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +274 -0
- data/README.md +412 -204
- data/app/channels/concerns/beskar/channels/session_security.rb +46 -0
- data/app/controllers/beskar/administrative_actions_controller.rb +16 -0
- data/app/controllers/beskar/application_controller.rb +214 -0
- data/app/controllers/beskar/banned_ips_controller.rb +255 -0
- data/app/controllers/beskar/dashboard_controller.rb +62 -0
- data/app/controllers/beskar/security_events_controller.rb +164 -0
- data/app/controllers/concerns/beskar/controllers/audit_export.rb +54 -0
- data/app/controllers/concerns/beskar/controllers/security_tracking.rb +76 -48
- data/app/controllers/concerns/beskar/controllers/session_security.rb +29 -0
- data/app/jobs/beskar/notification_job.rb +33 -0
- data/app/mailers/beskar/security_mailer.rb +59 -0
- data/app/models/beskar/administrative_action.rb +41 -0
- data/app/models/beskar/banned_ip.rb +105 -105
- data/app/models/beskar/security_event.rb +51 -4
- data/app/models/beskar/security_state.rb +58 -0
- data/app/services/beskar/banned_ip_manager.rb +88 -0
- data/app/views/beskar/administrative_actions/index.html.erb +33 -0
- data/app/views/beskar/administrative_actions/show.html.erb +21 -0
- data/app/views/beskar/banned_ips/edit.html.erb +195 -0
- data/app/views/beskar/banned_ips/index.html.erb +319 -0
- data/app/views/beskar/banned_ips/new.html.erb +190 -0
- data/app/views/beskar/banned_ips/review.html.erb +24 -0
- data/app/views/beskar/banned_ips/show.html.erb +304 -0
- data/app/views/beskar/dashboard/index.html.erb +280 -0
- data/app/views/beskar/security_events/index.html.erb +302 -0
- data/app/views/beskar/security_events/show.html.erb +293 -0
- data/app/views/beskar/shared/_export_form.html.erb +10 -0
- data/app/views/layouts/beskar/_behavior.html.erb +121 -0
- data/app/views/layouts/beskar/application.html.erb +581 -6
- data/config/routes.rb +30 -0
- data/db/migrate/20251016000001_create_beskar_security_events.rb +3 -3
- data/db/migrate/20260910000001_create_beskar_security_states.rb +14 -0
- data/db/migrate/20260911000001_create_beskar_administrative_actions.rb +22 -0
- data/db/migrate/20260911000002_expand_administrative_action_targets.rb +6 -0
- data/docs/README.md +73 -0
- data/docs/archive/project-documentation.md +659 -0
- data/docs/audits/project-review.md +437 -0
- data/docs/audits/repair-status.md +216 -0
- data/docs/guides/audit-and-waf.md +175 -0
- data/docs/guides/audit-lifecycle.md +172 -0
- data/docs/guides/authentication.md +213 -0
- data/docs/guides/configuration.md +182 -0
- data/docs/guides/dashboard-and-search.md +251 -0
- data/docs/guides/notifications-and-recovery.md +157 -0
- data/docs/guides/risk-scoring.md +116 -0
- data/docs/operations/monitor-only-mode.md +85 -0
- data/docs/operations/security-hardening.md +167 -0
- data/docs/operations/state-storage.md +144 -0
- data/docs/research/rust-performance-assessment.md +69 -0
- data/lib/beskar/configuration.rb +105 -20
- data/lib/beskar/configuration_validator.rb +188 -0
- data/lib/beskar/devise_authentication.rb +24 -0
- data/lib/beskar/engine.rb +21 -88
- data/lib/beskar/logger.rb +288 -0
- data/lib/beskar/middleware/request_analyzer.rb +133 -99
- data/lib/beskar/models/security_trackable_authenticable.rb +76 -97
- data/lib/beskar/models/security_trackable_devise.rb +34 -25
- data/lib/beskar/models/security_trackable_generic.rb +171 -214
- data/lib/beskar/risk_level.rb +22 -0
- data/lib/beskar/services/account_locker.rb +90 -81
- data/lib/beskar/services/administrative_audit.rb +36 -0
- data/lib/beskar/services/administrative_bans.rb +104 -0
- data/lib/beskar/services/audit_data.rb +72 -0
- data/lib/beskar/services/authentication.rb +31 -0
- data/lib/beskar/services/authentication_attempt.rb +141 -0
- data/lib/beskar/services/ban_expiry.rb +28 -0
- data/lib/beskar/services/device_detector.rb +32 -41
- data/lib/beskar/services/event_search.rb +58 -0
- data/lib/beskar/services/geolocation_service.rb +83 -114
- data/lib/beskar/services/ip_whitelist.rb +31 -40
- data/lib/beskar/services/location_assessment.rb +109 -0
- data/lib/beskar/services/native_account_lock.rb +82 -0
- data/lib/beskar/services/notifications.rb +46 -0
- data/lib/beskar/services/rate_limiter.rb +99 -125
- data/lib/beskar/services/request_context.rb +64 -0
- data/lib/beskar/services/risk_assessment.rb +58 -0
- data/lib/beskar/services/session_revocation.rb +62 -0
- data/lib/beskar/services/waf.rb +311 -198
- data/lib/beskar/services/waf_request.rb +60 -0
- data/lib/beskar/version.rb +1 -1
- data/lib/beskar/warden_authentication.rb +53 -0
- data/lib/beskar.rb +54 -4
- data/lib/generators/beskar/install/install_generator.rb +158 -0
- data/lib/generators/beskar/install/templates/initializer.rb.tt +261 -0
- data/lib/tasks/beskar_tasks.rake +25 -20
- metadata +93 -12
- data/lib/beskar/templates/beskar_initializer.rb +0 -107
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Review remediation
|
|
2
|
+
|
|
3
|
+
Tracks [Original project review](project-review.md), which remains the historical baseline.
|
|
4
|
+
Ten implementation batches are complete; this is not a claim that all findings are fixed.
|
|
5
|
+
The runtime is the project's mise default Ruby **4.0.6**.
|
|
6
|
+
|
|
7
|
+
Batch ten addresses the renewed audit findings 2–6. See
|
|
8
|
+
[Security hardening and rollout](../operations/security-hardening.md) for the entry-point coverage matrix,
|
|
9
|
+
revocation semantics, separate administrative permissions, required export/config
|
|
10
|
+
history, changed availability defaults, and the host/database validation gates
|
|
11
|
+
that remain open. Account deletion still retains events unchanged.
|
|
12
|
+
|
|
13
|
+
## Implemented and locally verified
|
|
14
|
+
|
|
15
|
+
| Finding | Change and evidence |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| F01 | Devise database-password/HTTP Basic admission now reserves and enforces IP/account and opt-in global limits before password verification. Failed known targets count against their account; unknown identities use normalized hashed keys. Native controllers have explicit admission/session guards. Outcome callbacks do not double-count, and credential-free page visits create no attempts. Standard custom Warden strategies now have IP pre-verification admission and account binding after identity resolution; set_user is guarded even when callbacks are disabled. API/Cable adapters and remaining host integration boundaries are documented in docs/operations/security-hardening.md. |
|
|
18
|
+
| F02 | Warden sign-out uses the current request's actual lock result, not recent audit rows, and affects only the current scope. Confirmed locks now always reject admission, rotate a durable account generation, and invalidate all prior Devise sessions/remember cookies. Unlock never restores them; unrelated accounts remain signed in. |
|
|
19
|
+
| F03 | Beskar automatic account locks, sign-outs, and emergency resets honor monitor/whitelist policy. Whitelisted authentication observations cannot consume enforced global capacity. Risk-enabled audits now record would-lock eligibility and adapter availability without claiming an actual lock. Host Devise/Rails policies remain independent. |
|
|
20
|
+
| F04 | Implemented persistent native locks, expiry/manual unlock, all-session revocation, and a transactional session-creation guard. Existing-session readers honor lock state. Separate-connection races and failed/aborted destruction callbacks are covered; failed cleanup requires successful cleanup before manual unlock. Host integration and same-writer-pool requirements are explicit in docs/guides/authentication.md. |
|
|
21
|
+
| F05 | Geographic history pairs normalized JSON locations with their own timestamps/IDs, uses actual elapsed seconds and explicit chronology, and rejects unusable coordinates/times. Real Devise/native requests exercise persisted travel evidence through actual locks. Mock locations never create travel/country risk. Bounded history and geolocation limitations are explicit in docs/guides/risk-scoring.md; production accuracy remains unverified. |
|
|
22
|
+
| F06 | Middleware, authentication audit, rate limiting, and WAF attribution use the Rails-resolved `remote_ip`. Trusted-proxy regression coverage added. Hosts must configure trusted proxies correctly. |
|
|
23
|
+
| F07 | Ban enforcement reads the database, never cached booleans. Regressions cover rollback, IP edits, expiry edits, stale positives, and stale negatives. |
|
|
24
|
+
| F08 | Permanent flag is authoritative; permanent bans are excluded from expiry cleanup and normalize expiry to nil on save. Temporary bans require expiry. Legacy-data review remains an upgrade prerequisite. |
|
|
25
|
+
| F09 | Database-backed state, unique keys, transactional updates, optimistic conflict detection, and bounded retries replace cache read/modify/write. Separate-connection SQLite regressions exercise counters, admissions, WAF history, and repeated bans. Other database concurrency/load runs remain unverified. |
|
|
26
|
+
| F13 | Minitest 5 pinned for Rails 8.0; lint dependencies updated for Ruby 4. CI now includes Ruby 4.0.6 and 3.4; duplicate automatic workflow made manual. Local verification below; hosted CI has not been run here. |
|
|
27
|
+
| F14 | Removed the disconnected `ip_auth_failures` cache representation. Request-wide IP-quota blocking and its fixed-window denial/auto-ban counter are now opt-in; unrelated shared-NAT traffic remains accessible by default. |
|
|
28
|
+
| F15 | Read-only previews, configured counting windows, bounded attempt storage, enforced backoff deadlines, longest retry selection, global backoff separation, and explicit global/IP-denial reset coverage. Global capacity is now opt-in, removing the default shared lock hot spot and distributed-denial budget. |
|
|
29
|
+
| F16 | Added bounded path canonicalization, root/segment-aware signatures, exact-format query matching, and method/path/category exclusions. Ordinary Rails exceptions no longer score without independent scanner evidence by default; broad scoring requires opt-in. Middleware records at most one charge for path plus exception. Benign/attack corpora cover boundary false positives, encoded traversal, query poisoning, malformed input, and exclusions. Production route coverage and false-positive rates remain unverified; this remains a cumulative scanner heuristic, not first-request exploit prevention. |
|
|
30
|
+
| F19 | Monitor WAF/rate observations use separate state and never create automatic active IP bans. WAF audit `would_be_blocked` respects whitelist and auto-block policy. Automatic account actions now honor the same monitor/whitelist policy. |
|
|
31
|
+
|
|
32
|
+
## Partially addressed
|
|
33
|
+
|
|
34
|
+
| Finding | Completed so far | Still open |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| F10 | Dashboard callbacks that render/redirect are not rendered twice; missing-config logging no longer interpolates secrets; credential recipes reject blank/missing values and use secure comparison; recursive installer example removed. | Broader host-adapter authorization coverage and review of custom callbacks. |
|
|
37
|
+
| F11 | Authentication context drops session IDs/forwarded headers and cleans referrers. WAF sinks retain bounded rule evidence, not raw URLs/headers/exception messages. Audit models bound/filter metadata and text using built-in plus host parameter filters, including on legacy model reads. Rescued-exception logs use classes rather than messages. CSV exporters quote cells and visibly prefix formula-like text; JSON projects known fields. Associated Devise/native email presentation now shares filtering across views and exports. Export authorization and no-store behavior are tested. | Arbitrary secrets in allowed free text/paths, historical rows/logs/backups, raw SQL/bulk API bypasses, retention/access policy, and actual spreadsheet-client import/save/reopen QA. Stored-value search can reveal legacy record membership despite read-time redaction. Audit filtering does not anonymize enforcement fields such as ban IPs. |
|
|
38
|
+
| F12 | Logger fallback no longer recurses. Cache availability is not required for authoritative state. Required authentication-state, lock-persistence, and enforced risk-assessment failures reject with 503; optional authentication and WAF audit writes are isolated with savepoints. A failed optional WAF audit does not prevent state updates or bans. Optional host analysis queues only after enclosing commits; rollback cancels it and queue failures do not undo admission/committed work or log raw exception messages. | Unified dependency-failure policy for the other middleware/WAF paths and database-specific fault tests. The optional analysis hook has no outbox/durable-delivery guarantee. |
|
|
39
|
+
| F17 | One assessment provides explicit factors whose points/caps sum to the score and flow into lock evidence. Corrected bot/reason flags, browser captures, and midnight scoring. Removed implicit IP/unlock trust discounts and geographic bypasses; only explicitly admitted, unlocked-success history supplies travel observations. The geographic pattern helper is implemented. Observation history does not raise enforced risk. | Production calibration/false-positive evaluation and any future verified-device/recovery trust mechanism. User-Agent and IP geography remain spoofable/approximate heuristics, not identity proof. |
|
|
40
|
+
| F18 | Native unlock deadlines/manual locks work; Devise's own unlock policy is explicitly separate. Emergency thresholds count confirmed locks with true flags, not duplicated successes or false-flag strings. Password invalidation, session revocation, and mandatory recovery audit are transactional. Opt-in Action Mailer lock/reset/team notices now use independent post-commit jobs with five-attempt retries, validated sender/recipients/recovery page, and isolated failures. Native recovery is exercised through the host reset email/token redemption and authorized manual unlock; resetting the native password alone cannot bypass the lock. A real Devise reset-email/token handoff now verifies its own email-unlock policy, replay rejection and continued revocation of old sessions. | Real SMTP/production worker and broader host recovery verification; durable outbox/reconciliation, duplicate/bounce handling, delivery-status history, and broader host recovery/identity-verification policies. |
|
|
41
|
+
| F20 | Correct migration source/destination paths, destination-aware mount detection, migration numbering, idempotence with engine-copied migrations, monitor-first initializer, truthful immediate installation instructions. Copied migrations run successfully against a fresh SQLite database. | Full fresh host-app boot/authentication exercise and remaining older documentation claims. |
|
|
42
|
+
| F21 | README and gem metadata describe scanner-path and User-Agent heuristics without promising SQLi/XSS filtering, JavaScript challenges, or honeypots. Removed versioned API routes that pointed to missing controllers; authenticated resource exports remain. Devise's invalid callback-registration path was removed. Automatic background analysis now defaults off and requires an explicit host Active Job with a documented post-commit contract. Both authentication paths invoke it only after admission. Removed custom-lock/IP2Location no-op placeholders; unsupported settings fail explicitly. Notification defaults now opt out and their enabled paths perform real Action Mailer delivery, not log-only intent. Current operational-contract documents ship in the gem. | Remaining historical capability/configuration claims. No built-in analyzer, production worker validation, custom lock adapter API, IP2Location integration, or standalone recovery/token endpoint is supplied. |
|
|
43
|
+
| F22 | Added a composite user/event/time index; risk-history projections are ordered/bounded and one device/geographic snapshot supplies scoring/evidence. Exports are capped at 1,000 rows with cursors. Centralized PostgreSQL/SQLite/MySQL JSON search expressions, exact legacy email fallback, bound text values, and literal LIKE escaping. Overview counts reuse grouped queries; related-event tables preload users. Query-count regressions pass. | Live PostgreSQL/MySQL execution, adapter/collation/query-plan/load testing, further query reduction, and retention. JSON-text substring search remains expensive; paging and grouped counts are not transactional snapshots. |
|
|
44
|
+
| F23 | Account deletion retains linked security events unchanged, including original polymorphic IDs, as explicitly chosen. Raw-row comparisons and dashboard/export regressions cover both adapters. Dashboard ban changes require a trusted actor and reason, with bounded before/after snapshots, server operation UUID, and request correlation in a separate journal that survives target/actor deletion. Changes and history share a transaction and ban coordination; bulk selections are bounded, deduplicated, and all-or-nothing. Required-history failures and aborted destruction never report success. SecurityEvent and administrative history reject normal instance CRUD rewrites. Exports require separate permission, actor/reason and a mandatory preparation journal; runtime configuration has its own grant and before/after journal; manual extensions no longer fabricate violations. | No automatic retention period/purge, historical backfill, universal auditing of direct model/automatic changes, failed-attempt journal, database tamper resistance, or production adapter/load verification. Low-level SQL/bulk/counter APIs and privileged host code can bypass model protections. Boot/deployment changes need host source-control/deployment auditing. |
|
|
45
|
+
| F24 | Whitelist parsing follows replaced/mutated configuration; default getters and Devise scope mappings are corrected. Geolocation caches are isolated by provider/database identity, and readers follow database generations. Startup/configure validation checks section schemas, booleans, numeric bounds, whitelist/exclusions, supported providers/strategies, readable MaxMind paths, and enabled host jobs. Partial section assignments merge library defaults; invalid configure blocks do not publish partial changes. Boot/job-autoload and direct unsupported-strategy authentication regressions pass. | Host model/adapter readiness checks, real MaxMind hot-reload/load testing and distributed configuration. Configuration is sealed at boot; authorized runtime changes serialize local publication with required history. This is process-local, not a distributed or crash-atomic publication protocol. |
|
|
46
|
+
| F25 | Lowercase Rack 3 response headers and actual retry deadlines; verified using Rack::Lint. | Content negotiation for API clients. |
|
|
47
|
+
| F26 | Reporting bands, full-history statistics, native-user identity/filtering, and stable event ordering are unified. Ban expiry fields now explicitly use UTC with strict server parsing, browser-compatible milliseconds, and unchanged-value microsecond preservation. Presets use server time; invalid inputs are rejected, custom reasons survive edits, and validation retries preserve duration selections. One nonce-bearing script replaces inline handlers and handwritten method-link submission. Native navigation opts out of host Turbo; real forms retain CSRF and work without JavaScript. Page-size changes preserve filters without duplicate/stale fields; validation feedback stays visible. Chromium regressions cover timezone/DST, skewed clocks, precision, toggles, bulk confirmations, Turbo/back navigation, and no-script forms. | Full strict-style CSP (inline style attributes remain), other browsers, mobile/accessibility and broader host-layout/auth QA, Turbo Frames/Streams, stale-form protection, and remaining dashboard semantics. |
|
|
48
|
+
|
|
49
|
+
## Next implementation batch
|
|
50
|
+
|
|
51
|
+
First run the new PostgreSQL/MySQL CI jobs and host multi-worker load/failure
|
|
52
|
+
tests; Docker daemon access was denied locally, including outside the sandbox.
|
|
53
|
+
Verify host API/Cable/native adapters and deployment configuration audit coverage.
|
|
54
|
+
Then address F25 content negotiation and the remaining F26 strict-style CSP/host UI work.
|
|
55
|
+
Continue the remaining privacy and
|
|
56
|
+
real-client validation work in F11.
|
|
57
|
+
Complete the remaining portions of F10/F12/F18/F20/F21/F22/F24/F25 alongside those changes.
|
|
58
|
+
|
|
59
|
+
## Verification
|
|
60
|
+
|
|
61
|
+
- Batch ten regular suite: **839 tests, 4,369 assertions, 0 failures/errors,
|
|
62
|
+
3 existing MaxMind-data skips**, Ruby 4.0.6, seeds `20260915` and `20260916`.
|
|
63
|
+
- Batch ten Chromium suite: **10 tests, 97 assertions, 0 failures/errors/skips**,
|
|
64
|
+
Chromium/ChromeDriver **153.0.8010.36**, seeds `20260915` and `20260916`. Includes the new
|
|
65
|
+
reason-bearing export form, required history, and existing form regressions.
|
|
66
|
+
- Batch ten lint: **180 files, no offenses**. Zeitwerk and CI YAML parsing pass.
|
|
67
|
+
The existing dummy mailer-preview eager-load warning remains. New test-only
|
|
68
|
+
adapters are pg 1.6.3 and mysql2 0.5.7; their servers/CI jobs were not run locally.
|
|
69
|
+
- New authentication regressions cover custom Warden failure admission, disabled
|
|
70
|
+
callbacks, same-scope identity substitution, whitelist isolation, multiple
|
|
71
|
+
browser/remember-cookie revocation, lock rollback, revocation during verification,
|
|
72
|
+
generic token issuance, stale native sessions, deleted cached Cable users,
|
|
73
|
+
inbound/outbound Cable denial, and Devise recovery-token replay rejection.
|
|
74
|
+
- New administration/availability regressions cover separate permissions,
|
|
75
|
+
required export actor/reason/history, append-only events, sealed/runtime config
|
|
76
|
+
publication, failed journal/transaction rejection, distributed login capacity,
|
|
77
|
+
shared-NAT page access, eliminated default quota queries and database failures.
|
|
78
|
+
|
|
79
|
+
- Before implementation, restored baseline: **595 tests, 2,342 assertions,
|
|
80
|
+
0 failures, 0 errors, 4 skips**.
|
|
81
|
+
- First batch: **626 tests, 2,465 assertions, 0 failures, 0 errors, 4 skips**.
|
|
82
|
+
- Second batch: **655 tests, 2,606 assertions, 0 failures, 0 errors, 4 skips**.
|
|
83
|
+
- Third batch: **679 tests, 2,753 assertions, 0 failures, 0 errors, 4 skips**.
|
|
84
|
+
- Fourth batch: **700 tests, 2,951 assertions, 0 failures, 0 errors, 4 skips**.
|
|
85
|
+
- Fifth batch: **718 tests, 3,177 assertions, 0 failures, 0 errors, 4 skips**.
|
|
86
|
+
- Sixth batch: **747 tests, 3,388 assertions, 0 failures, 0 errors, 3 skips**.
|
|
87
|
+
- Seventh batch: **768 tests, 3,623 assertions, 0 failures, 0 errors, 3 skips**.
|
|
88
|
+
- Eighth batch: **791 tests, 3,932 assertions, 0 failures, 0 errors, 3 skips**.
|
|
89
|
+
- Ninth-batch regular suite: **798 tests, 4,073 assertions, 0 failures, 0 errors,
|
|
90
|
+
3 existing MaxMind database skips**, Ruby 4.0.6, seeds `20260923` and `20260924`.
|
|
91
|
+
- Ninth-batch Chromium system suite: **9 tests, 91 assertions, 0 failures, 0 errors,
|
|
92
|
+
0 skips**, Chromium/ChromeDriver 152.0.7977.82, seeds `20260923` and `20260924`.
|
|
93
|
+
The formerly assertion-free ban-deletion test now verifies a 404 response.
|
|
94
|
+
- Standard Ruby lint passes (164 files). Zeitwerk eager-load verification passes;
|
|
95
|
+
the dummy application's mailer-preview directory is outside eager-load paths.
|
|
96
|
+
- New regressions include separate database connections (not transactional fixture
|
|
97
|
+
connection-sharing), null/unavailable caches, retry/rollback, counter isolation,
|
|
98
|
+
long windows, ban permanence, monitor isolation, trusted proxies, Rack responses,
|
|
99
|
+
dashboard challenges, and fresh migration execution.
|
|
100
|
+
- Authentication regressions additionally exercise real Devise/Warden requests,
|
|
101
|
+
pre-password denial, HTTP Basic, independent scopes, disabled/unavailable audits,
|
|
102
|
+
state/risk failures, native lock/session races, cleanup callbacks, and transactional
|
|
103
|
+
password-reset rollback.
|
|
104
|
+
- Risk regressions cover persisted travel through both adapters, matching score/
|
|
105
|
+
lock evidence, history order and bounds, removal of implicit trust, observation
|
|
106
|
+
isolation, malformed geography/times, midnight and browser scoring, cache/provider
|
|
107
|
+
generation isolation, and unavailable optional caches. Old discount assertions
|
|
108
|
+
were replaced with regressions that reject unsupported trust assumptions.
|
|
109
|
+
- Audit/WAF regressions cover nested/host redaction, metadata bounds, legacy model
|
|
110
|
+
reads and state projection, partial model queries, CSV formulas/column boundaries,
|
|
111
|
+
filtered-email admission accounting, both export cursors/authorization, canonical
|
|
112
|
+
matching/exclusions, benign exception policy, single charges, and optional audit
|
|
113
|
+
failures. Existing tests that required raw emails or exception messages now assert
|
|
114
|
+
redaction; attack-accounting checks use user associations and hashed counters.
|
|
115
|
+
- Dashboard/search regressions cover reporting boundaries across model/UI/export,
|
|
116
|
+
invalid/fractional scores, selected-time statistics, full-history ban totals,
|
|
117
|
+
native/Devise display privacy, stable equal-time ordering, absent phantom API
|
|
118
|
+
routes, literal search text, legacy JSON email fallback, adapter-specific SQL
|
|
119
|
+
generation, composition across pages/exports, and aggregate/preload query counts.
|
|
120
|
+
- Configuration/analysis regressions cover real application startup and host job
|
|
121
|
+
autoloading, host configuration callbacks, invalid-block publication, nested
|
|
122
|
+
default merging, unsupported providers/strategies, both authentication paths,
|
|
123
|
+
native final-session denial, real enclosing commits/savepoint rollbacks, and
|
|
124
|
+
queue exceptions/aborted enqueues. The formerly skipped background-analysis
|
|
125
|
+
test now exercises the supported opt-in contract.
|
|
126
|
+
- Notification regressions exercise actual Devise/native lock requests, real
|
|
127
|
+
commits/savepoint rollbacks, reset-token invalidation, independent user/team
|
|
128
|
+
hooks, enqueue aborts/failures, bounded delivery retries and failed retry enqueue,
|
|
129
|
+
malformed recipients, deleted users, recipient-list changes, delivery switches,
|
|
130
|
+
sanitized transport/job logging, and the full dummy native recovery handoff.
|
|
131
|
+
Only the Rails test delivery backend was used; no real emails were sent.
|
|
132
|
+
- Lifecycle/admin regressions compare every stored event field after native/Devise
|
|
133
|
+
account deletion, including legacy evidence, and exercise missing-user pages and
|
|
134
|
+
exports without rewriting retained rows. Administrative tests cover trusted actor
|
|
135
|
+
resolution, real CSRF rejection, required-history rollback, later-item bulk
|
|
136
|
+
failure, deletion callbacks, strict selections/durations, no-ops and subsecond
|
|
137
|
+
changes, ordinary history CRUD rejection, legacy read filtering, bounded history
|
|
138
|
+
paging, escaped HTML, and confirmation-form verbs. Separate SQLite connections
|
|
139
|
+
exercise concurrent manual extensions and mixed manual/automatic updates.
|
|
140
|
+
- Form/browser regressions additionally cover non-UTC application-zone parsing,
|
|
141
|
+
UTC and offset instants, invalid calendar dates/types/overflow/precision, strict
|
|
142
|
+
creation presets/defaults, permanent/temporary validation, nonce-bearing markup,
|
|
143
|
+
native navigation, and filter-preserving page sizes. Actual Chromium tests use
|
|
144
|
+
multiple browser zones, DST-boundary dates, a skewed browser clock, real CSRF,
|
|
145
|
+
host Turbo loaded from its package, JavaScript disabled, and browser console
|
|
146
|
+
assertions. They verify persistent validation feedback, precise expiry retention,
|
|
147
|
+
and no duplicate administrative operations. A separate browser CI job is added.
|
|
148
|
+
- PostgreSQL/MySQL concurrency, production throughput, hosted CI, and broader
|
|
149
|
+
cross-browser/host UI testing remain unverified. The tested CSP forbids inline
|
|
150
|
+
script handlers but explicitly allows the existing inline style attributes.
|
|
151
|
+
|
|
152
|
+
## Rollout contract
|
|
153
|
+
|
|
154
|
+
See [State storage](../operations/state-storage.md): apply the migration before starting new
|
|
155
|
+
workers, drain old cache-based workers, share one authoritative writer database,
|
|
156
|
+
review legacy bans, and schedule `beskar:cleanup_security_state`. Cache counters
|
|
157
|
+
are not imported. The test database has been migrated locally; no host production
|
|
158
|
+
database was changed.
|
|
159
|
+
|
|
160
|
+
Rails-native hosts must also adopt the login and existing-session guards in
|
|
161
|
+
[Authentication](../guides/authentication.md). No additional migration beyond the
|
|
162
|
+
shared-state table is required for batches two through seven. Custom lock strategies
|
|
163
|
+
and cross-database authentication models remain unsupported. Batch ten's
|
|
164
|
+
API/Cable/Warden adapters are documented in docs/operations/security-hardening.md.
|
|
165
|
+
|
|
166
|
+
Risk weights were not calibrated against production traffic. Review the corrected
|
|
167
|
+
factors and removal of implicit trust discounts in [Risk scoring](../guides/risk-scoring.md)
|
|
168
|
+
before enabling enforcement or emergency resets. Legacy records without explicit
|
|
169
|
+
admission evidence are not automatically promoted into travel history.
|
|
170
|
+
|
|
171
|
+
Review [Audit data and WAF](../guides/audit-and-waf.md) for changed capture/export fields,
|
|
172
|
+
host email-filter behavior, the 1,000-row export cursor contract, visible CSV text
|
|
173
|
+
prefixes, and the narrower default exception policy. No historical personal data
|
|
174
|
+
or existing bans were bulk-rewritten or deleted by this batch.
|
|
175
|
+
|
|
176
|
+
Review [Dashboard and search](../guides/dashboard-and-search.md) for the corrected reporting
|
|
177
|
+
bands, search behavior/privacy limits, native-user display, and removal of unused
|
|
178
|
+
versioned API routes/helpers. Authentication risk weights and lock thresholds were
|
|
179
|
+
not changed by the dashboard repair.
|
|
180
|
+
|
|
181
|
+
Review [Configuration](../guides/configuration.md) before deploying the sixth batch:
|
|
182
|
+
invalid/obsolete settings now stop startup, partial section assignments overlay
|
|
183
|
+
library defaults, and `:custom`/IP2Location placeholders are rejected. Automatic
|
|
184
|
+
analysis defaults off; opt in with a host job and the new keyword contract. The
|
|
185
|
+
analysis hook supplies neither a built-in analyzer nor recovery delivery.
|
|
186
|
+
|
|
187
|
+
Review [Notifications and recovery](../guides/notifications-and-recovery.md) for batch seven.
|
|
188
|
+
The three notification flags now default to false; enabling them requires explicit
|
|
189
|
+
sender/recovery/team settings and a worker consuming `beskar_notifications`.
|
|
190
|
+
Notifications use their own post-commit delivery jobs. They do not issue recovery
|
|
191
|
+
tokens, unlock accounts, guarantee delivery, or supply an outbox. Production
|
|
192
|
+
transports and host recovery/support procedures must be configured and verified.
|
|
193
|
+
|
|
194
|
+
Review [Audit lifecycle](../guides/audit-lifecycle.md) for batch eight. Apply the new
|
|
195
|
+
administrative-action migration and configure a trusted `audit_actor` before
|
|
196
|
+
dashboard writes; forms/scripted callers must supply `audit_reason`. Drain old
|
|
197
|
+
workers, whose code can still delete events with accounts or make unaudited ban
|
|
198
|
+
changes. Events now survive account deletion unchanged, not anonymized. No audit
|
|
199
|
+
retention period, purge, historical reconstruction, or production data rewrite
|
|
200
|
+
was performed. Journal protections are not a database tamper-resistance guarantee.
|
|
201
|
+
|
|
202
|
+
Review the form/browser sections of [Dashboard and search](../guides/dashboard-and-search.md)
|
|
203
|
+
for batch nine. No additional migration is needed. Expiry inputs are now UTC;
|
|
204
|
+
scripted callers must use valid UTC/offset timestamps and valid bounded duration
|
|
205
|
+
seconds. The host must allow its nonce for the behavior script; the engine does
|
|
206
|
+
not loosen CSP. Inline style attributes still require separate cleanup for hosts
|
|
207
|
+
with strict style policies. Native navigation is deliberate, including with host
|
|
208
|
+
Turbo loaded; it is not support for arbitrary Turbo Frames/Streams integrations.
|
|
209
|
+
|
|
210
|
+
For batch ten, apply `ExpandAdministrativeActionTargets`, configure explicit
|
|
211
|
+
dashboard grants and actor resolution, and update exports to supply a reason.
|
|
212
|
+
Adopt the API/Cable/native adapters, bind credential generations, and plan the
|
|
213
|
+
one-time Devise session invalidation. Configuration is now sealed after boot.
|
|
214
|
+
Global login capacity and request-wide quota blocking are explicit opt-ins.
|
|
215
|
+
Read [Security hardening and rollout](../operations/security-hardening.md) before rolling out; host
|
|
216
|
+
integration, deployment auditing and PostgreSQL/MySQL/load gates remain open.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Audit data, exports, and WAF matching
|
|
2
|
+
|
|
3
|
+
This contract was introduced in the fourth remediation batch. The fifth batch
|
|
4
|
+
adds [consistent dashboard reporting and search](dashboard-and-search.md).
|
|
5
|
+
The eighth batch adds [account-deletion retention and administrative history](audit-lifecycle.md).
|
|
6
|
+
Earlier examples
|
|
7
|
+
that retain full URLs/exception messages or score every Rails exception are
|
|
8
|
+
historical. See [Repair status](../audits/repair-status.md) for remaining findings.
|
|
9
|
+
|
|
10
|
+
## Audit capture and disclosure
|
|
11
|
+
|
|
12
|
+
WAF matching uses request data transiently. Persisted WAF evidence contains rule
|
|
13
|
+
IDs, static descriptions/categories, severity, rules version, decoding-pass count,
|
|
14
|
+
an allowlisted HTTP method/exception class, timestamps, and scores. The event
|
|
15
|
+
retains the Rails-resolved IP for attribution, subject to host audit filters.
|
|
16
|
+
Raw/canonical paths, query strings, exception messages, and User-Agent headers are
|
|
17
|
+
not copied into WAF events, ban metadata, state, or WAF messages. Public
|
|
18
|
+
`record_violation` calls also project old-style analysis onto this bounded schema.
|
|
19
|
+
The state API strips legacy raw-path/description fields on reads and on the next
|
|
20
|
+
normal write; it does not bulk-rewrite historical database contents.
|
|
21
|
+
|
|
22
|
+
Authentication audits still retain bounded paths, cleaned HTTP(S) referrers,
|
|
23
|
+
device/geographic evidence, IPs, and bounded User-Agent text. Referrers exclude
|
|
24
|
+
credentials, queries, and fragments; session IDs and raw forwarded headers are
|
|
25
|
+
not captured. These records remain sensitive and require access controls.
|
|
26
|
+
|
|
27
|
+
`Services::AuditData` bounds metadata and applies both built-in sensitive-key
|
|
28
|
+
filters and `Rails.application.config.filter_parameters`. Built-ins cover
|
|
29
|
+
passwords, secrets, tokens, authorization, cookies, session IDs, CSRF,
|
|
30
|
+
`exception_message`, `fullpath`, and `matched_path`. Nested keys are filtered.
|
|
31
|
+
Host email filters now redact attempted emails and associated-user presentation
|
|
32
|
+
in views and exports; account associations and hashed admission counters remain intact.
|
|
33
|
+
Plaintext email searches cannot find newly redacted email values.
|
|
34
|
+
|
|
35
|
+
Model validation and model loading sanitize:
|
|
36
|
+
|
|
37
|
+
| Model | Filtered audit fields |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| SecurityEvent | Metadata, event type (100 characters), IP text (64), User-Agent (500), attempted email (320) |
|
|
40
|
+
| BannedIp | Metadata, reason (100 characters), details (2,048) |
|
|
41
|
+
| AdministrativeAction | Actor/request ID (200), reason (1,000), bounded before/after state projections |
|
|
42
|
+
| Native lock state | Metadata supplied when locking; lock authority is separate |
|
|
43
|
+
|
|
44
|
+
Ban IP addresses, permanence, expiry, IDs, and other enforcement fields are
|
|
45
|
+
deliberately not passed through audit filters. They remain visible to authorized
|
|
46
|
+
dashboard/export readers. Redacting a ban IP must never disable enforcement.
|
|
47
|
+
Host filters targeting event types, geolocation, or authentication evidence can
|
|
48
|
+
reduce audit reporting and history-based risk enrichment; review that policy
|
|
49
|
+
explicitly. Admission state does not depend on a successful optional audit write.
|
|
50
|
+
|
|
51
|
+
Metadata admits JSON-compatible values, at most 64 entries per hash, 50 per
|
|
52
|
+
array, depth 10, and a 512-node traversal budget. Keys are at most 128 bytes;
|
|
53
|
+
string values are at most 2,048 characters, with controls replaced and invalid
|
|
54
|
+
UTF-8 repaired. Non-finite numbers become null. Excessively large serialized
|
|
55
|
+
metadata becomes `{"_truncated":true}` (64-KiB limit); depth/budget exhaustion uses
|
|
56
|
+
`[TRUNCATED]`. Do not treat truncated audit data as complete evidence.
|
|
57
|
+
|
|
58
|
+
Read-time filtering changes loaded objects, not stored legacy rows. Raw SQL,
|
|
59
|
+
`pluck`/`pick`, bulk inserts, and writes that skip validation bypass model
|
|
60
|
+
sanitization. No historic rows, logs, backups, or exports were purged by this
|
|
61
|
+
repair. Arbitrary secrets embedded in allowed free text, path segments, or
|
|
62
|
+
User-Agent text cannot be reliably identified by key-based filters. Hosts must
|
|
63
|
+
filter/drop those fields if their application places secrets there.
|
|
64
|
+
|
|
65
|
+
Beskar's rescued-exception log messages now use the exception class instead of
|
|
66
|
+
its message. This does not sanitize the host application's own request logs,
|
|
67
|
+
exception reporters, custom callbacks, or externally supplied free-text log
|
|
68
|
+
arguments. Account deletion now retains events unchanged, and dashboard ban changes
|
|
69
|
+
require transactional administrative history; see [Audit lifecycle](audit-lifecycle.md).
|
|
70
|
+
An operational retention period, historical cleanup, and tamper-resistant archival
|
|
71
|
+
remain separate work.
|
|
72
|
+
|
|
73
|
+
## Export contract
|
|
74
|
+
|
|
75
|
+
Both event and ban exports require dashboard authentication, an explicit `:export`
|
|
76
|
+
permission, trusted actor, and a nonblank reason (`audit_reason` or
|
|
77
|
+
`X-Beskar-Audit-Reason`). Each page requires a persisted administrative export
|
|
78
|
+
record before data is sent; see [Audit lifecycle](audit-lifecycle.md).
|
|
79
|
+
CSV and JSON return at most 1,000 records in descending ID order. They set
|
|
80
|
+
`Cache-Control: private, no-store`, `X-Content-Type-Options: nosniff`, and:
|
|
81
|
+
|
|
82
|
+
- `X-Beskar-Export-Limit: 1000`.
|
|
83
|
+
- `X-Beskar-Export-Truncated: true|false`.
|
|
84
|
+
- `X-Beskar-Next-Cursor` when more rows remain.
|
|
85
|
+
|
|
86
|
+
Pass that cursor as `before_id` with the same filters and format for the next
|
|
87
|
+
page. Malformed/nonpositive/out-of-range IDs return 422. Exports no longer load
|
|
88
|
+
the entire relation or silently override ordering with `find_each`.
|
|
89
|
+
Pagination is not a transactional snapshot: concurrent edits/deletions and
|
|
90
|
+
relative-time filters can change membership. Newly inserted higher IDs require
|
|
91
|
+
starting a new export. The 1,000-row cap bounds rows, not database query cost.
|
|
92
|
+
|
|
93
|
+
CSV fields are quoted/escaped, bounded, and dangerous textual prefixes receive a
|
|
94
|
+
visible `text: ` marker. Detection covers leading `=`, `+`, `-`, and `@`,
|
|
95
|
+
including preceding controls/whitespace/formatting characters and Unicode
|
|
96
|
+
compatibility variants. Numeric database fields stay numeric. This intentionally
|
|
97
|
+
changes exported text; JSON preserves the filtered text without a spreadsheet
|
|
98
|
+
marker. Neither format exports an associated user object's custom serialization:
|
|
99
|
+
only its ID and filtered email are included.
|
|
100
|
+
|
|
101
|
+
Quoting alone is not a formula defense, and spreadsheet import/save/reopen
|
|
102
|
+
behavior varies. The visible prefix avoids relying solely on a removable
|
|
103
|
+
apostrophe. See [OWASP's CSV injection guidance](https://owasp.org/www-community/attacks/CSV_Injection).
|
|
104
|
+
Automated tests cover emitted CSV cells and parsed column boundaries, not actual
|
|
105
|
+
Excel/LibreOffice/Sheets clients or their save/reopen behavior. Test the supported
|
|
106
|
+
client workflow before claiming spreadsheet-client safety.
|
|
107
|
+
|
|
108
|
+
## WAF matching contract
|
|
109
|
+
|
|
110
|
+
The WAF is a scanner-path heuristic, not a general SQL injection/XSS engine.
|
|
111
|
+
It does not parse request bodies or implement JavaScript challenges/honeypots.
|
|
112
|
+
|
|
113
|
+
- Match at most 8,192 path bytes, with at most two percent-decoding passes.
|
|
114
|
+
- Convert backslashes to slashes, preserve literal plus signs, and retain dot
|
|
115
|
+
segments so traversal is detectable rather than erased by normalization.
|
|
116
|
+
- Flag oversized paths, malformed escapes, invalid UTF-8/control bytes, and
|
|
117
|
+
excessive encoding as medium-severity malformed-path evidence.
|
|
118
|
+
- Use root/segment boundaries to avoid matches inside ordinary words.
|
|
119
|
+
Ordinary `.well-known` routes are not scanner signatures.
|
|
120
|
+
- Ignore arbitrary query text. Only a flat, exact `format` query key with an
|
|
121
|
+
allowlisted executable value is checked; queries over 8,192 bytes are skipped.
|
|
122
|
+
- Retain matched rule IDs with `rules_version: 1`, not the matching input.
|
|
123
|
+
|
|
124
|
+
Use narrow exclusions for legitimate host routes:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
Beskar.configure do |config|
|
|
128
|
+
config.waf[:exception_detection] = :suspicious
|
|
129
|
+
config.waf[:request_exclusions] = [
|
|
130
|
+
{path: %r{\A/wp-content/}, methods: ["GET", "HEAD"],
|
|
131
|
+
categories: [:wordpress_static]},
|
|
132
|
+
{path: %r{\A/reports/}, methods: ["GET"], categories: [:unknown_format]}
|
|
133
|
+
]
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Exclusions use the decoded path. Omitted methods/categories mean all methods/
|
|
138
|
+
categories for that path. A static-file exclusion does not exclude traversal or
|
|
139
|
+
configuration-file rules. Existing `record_not_found_exclusions` still apply
|
|
140
|
+
to RecordNotFound exception analysis, not independent path signatures.
|
|
141
|
+
|
|
142
|
+
Exception policies:
|
|
143
|
+
|
|
144
|
+
- `:suspicious` (default): ordinary known Rails exceptions need independent
|
|
145
|
+
path/format evidence. A missing record or unsupported format alone is not abuse.
|
|
146
|
+
- `:all`: opt into broad scoring of the four known exception classes; legitimate
|
|
147
|
+
errors can accumulate enough points to ban an IP.
|
|
148
|
+
- `:none`: disable exception scoring, without disabling request-path matching.
|
|
149
|
+
|
|
150
|
+
The known classes are UnknownFormat, InvalidType, RecordNotFound, and
|
|
151
|
+
IpSpoofAttackError (matched by exact class name). Middleware only attributes
|
|
152
|
+
IP-spoof exceptions when an IP was safely resolved; it never substitutes an
|
|
153
|
+
attacker-supplied forwarded header. Application exceptions still propagate.
|
|
154
|
+
|
|
155
|
+
One middleware pass records at most one violation. Matching several path rules
|
|
156
|
+
uses the highest path severity; a subsequent application exception neither
|
|
157
|
+
adds another charge nor upgrades that earlier charge. Scores remain cumulative:
|
|
158
|
+
one critical signature adds 95 points, below the default threshold of 150.
|
|
159
|
+
This is not a promise to block the first exploit request. Monitor/whitelist
|
|
160
|
+
observations remain isolated from enforcement state.
|
|
161
|
+
|
|
162
|
+
## Rollout and remaining verification
|
|
163
|
+
|
|
164
|
+
The fourth-batch changes require only the first batch's shared-state migration;
|
|
165
|
+
batch eight also requires the administrative-action table described in
|
|
166
|
+
[Audit lifecycle](audit-lifecycle.md). Review changed audit fields, host filters, cursor-based export
|
|
167
|
+
consumers, and default exception policy before deployment. Existing enforced bans
|
|
168
|
+
are not automatically forgiven by the narrower matching policy.
|
|
169
|
+
|
|
170
|
+
Run monitor-first against real host routes and mounted paths. Local benign/attack
|
|
171
|
+
corpora cover percent/double encoding, backslashes, query poisoning, boundaries,
|
|
172
|
+
exclusions, ordinary Rails exceptions, legacy state, and single-charge behavior.
|
|
173
|
+
These tests do not establish production false-positive rates or complete scanner
|
|
174
|
+
coverage. Production database fault/load tests, spreadsheet-client QA, historical
|
|
175
|
+
data cleanup, and operational retention policies remain unverified or unimplemented.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Audit lifecycle and administrative changes
|
|
2
|
+
|
|
3
|
+
This is the eighth remediation batch's contract. Earlier documentation describing
|
|
4
|
+
account-deletion cascades or unaudited dashboard ban changes is historical.
|
|
5
|
+
|
|
6
|
+
## Account deletion: retain events unchanged
|
|
7
|
+
|
|
8
|
+
Deleting a native or Devise account no longer destroys its linked security events.
|
|
9
|
+
Beskar leaves every stored event field unchanged, including `user_type`, `user_id`,
|
|
10
|
+
timestamps, attempted email, IP address, and metadata. The association resolves to
|
|
11
|
+
nil once the account is gone; dashboard pages and exports tolerate that absence.
|
|
12
|
+
JSON exports retain the original polymorphic identifiers without embedding a
|
|
13
|
+
nonexistent user. Existing read-time privacy filters still apply without rewriting
|
|
14
|
+
stored rows.
|
|
15
|
+
|
|
16
|
+
This is **retention, not anonymization**. Identifiers and evidence can still identify
|
|
17
|
+
people. Beskar does not nullify the linkage, impose an expiry, or supply an automatic
|
|
18
|
+
event purge. Unidentified failed-login events also remain independent of account
|
|
19
|
+
deletion. Previously cascade-deleted events are not recovered, and this change
|
|
20
|
+
does not rewrite historical data or backups.
|
|
21
|
+
|
|
22
|
+
Hosts must choose and operate any retention period, access controls, backup policy,
|
|
23
|
+
or explicitly authorized erasure workflow separately. Avoid reusing account IDs;
|
|
24
|
+
the retained polymorphic reference can resolve to a replacement row with the same
|
|
25
|
+
type/ID. Model renames/removals also require a host migration strategy. Host-defined
|
|
26
|
+
callbacks, bulk SQL, and database rules can override this lifecycle. Normal
|
|
27
|
+
SecurityEvent save/update/update_columns/destroy/delete operations are now
|
|
28
|
+
rejected after creation. This is append-only model behavior, not tamper-proof
|
|
29
|
+
database storage or a restriction on privileged host code.
|
|
30
|
+
|
|
31
|
+
## Trusted administrative actor
|
|
32
|
+
|
|
33
|
+
Dashboard writes require both the existing authorization callback and a separate
|
|
34
|
+
server-side `audit_actor` resolver. For a host with a Devise `User#admin?` role:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
Beskar.configure do |config|
|
|
38
|
+
config.authenticate_admin = ->(request) do
|
|
39
|
+
request.env["warden"]&.authenticate(scope: :user)&.admin?
|
|
40
|
+
end
|
|
41
|
+
config.audit_actor = ->(request) do
|
|
42
|
+
user = request.env["warden"]&.user(scope: :user)
|
|
43
|
+
"User:#{user.id}" if user&.admin?
|
|
44
|
+
end
|
|
45
|
+
config.authorize_admin = ->(request, permission) do
|
|
46
|
+
user = request.env["warden"]&.user(scope: :user)
|
|
47
|
+
user&.admin? && user.beskar_permissions.include?(permission.to_s)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Adapt the scope, role, and `beskar_permissions` lookup to the host. Permission
|
|
53
|
+
names are `read`, `manage_bans`, `export`, and `read_audit`; none is implied by
|
|
54
|
+
another or by successful authentication. Missing grants deny access (403).
|
|
55
|
+
These callbacks run in controller
|
|
56
|
+
context; the actor callback runs only after dashboard authorization and CSRF
|
|
57
|
+
verification, once per mutation request. Native hosts should derive the identifier
|
|
58
|
+
from their already authenticated administrative session. A shared Basic/token
|
|
59
|
+
credential may use an explicit stable service identifier, but that records a
|
|
60
|
+
shared identity, not which individual used it.
|
|
61
|
+
|
|
62
|
+
The resolver must return an opaque string of 1–200 ASCII characters, starting with
|
|
63
|
+
a letter/digit and otherwise containing letters, digits, `:`, `_`, `.`, `/`, or `-`.
|
|
64
|
+
Use a stable type/ID, never an email, password, token, request parameter, or
|
|
65
|
+
unverified header. Beskar cannot verify the truth of a host callback's identity.
|
|
66
|
+
Authorization's boolean result is not inferred to be an actor.
|
|
67
|
+
|
|
68
|
+
`audit_actor` defaults to nil. Separately authorized reads still work, but mutations and exports return
|
|
69
|
+
503 when the resolver is missing, invalid, or raises. Startup validates Proc-or-nil
|
|
70
|
+
without executing the callback. Every mutation also requires `audit_reason`, a
|
|
71
|
+
nonblank string of at most 1,000 characters; invalid input returns 422. Prefer a
|
|
72
|
+
case reference and short explanation. Key-based filtering cannot identify arbitrary
|
|
73
|
+
secrets embedded in free text.
|
|
74
|
+
|
|
75
|
+
## Journal and transactional behavior
|
|
76
|
+
|
|
77
|
+
Apply `ExpandAdministrativeActionTargets` as well as the original journal migration.
|
|
78
|
+
Entries now distinguish `BannedIp`, `SecurityEvent`, and `Configuration` targets;
|
|
79
|
+
collection/configuration entries have no target ID. Export preparation records
|
|
80
|
+
the actor, required reason, request, format, filtered query, count, ID bounds,
|
|
81
|
+
and truncation before sending data. A journal failure returns 503 without an
|
|
82
|
+
export body. Supply `audit_reason` or `X-Beskar-Audit-Reason` on every cursor page.
|
|
83
|
+
The record is not proof of successful client download. Runtime configuration
|
|
84
|
+
publication has a separate permission and journal; see [Configuration](configuration.md).
|
|
85
|
+
|
|
86
|
+
`beskar_administrative_actions` records dashboard ban creation, updates, unbans,
|
|
87
|
+
extensions, and conversion to permanent bans. Each changed target receives:
|
|
88
|
+
|
|
89
|
+
- A server-resolved actor, required reason, action name, ban ID, and creation time.
|
|
90
|
+
- A server-generated operation UUID shared by all targets of one bulk operation.
|
|
91
|
+
- The Rails request ID, bounded to 200 characters, for correlation only. It may
|
|
92
|
+
originate in a client `X-Request-ID` header and is not proof of identity.
|
|
93
|
+
- Filtered, bounded before/after projections of ban ID/IP, reason, details,
|
|
94
|
+
permanence, ban/expiry times, violation count, and metadata. Creation has an empty
|
|
95
|
+
before-state; unban has an empty after-state.
|
|
96
|
+
|
|
97
|
+
The projections use [AuditData filtering and bounds](audit-and-waf.md), not a raw
|
|
98
|
+
database copy. Timestamp precision, redaction, and truncation can make projections
|
|
99
|
+
look identical despite a real stored change; such changes still receive an entry.
|
|
100
|
+
The journal is not a full-fidelity restore backup.
|
|
101
|
+
|
|
102
|
+
Ban changes and required journal inserts share one writer-database transaction and
|
|
103
|
+
the same coordination keys used by automatic escalation. Bulk requests accept
|
|
104
|
+
1–100 positive ban IDs, deduplicate and sort them, and require every target to
|
|
105
|
+
exist. Missing targets return 404; malformed inputs and unsupported operations or
|
|
106
|
+
extension durations return 422. Supported extensions are `1h`, `6h`, `24h`, `7d`,
|
|
107
|
+
and `30d`; making a ban permanent is a separate operation. Existing permanent bans
|
|
108
|
+
cannot be extended. Manual extensions do not increment violation counts; automatic
|
|
109
|
+
`BannedIp.extend_ban!` behavior is unchanged. Dashboard edits cannot change a ban's
|
|
110
|
+
IP identity.
|
|
111
|
+
|
|
112
|
+
There is no partial bulk success. A failed target callback or required history
|
|
113
|
+
insert rolls back the transaction, including earlier items. Success messages follow
|
|
114
|
+
successful completion; Active Record errors and rejected persistence callbacks
|
|
115
|
+
return 503 rather than claiming success (other unexpected exceptions propagate).
|
|
116
|
+
If a connection fails during commit, the client may not know whether the
|
|
117
|
+
transaction committed: reload state/history before retrying. Operation UUIDs are
|
|
118
|
+
correlation, not a client retry/idempotency protocol. Exact no-op updates create no
|
|
119
|
+
history, and the bulk response reports the count actually changed.
|
|
120
|
+
|
|
121
|
+
Authenticated history pages at `/beskar/administrative_actions` and `/:id` are
|
|
122
|
+
read-only, paginated (at most 100 rows/page), HTML-escaped, and marked `no-store`.
|
|
123
|
+
Filter the index with `target_id`. There are no history edit/delete/export routes.
|
|
124
|
+
History has no actor or ban foreign key and survives their deletion. Ordinary
|
|
125
|
+
instance save/update, `update_columns`, destroy, and delete attempts are rejected.
|
|
126
|
+
This is **not tamper-proof storage**: low-level counter/bulk APIs, raw SQL, and
|
|
127
|
+
privileged database access can bypass model protections. Database access policy,
|
|
128
|
+
external archival, and tamper detection remain separate work.
|
|
129
|
+
|
|
130
|
+
Unban and row-extension links now open review pages with real forms and required
|
|
131
|
+
reasons; their GET requests do not mutate state. New/edit/inline-extension/bulk
|
|
132
|
+
forms also require reasons. Batch nine consolidates behavior in a nonce-bearing
|
|
133
|
+
script, removes inline event handlers/method-link synthesis, and adopts native
|
|
134
|
+
navigation. See [Dashboard and search](dashboard-and-search.md) for UTC inputs,
|
|
135
|
+
JavaScript-disabled behavior, browser verification, and the remaining style-CSP limit.
|
|
136
|
+
|
|
137
|
+
## Coverage and deployment boundaries
|
|
138
|
+
|
|
139
|
+
The journal is prospective and covers dashboard exports, audited runtime
|
|
140
|
+
configuration publication, and dashboard/explicit `AdministrativeBans` changes.
|
|
141
|
+
It does **not** automatically journal every
|
|
142
|
+
direct model/manager/console write, WAF transition, expiry cleanup, or failed action
|
|
143
|
+
attempt. Existing administrative history is not reconstructed. For host-owned
|
|
144
|
+
manual workflows, authorize the operator first, then create one service instance
|
|
145
|
+
per operation with trusted `actor:`, required `reason:`, and a nonblank `request_id:`:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
Beskar::Services::AdministrativeBans.new(
|
|
149
|
+
actor: "Operator:42", reason: "Case 123: false positive", request_id: SecureRandom.uuid
|
|
150
|
+
).change!([ban.id], action: "unban")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The service itself is not an authorization boundary. As with coordinated security
|
|
154
|
+
state, bans, journal, and state rows must use the same authoritative writer pool.
|
|
155
|
+
Any `Rails.cache` backend remains supported.
|
|
156
|
+
|
|
157
|
+
Before rollout, copy and apply `CreateBeskarAdministrativeActions` and
|
|
158
|
+
`ExpandAdministrativeActionTargets` along with any
|
|
159
|
+
missing earlier migrations (`bin/rails beskar:install:migrations`, then
|
|
160
|
+
`bin/rails db:migrate`). Configure `authorize_admin` and `audit_actor`, update scripted dashboard callers
|
|
161
|
+
to supply `audit_reason`, and drain/restart old workers: older code can still delete
|
|
162
|
+
events on account deletion or mutate bans without the journal. The new table was
|
|
163
|
+
applied only to the local test database; no production database was changed.
|
|
164
|
+
|
|
165
|
+
No cleanup job is added for either audit table. Per-entry bounds do not bound total
|
|
166
|
+
storage; hosts must monitor growth and choose retention deliberately. Local tests
|
|
167
|
+
cover both account adapters, unchanged raw rows after deletion/read/export, real
|
|
168
|
+
CSRF rejection, required-history and later-bulk-item rollback, escaped HTML/forms,
|
|
169
|
+
fresh migration execution, and simultaneous manual/automatic updates using separate
|
|
170
|
+
SQLite connections. Batch nine adds Chromium form/CSRF/navigation checks. Live
|
|
171
|
+
PostgreSQL/MySQL, production load, broader host browser flows, and tamper-resistant
|
|
172
|
+
external archival remain unverified.
|