Files
huntingclubroster/SECURITY-CAPACITY-2026-10-20.md
T

9.1 KiB
Raw Blame History

Administrator security and shared capacity — October 20, 2026

Implements the first four remaining multi-tenant review recommendations. All database verification uses disposable local databases. This work does not migrate the development database or enroll any real account's authenticator.

1. Administrator MFA and recent authentication

  • Added maintained spomky-labs/otphp 11.5.0 and its locked dependencies.
  • Every authenticated request passes an MFA gate before application dispatch. Super System Administrators and active club administrators must enroll and complete MFA; an ordinary global User role does not bypass a club administrator membership. Enrolled ordinary members must also complete their chosen MFA.
  • Existing password login establishes the first factor only. Direct URLs and AJAX requests cannot use administrator functionality before the gate completes.
  • Setup requires the current password and a working authenticator code. The setup page offers a locally generated QR code and a manual key for authenticator apps using SHA-1, six digits and 30-second periods. The QR uses the same pending secret, the application issuer and account email. A white quiet zone preserves scan contrast on dark pages. Pending setup expires after ten minutes. No third-party QR service receives the secret, and QR images are not saved to disk.
  • Secrets use AES-256-GCM with a random nonce and account ID as authenticated data. HCR_MFA_KEY is a separate, required 32-byte encryption key, encoded as base64. Database records do not contain plaintext secrets.
  • Verifies the current or adjacent time step, rejecting counters already used. Verification locks the factor row. Ten random 128-bit recovery codes are displayed once; only SHA-256 hashes are stored. Consumption and mandatory audit commit together. Failed audit writes roll back consumption.
  • Limits authentication attempts per account and IP. Successful MFA rotates the session ID and CSRF token. Password login clears earlier MFA verification; password resets preserve the factor and its replay counter.
  • Account security is linked from the home page. Lost authenticator replacement requires the password and an unused recovery/current authenticator code. It retains the existing factor while the authorized session configures a new one. Confirmation atomically replaces the factor, refreshes recovery codes and revokes other sessions. See MFA repairs.
  • Global account/security changes and membership role/status changes require password plus MFA confirmation within five minutes. The original write is never replayed automatically after authentication; the user submits it again.
  • MFA enrollment/replacement invalidates other account sessions. Session versions are captured under lock, avoiding adoption of a concurrent password-reset version. Recovery does not use email-only factor removal.

2. Absolute session lifetime

  • AuthService records an authentication start timestamp at password login and enforces an eight-hour maximum lifetime independently of the existing 30-minute idle timeout.
  • Activity, MFA and recent-authentication confirmation do not extend that start time. Legacy sessions without a start timestamp must sign in again.

3. Bounded collections and exports

  • Added shared count/paging support using the same authorized SQL for both.
  • Club navigation, roster members, seasons, dues, payment histories, corrections, administrator invitation lists, pending invitations and ticket messages now have independent 25-record pages with first/previous/next/last controls.
  • Page numbers are validated and clamped. Links preserve the selected club and other page/filter context. Personal member visibility applies to counts as well as rows. Older records remain accessible through the pager.
  • Dues-entry member/season selectors use their corresponding displayed pages; navigate those pages to choose older seasons or additional members.
  • Added a read-only, authorized report-catalog route for page navigation. Its club list counts only clubs the administrator can manage.
  • Roster CSV/Excel/PDF and financial CSV exports reject oversized collections before loading them, with a default 1,000-row limit. Queries retain a second hard bound so concurrent growth cannot create unbounded allocations. HCR_EXPORT_MAX_ROWS accepts 1–5,000. Oversized exports return a clear error, never a silently truncated file; a large-export workflow is not added here.

4. Shared club resource budgets

Each club has independent hourly counters for:

Resource Default per hour Configuration
Invitation email attempts 100 HCR_CLUB_EMAIL_HOURLY
New tickets 100 HCR_CLUB_TICKET_HOURLY
Ticket replies 1,000 HCR_CLUB_REPLY_HOURLY
Exports, across all formats 20 HCR_CLUB_EXPORT_HOURLY
  • Configured budgets accept 1–100,000; invalid configuration fails closed.
  • Existing individual rate limits remain. Changing users or formats does not reset the club counter. Another club has a separate budget.
  • Atomic database increments return each connection's own assigned slot, so simultaneous requests cannot exceed the configured budget.
  • Permission checks precede consumption. Budgets count authorized attempts, including failed delivery or later validation; they are deliberately not refunded by transaction retries or failed writes.
  • Counters occupy one row per club/resource and reset on the next attempt after an hour, so request history does not grow without a bound.

Deployment steps

  1. Preserve a backup, then run bin/migrate-security-capacity.php with migration credentials after the existing migrations. It adds user_mfa, mfa_recovery and club_resource_usage; rerunning it preserves existing data.
  2. Inject a unique random HCR_MFA_KEY through the application's secret store. Generate 32 cryptographically random bytes and base64-encode them. Never use the disposable test key. Keep a protected recovery copy separate from SQL backups; changing or losing the key prevents decrypting existing factors. Development can instead set MfaKeyFile in its private local settings to a protected file outside the application directory. An explicit environment key takes precedence. Production does not allow this development fallback.
  3. Apply the new table grants in deploy/runtime-grants.sql.example to the restricted runtime account. The health check now verifies these tables and, in Production, the MFA key format.
  4. Deploy the complete locked dependencies and updated source together. The release builder includes the migration and new classes. Application version is now 1.04.
  5. Administrators sign in and enroll their own authenticator, saving their recovery codes offline. Keep server clocks synchronized. If both the device and all recovery codes are lost, recovery requires a trusted operator to verify identity and perform an audited database recovery; there is no public email-only bypass.

Production host acceptance, sender-domain/inbox checks, off-host recovery and realistic sustained load testing remain separate deployment work. These changes do not establish production hosting readiness by themselves.

Verification

Regression verification covers 675 checks and ten concurrency scenarios:

  • Existing 584 authentication, maintenance, tenant isolation, financial, browser, backup and deployment checks, plus nine existing concurrency races.
  • 24 HTTP MFA/session checks, 41 service checks, 11 large-list/export HTTP checks and 15 shared-budget HTTP checks.
  • Twelve simultaneous requests from two administrators compete for five export slots: exactly five succeed and seven receive the limit response.
  • MFA checks include valid code verification, counter replay rejection, single-use recovery, audit-failure rollback, account-bound encryption, password-reset factor preservation, lost-device replacement, recent authentication, promotion-triggered enrollment and both session timeouts.
  • Source validation covers 60 PHP files, 57 application JavaScript files and 39 XSL stylesheets, plus bundled browser dependency integrity. Composer installation reported no security advisories for the locked dependencies.

The existing regression suites and all new suites passed across the full run and targeted follow-up runs. Test databases are disposable and no real email is sent. The isolated local database server was stopped after verification.

The later development migration and acceptance run verified the persistent key-file configuration with eight additional regression checks and 68 acceptance checks against the backed-up development database.

The subsequent QR setup change passed ten browser checks using an independent decoder, 24 existing MFA/security HTTP checks and 16 deployment checks. Browser coverage includes scanning, enrollment using the scanned secret, phone-width layout, stable pending setup details, replacement and absence of external QR requests. QR rendering was also verified on ositsdevels without changing a real account. No new application dependency or database migration was required.