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

153 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](MFA-REPAIRS-2026-10-20.md).
- 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](SECURITY-MIGRATION-2026-10-20.md)
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.